#include "config.h" #include "crc.h" #include "error.h" #include #include #include #include #include #include #include #include #ifndef O_BINARY #define O_BINARY 0 #endif static int debug; static int summary; static int verbose; static int even_parity; static int read_from_tape; static int write_tap_format; // currently, this is never set static int gen_output; static char *prefix = "file"; static char *fn_format = "%s%s%05d.dat"; static int total_mismatch; static int total_128_mismatch; static int total_parity; static int total_premature_eof; static int max_parity_errors = 10000; static int total_files_with_parity; static int total_tapes_with_parity; static int total_overflow_aborts; static int total_excess_error_aborts; static int total_length_mismatch_aborts; static int total_files; static int total_records; static long long total_bytes; static int bits_in[] = { 0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4, 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, }; typedef struct { int errors; int counts; } Freqs; static int ff_size, tf_size; static Freqs *file_freqs, *tape_freqs; static int freq_cmp(const void *vp1, const void *vp2) { const Freqs *f1 = vp1; const Freqs *f2 = vp2; return f1->errors - f2->errors; } static void accumulate(Freqs **freqs, int *size, int errors) { int pos; for (pos = 0; pos < *size; pos++) if ((*freqs)[pos].errors == errors) break; if (pos == *size) { *size = pos + 1; *freqs = realloc(*freqs, (pos+1) * sizeof(**freqs)); (*freqs)[pos].errors = errors; (*freqs)[pos].counts = 0; } (*freqs)[pos].counts++; } static int32_t unsign(int32_t len) { return len & ~0x80000000; } // read_fully -- collects partial reads into a complete read request. // Required on VMS when reading a byte stream from a fixed record // length file. static ssize_t read_fully(int fd, void *buf, size_t len) { size_t have = 0; size_t need = len; while (need > 0) { ssize_t got = read(fd, ((char *)buf) + have, need); if (got <= 0) return got; have += got; need -= got; } return len; } static int32_t read_rec_len(int fd) { int32_t len; uint8_t len_buf[4]; ssize_t got = read_fully(fd, len_buf, sizeof(len_buf)); if (got == 0) return 0; // At EOF if (got != sizeof(len_buf)) fail(errno, "Can't read record length"); len = (len_buf[3]<<24) + (len_buf[2]<<16) + (len_buf[1]<<8) + len_buf[0]; if (debug) printf("length %d (%02x, %02x, %02x, %02x) at %ld\n", len, len_buf[0], len_buf[1], len_buf[2], len_buf[3], (long)lseek(fd, 0, SEEK_CUR)); return len; } // read_record -- reads one record, either from a tape drive or from a // JBI ".tap" formatted tape image file. static ssize_t read_record(int fd, void *buf, size_t len) { if (read_from_tape) return read(fd, buf, len); // Read and return a .tap file record. int32_t rec_len = read_rec_len(fd); if (rec_len == 0) { if (lseek(fd, 0, SEEK_CUR) != 4) return 0; //EOF if (!summary) warn(0, "Skipping end-of-file mark at beginning of file"); return read_record(fd, buf, len); // retry on EOF at BOT } int32_t ulen = unsign(rec_len); if (len < (unsigned)ulen) { total_overflow_aborts += 1; if (summary && fail_longjmp) longjmp(fail_jmp_buf, fail_longjmp); fail(0, "rec_len (%d) exceeds buf len(%d)", ulen, len); } off_t rec_pos = lseek(fd, 0, SEEK_CUR); int got = read_fully(fd, buf, ulen); if (got != ulen) { if (got == 0) { total_premature_eof += 1; return 0; } fail(errno, "Can't read %d bytes; got %d at pos %ld", ulen, got, lseek(fd, 0, SEEK_CUR)); } int32_t tail_len = read_rec_len(fd); if (ulen != unsign(tail_len)) { total_mismatch += 1; if (ulen != 128 && unsign(ulen) != 0) { total_length_mismatch_aborts += 1; if (summary && fail_longjmp) longjmp(fail_jmp_buf, fail_longjmp); fail(0, "L1(%08x) != L2(%08x) at pos %ld", ulen, unsign(tail_len), lseek(fd, 0, SEEK_CUR)); } total_128_mismatch += 1; if (!summary) warn(0, "Skipping mismatched 128 byte record at offset %ld", rec_pos); lseek(fd, rec_pos, SEEK_SET); return read_record(fd, buf, len); } errno = 0; if (ulen != rec_len) return -1; // parity error if (even_parity) { int i, even, msb; for (i = even = msb = 0; i < rec_len; i++) { uint8_t byte = ((uint8_t *)buf)[i]; msb += !!(byte & 0x80); even += (bits_in[byte & 0x7f] & 1); } if (msb) printf("%d, %d, %d\n", i, even, msb); } return ulen; } static int start_file(unsigned long *crc, int *file_len, int *file_num, const char *dst_base, const char *label, char *dst_fn) { int dst_fd = -1; *crc = 0; *file_len = 0; *file_num += 1; total_files += 1; if (dst_base) { sprintf(dst_fn, fn_format, dst_base, label, total_files); dst_fd = open(dst_fn, O_CREAT | O_WRONLY | O_TRUNC, 0666, "ctx=bin", "rat=none", "rfm=var"); if (dst_fd < 0) fail(errno, "Can't create output file %s", dst_fn); } return dst_fd; } static void copy(const char *src_fn, const char *dst_base, const char *label) { FILE *rpt_fp; int src_fd, dst_fd, file_started; int len, rec, min, max; int file_num, tape_len, file_len; int file_errors, tape_errors; unsigned long crc; char rpt_fn[500], dst_fn[1000], buf[64*1024]; src_fd = open(src_fn, O_RDONLY | O_BINARY); if (src_fd < 0) fail(errno, "Can't open tape %s", src_fn); #if __VMS sprintf(rpt_fn, "%s%s.RPT", dst_base, label); #else sprintf(rpt_fn, "%s/%s.rpt", dst_base ? dst_base : ".", label); #endif rpt_fp = fopen(rpt_fn, total_files == 0 ? "w" : "a"); if (rpt_fp == NULL) fail(errno, "Can't create report file %s", rpt_fn); rec = min = max = file_started = 0; file_errors = tape_errors = 0; file_num = file_len = tape_len = 0; while (1) { len = read_record(src_fd, buf, sizeof(buf)); total_records += 1; if (len == sizeof(buf)) { total_overflow_aborts += 1; fail(0, "Buffer overflow from read_record (%d)", len); } else if (len > 0) // We've successfully read a record { if (! file_started) { dst_fd = start_file(&crc, &file_len, &file_num, dst_base, label, dst_fn); file_started = 1; } rec += 1; file_len += len; crc = crc32_update(crc, buf, len); if (max == 0 || len < min) min = len; if (max == 0 || max < len) max = len; if (dst_base) { uint8_t reclen[4] = { len & 255, len / 0x100, len / 0x10000, len / 0x1000000 }; if (write_tap_format) if (write(dst_fd, reclen, sizeof(reclen)) != sizeof(reclen)) fail(errno, "Can't write to %s", dst_fn); if (write(dst_fd, buf, len) != len) fail(errno, "Can't write to %s", dst_fn); if (write_tap_format) if (write(dst_fd, reclen, sizeof(reclen)) != sizeof(reclen)) fail(errno, "Can't write to %s", dst_fn); } } else if (len == 0 && rec != 0) // We've reached end-of-file { static const char *emsg[] = { "Copied from tape to disk and passed validation", "Copied from tape to disk with parity errors" }; if (verbose) printf("%d: recs=%d, errs=%d, reclen=%d..%d, crc=%08lx, bytes=%d\n", total_files, rec, file_errors, min, max, crc, file_len); fprintf(rpt_fp, "%s\t%d\t%s\t%s_F%05d.PHYS\t%s\t%d\t%d\t%08lx\n", label, total_files, dst_base ? dst_base : "-", label, total_files, emsg[!!file_errors], max, min, crc); if (! file_started && rec != 0) dst_fd = start_file(&crc, &file_len, &file_num, dst_base, label, dst_fn); if (dst_base) { uint8_t reclen[4] = { 0 }; if (write_tap_format) if (write(dst_fd, reclen, sizeof(reclen)) != sizeof(reclen)) fail(errno, "Can't write to %s", dst_fn); close(dst_fd); file_started = 0; } tape_len += file_len; accumulate(&file_freqs, &ff_size, file_errors); file_errors = file_len = rec = min = max = 0; } else if (len == 0 && rec == 0) // We've reached end-of-tape { if (!summary) { #if __VMS printf("Tape drive %s, tape %s, report file %s, output %s\n", src_fn, label, rpt_fn, dst_base); #else printf("Tape '%s', report file '%s'\n", src_fn, rpt_fn); #endif printf(" Read %d files, %d bytes with %d error%s\n\n", file_num, tape_len, tape_errors, tape_errors == 1 ? "" : "s"); } fclose(rpt_fp); close(src_fd); total_bytes += tape_len; accumulate(&tape_freqs, &tf_size, tape_errors); return; } else // if (len < 0) // An I/O error has occurred { if (! file_started && rec != 0) { dst_fd = start_file(&crc, &file_len, &file_num, dst_base, label, dst_fn); file_started = 1; } rec += 1; file_errors += 1; if (file_errors == 1) total_files_with_parity += 1; tape_errors += 1; if (tape_errors == 1) total_tapes_with_parity += 1; total_parity += 1; if (!summary) warn(errno, "Parity error on %s, tape %s, file %d, record %d", src_fn, label, file_num, rec); if (tape_errors >= max_parity_errors) { total_excess_error_aborts += 1; if (summary && fail_longjmp) longjmp(fail_jmp_buf, fail_longjmp); fail(0, "Too many errors encountered; giving up."); } } } } #if __VMS static char *option_chars = "hvdest"; static void help(void) { printf( "%s: usage: %s [-%s]