diff --git a/converters/Makefile b/converters/Makefile index 05db906..376135c 100644 --- a/converters/Makefile +++ b/converters/Makefile @@ -9,6 +9,7 @@ CC=gcc all: cd asc && $(MAKE) CFLAGS="$(CFLAGS)" BIN="$(BIN)" INSTALL="$(INSTALL)" CC="$(CC)" + cd cosy && $(MAKE) CFLAGS="$(CFLAGS)" BIN="$(BIN)" INSTALL="$(INSTALL)" CC="$(CC)" cd dtos8cvt && $(MAKE) CFLAGS="$(CFLAGS)" BIN="$(BIN)" INSTALL="$(INSTALL)" CC="$(CC)" cd hpconvert && $(MAKE) CFLAGS="$(CFLAGS)" BIN="$(BIN)" INSTALL="$(INSTALL)" CC="$(CC)" cd littcvt && $(MAKE) CFLAGS="$(CFLAGS)" BIN="$(BIN)" INSTALL="$(INSTALL)" CC="$(CC)" @@ -52,6 +53,7 @@ clean: install: cd asc && $(MAKE) CFLAGS="$(CFLAGS)" BIN="$(BIN)" INSTALL="$(INSTALL)" CC="$(CC)" install + cd cosy && $(MAKE) CFLAGS="$(CFLAGS)" BIN="$(BIN)" INSTALL="$(INSTALL)" CC="$(CC)" install cd dtos8cvt && $(MAKE) CFLAGS="$(CFLAGS)" BIN="$(BIN)" INSTALL="$(INSTALL)" CC="$(CC)" install cd hpconvert && $(MAKE) CFLAGS="$(CFLAGS)" BIN="$(BIN)" INSTALL="$(INSTALL)" CC="$(CC)" install cd littcvt && $(MAKE) CFLAGS="$(CFLAGS)" BIN="$(BIN)" INSTALL="$(INSTALL)" CC="$(CC)" install @@ -73,6 +75,7 @@ install: uninstall: cd asc && $(MAKE) CFLAGS="$(CFLAGS)" BIN="$(BIN)" INSTALL="$(INSTALL)" CC="$(CC)" uninstall + cd cosy && $(MAKE) CFLAGS="$(CFLAGS)" BIN="$(BIN)" INSTALL="$(INSTALL)" CC="$(CC)" uninstall cd dtos8cvt && $(MAKE) CFLAGS="$(CFLAGS)" BIN="$(BIN)" INSTALL="$(INSTALL)" CC="$(CC)" uninstall cd hpconvert && $(MAKE) CFLAGS="$(CFLAGS)" BIN="$(BIN)" INSTALL="$(INSTALL)" CC="$(CC)" uninstall cd littcvt && $(MAKE) CFLAGS="$(CFLAGS)" BIN="$(BIN)" INSTALL="$(INSTALL)" CC="$(CC)" uninstall diff --git a/converters/cosy/Makefile b/converters/cosy/Makefile new file mode 100644 index 0000000..985c57e --- /dev/null +++ b/converters/cosy/Makefile @@ -0,0 +1,21 @@ +# all of these can be over-ridden on the "make" command line if don't suit +# your environment +TOOL=cosy +CFLAGS=-O2 -Wall -Wshadow -Wextra -pedantic -Woverflow -Wstrict-overflow +BIN=/usr/local/bin +INSTALL=install +CC=gcc + +$(TOOL): $(TOOL).c + $(CC) $(CPPFLAGS) $(CFLAGS) $(LDFLAGS) -o $(TOOL) $(TOOL).c $(LDLIBS) + +.PHONY: clean install uninstall + +clean: + rm -f $(TOOL) + +install: $(TOOL) + $(INSTALL) -p -m u=rx,g=rx,o=rx $(TOOL) $(BIN) + +uninstall: + rm -f $(BIN)/$(TOOL) diff --git a/converters/cosy/cosy.c b/converters/cosy/cosy.c new file mode 100644 index 0000000..2208331 --- /dev/null +++ b/converters/cosy/cosy.c @@ -0,0 +1,587 @@ +/* cosy.c: compress/decompress CDC1700 COSY format files + + Copyright (c) 2015-2017, John Forecast + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + JOHN FORECAST BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Except as contained in this notice, the name of John Forecast shall not + be used in advertising or otherwise to promote the sale, use or other dealings + in this Software without prior written authorization from John Forecast. + +*/ + +/* + * The CDC1700 COSY format is well documented with respect to the compression + * algorithm (Run length encoding of 3 - 62 consecutive blanks) but less so + * for the higher level COSY constructs. This program is written to handle + * COSY format files found on bitsavers.org. It operates in one of 2 modes: + * + * 1. Decompress a sequence of one or more COSY compressed decks. Each deck + * will start with a CSY/ card and end with a END/ card. If the input + * consists of a single compressed deck, the CSY/ and END/ cards may be + * omitted. If a deck name (e.g. "xxx") is provided on the CSY/ card, + * the output file will be named "deck_xxx", if an empty deck name is + * provided or the CSY/ card is missing, the output file will be named + * "nnnnn.deck" where nnnnn is the number of the deck within the input + * file. The CSY/ and END/ cards will not be passed through to the + * output file. + * + * 2. Compress a series of input decks, generating a single compressed COSY + * file. A CSY/ card will be prepended to each input file and a END/ + * card will be appended. If the input file name is "deck_yyy" the deck + * name on the CSY/ card will be yyy. Any other input file name will leave + * the deck name empty on the CSY/ card. The output file will be padded + * to a multiple of 192 words (384 bytes). + */ + +#include +#include +#include +#include + +#define PREFIX 0x5F + +/* + * Record lengths. + */ +#define RECLEN 384 +#define CARDLEN 80 + +char card[CARDLEN + 1], pad[RECLEN]; +int idx = 0, outcount = 0; + +int compress = 0; + +#define VALID(c) ((c >= 0x20) && (c <= 0x5E) && (c != 0x26)) + +/* + * Pre-defined strings to start and end a COSY deck. + */ +char *csy = " CSY/"; +char *end = " END/"; + +/* + * Compression table + */ +#define MAXSPACES 62 + +char *compr[MAXSPACES+1] = { + NULL, " ", " ", + "\x5F\x21", "\x5F\x22", "\x5F\x23", "\x5F\x24", "\x5F\x25", + "\x5F\x27", "\x5F\x28", "\x5F\x29", "\x5F\x2A", + "\x5F\x2B", "\x5F\x2C", "\x5F\x2D", "\x5F\x2E", + "\x5F\x2F", "\x5F\x30", "\x5F\x31", "\x5F\x32", + "\x5F\x33", "\x5F\x34", "\x5F\x35", "\x5F\x36", + "\x5F\x37", "\x5F\x38", "\x5F\x39", "\x5F\x3A", + "\x5F\x3B", "\x5F\x3C", "\x5F\x3D", "\x5F\x3E", + "\x5F\x3F", "\x5F\x40", "\x5F\x41", "\x5F\x42", + "\x5F\x43", "\x5F\x44", "\x5F\x45", "\x5F\x46", + "\x5F\x47", "\x5F\x48", "\x5F\x49", "\x5F\x4A", + "\x5F\x4B", "\x5F\x4C", "\x5F\x4D", "\x5F\x4E", + "\x5F\x4F", "\x5F\x50", "\x5F\x51", "\x5F\x52", + "\x5F\x53", "\x5F\x54", "\x5F\x55", "\x5F\x56", + "\x5F\x57", "\x5F\x58", "\x5F\x59", "\x5F\x5A", + "\x5F\x5B", "\x5F\x5C", "\x5F\x5D" +}; + +int doCompress(FILE *, int, char **), doDecompress(FILE *); + +/*++ + * compressCard + * + * Write the current card, in COSY compressed format, to the destination + * COSY file. + * + * Inputs: + * + * dest - Destination COSY file + * + * Outputs: + * + * ... + * + * Returns: + * + * Exit status: + * 0 - Success + * 3 - File write error + * 4 - Invalid input character + * + --*/ +int compressCard( + FILE *dest +) +{ + int i, spcount = 0, len = strlen(card); + + for (i = 0; i < len; i++) { + if (!VALID(card[i])) + return 4; + + if (card[i] != ' ') { + if (spcount != 0) { + size_t l = strlen(compr[spcount]); + + if (fwrite(compr[spcount], sizeof(char), l, dest) != l) + return 3; + outcount += l; + spcount = 0; + } + if (fputc(card[i], dest) == EOF) + return 3; + outcount++; + } else { + if (++spcount == MAXSPACES) { + size_t l = strlen(compr[spcount]); + + if (fwrite(compr[spcount], sizeof(char), l, dest) != l) + return 3; + outcount += l; + spcount = 0; + } + } + } + /* + * Terminate the card image. + */ + fwrite("\x5F\x5E", sizeof(char), 2, dest); + outcount += 2; + return 0; +} + +/*++ + * decompressCard + * + * Read the next card image from a source file while performing COSY + * decompression. The card image will be null terminated. + * + * Inputs: + * + * src - Source COSY file + * + * Outputs: + * + * ... + * + * Returns: + * + * 0 - Card image successfully read + * -1 - EOF read + * -2 - End of deck read + * + --*/ +int decompressCard( + FILE *src +) +{ + int ch, i, spcount; + + idx = 0; + + while (((ch = fgetc(src)) != -1) && (ch != 0)) { + if (ch == PREFIX) { + if ((ch = fgetc(src)) == -1) + break; + + switch (ch) { + case 0x21: case 0x22: case 0x23: case 0x24: case 0x25: + spcount = ch - 0x21 + 3; + goto fill; + + case 0x27: case 0x28: case 0x29: case 0x2A: case 0x2B: + case 0x2C: case 0x2D: case 0x2E: case 0x2F: case 0x30: + case 0x31: case 0x32: case 0x33: case 0x34: case 0x35: + case 0x36: case 0x37: case 0x38: case 0x39: case 0x3A: + case 0x3B: case 0x3C: case 0x3D: case 0x3E: case 0x3F: + case 0x40: case 0x41: case 0x42: case 0x43: case 0x44: + case 0x45: case 0x46: case 0x47: case 0x48: case 0x49: + case 0x4A: case 0x4B: case 0x4C: case 0x4D: case 0x4E: + case 0x4F: case 0x50: case 0x51: case 0x52: case 0x53: + case 0x54: case 0x55: case 0x56: case 0x57: case 0x58: + case 0x59: case 0x5A: case 0x5B: case 0x5C: case 0x5D: + spcount = ch - 0x27 + 8; + fill: + for (i = 0; i< spcount; i++) { + if (idx != CARDLEN) + card[idx++] = ' '; + } + break; + + case 0x5E: + card[idx] = '\0'; + return 0; + + case 0x5F: + card[idx] = '\0'; + return -2; + } + } else { + if (idx != CARDLEN) + card[idx++] = ch; + } + } + card[idx] = '\0'; + return -1; +} + +/*++ + * writeCard + * + * Remove trailing spaces from the card image and write the resulting line + * to the destination file with a terminating newline character. + * + * Inputs: + * + * dest - Destination file + * + * Outputs: + * + * ... + * + * Returns: + * + * 0 - write was successful + * -1 - error on write + * + --*/ +int writeCard( + FILE *dest +) +{ + /* + * Remove trailing spaces. + */ + while (idx && (card[idx - 1] == ' ')) + card[--idx] = '\0'; + + if (fprintf(dest, "%s\n", card) < 0) + return -1; + return 0; +} + +/*++ + * usage + * + * Display a usage message on stderr and exit. + * + * Inputs: + * + * None + * + * Outputs: + * + * None + * + * Returns: + * + * Never returns + * + --*/ +void usage(void) +{ + fprintf(stderr, + "Usage: cosy [-cd] [file ...]\n"); + fprintf(stderr, + " Compress/decompress a COSY file\n"); + fprintf(stderr, "\nSwitches:\n\n"); + fprintf(stderr, " -c Perform COSY compression\n"); + fprintf(stderr, " -d Perform COSY decompression (the default)\n"); + exit(1); +} + +/*++ + * main + * + * Entry point for cosy program. + * + * Inputs: + * + * argc - # of supplied arguments + * argv - Array of argument strings + * + * Outputs: + * + * None + * + * Returns: + * + * Exit status + * + --*/ +int main( + int argc, + char *argv[] +) +{ + int ch; + FILE *cosy; + + while ((ch = getopt(argc, argv, "cd")) != -1) { + switch (ch) { + case 'c': + compress = 1; + break; + + case 'd': + compress = 0; + break; + + default: + usage(); + } + } + argc -= optind; + argv += optind; + + if (argc < (compress ? 2 : 1)) + usage(); + + /* + * Open the COSY file. + */ + if ((cosy = fopen(argv[0], compress ? "w" : "r")) == NULL) { + fprintf(stderr, "Failed to open COSY file - %s\n", argv[0]); + return 2; + } + argc--, argv++; + + if (compress) { + return doCompress(cosy, argc, argv); + } + return doDecompress(cosy); +} + +/*++ + * doCompress + * + * Compress a sequence of 1 or more files into a single COSY file. + * + * Inputs: + * + * dest - Destination cosy file + * argc - # of source files to compress + * argv - Pointer to array of file names + * + * Outputs: + * + * ... + * + * Returns: + * + * Exit status: + * 0 - Success + * 2 - File open error + * 3 - File write error + * 4 - Invalid input character + * + --*/ +int doCompress( + FILE *dest, + int argc, + char *argv[] +) +{ + int status = 0; + unsigned int i; + char *filename; + FILE *src; + + while (argc != 0) { + filename = argv[0]; + + if ((src = fopen(filename, "r")) == NULL) { + status = 2; + break; + } + + /* + * Build a leading CSY/ card. + */ + strcpy(card, csy); + if (strncmp(filename, "deck_", 5) == 0) { + /* + * Possible deck name derived from filename. + */ + if (strlen(filename) <= 11) { + for (i = 5; i < strlen(filename); i++) + if (!VALID(filename[i])) + goto noname; + + strncpy(card, &filename[5], strlen(filename) - 5); + goto named; + } + noname: + fprintf(stderr, "Unable to use filename (%s) for deck name\n", filename); + } + named: + + if ((status = compressCard(dest)) != 0) + break; + + /* + * Copy the source file to the destination while compressing. + */ + while (fgets(card, sizeof(card), src) != NULL) { + int len = strlen(card); + + if (card[len - 1] == '\n') + card[len - 1] = '\0'; + + if ((status = compressCard(dest)) != 0) + goto error; + } + + /* + * Build a trailing END/ card. + */ + strcpy(card, end); + if ((status = compressCard(dest)) != 0) + break; + + fclose(src); + + argc--, argv++; + } + error: + + /* + * Report possible error. + */ + switch (status) { + case 0: + if ((outcount % RECLEN) != 0) + fwrite(pad, sizeof(char), outcount % RECLEN, dest); + break; + + case 2: + fprintf(stderr, "Failed to open file - %s\n", filename); + break; + + case 3: + fprintf(stderr, "Error writing COSY file\n"); + break; + + case 4: + fprintf(stderr, "Invalid character in file - %s\n", filename); + break; + + default: + fprintf(stderr, "Unknown exit status - %u\n", status); + break; + } + fclose(dest); + return status; +} + +/*++ + * doDecompress + * + * Decompress a COSY file. + * + * Inputs: + * + * src - Source COSY file + * + * Outputs: + * + * ... + * + * Returns: + * + * Exit status: + * 0 - Success + * 2 - File open error + * 3 - File write error + * + --*/ +int doDecompress( + FILE *src +) +{ + int seqno = 0, valid; + char filename[32], *eofn; + FILE *dest = NULL; + + while (decompressCard(src) == 0) { + /* + * First card of next deck has been read - is is a CSY/ card? + */ + sprintf(filename, "%05u.deck", seqno); + valid = 1; + if (strncmp("CSY/", &card[7], 4) == 0) { + if (card[0] != ' ') { + if ((eofn = strchr(card, ' ')) != NULL) { + int len = eofn - card; + + if (len <= 6) { + *eofn = '\0'; + sprintf(filename, "deck_%s", card); + } + } + } + valid = 0; + } + + if ((dest = fopen(filename, "w")) == 0) { + fprintf(stderr, "Failed to create file - %s\n", filename); + return 2; + } + + /* + * If the first line was not a CSY/ card, write to the destination file + */ + if (valid) { + if (writeCard(dest) == -1) { + fprintf(stderr, "Error writing to file - %s\n", filename); + fclose(dest); + return 3; + } + } + + /* + * Now process the rest of this file. + */ + while (dest != NULL) { + switch (decompressCard(src)) { + case 0: + if (strncmp("END/", &card[7], 4) == 0) { + fclose(dest); + dest = NULL; + break; + } + if (writeCard(dest) == -1) { + fprintf(stderr, "Error writing to file%s\n", filename); + fclose(dest); + return 3; + } + break; + + case -1: + fclose(dest); + return 0; + + case -2: + fclose(dest); + dest = NULL; + break; + } + } + seqno++; + } + return 0; +} diff --git a/converters/cosy/cosy.txt b/converters/cosy/cosy.txt new file mode 100644 index 0000000..d4027fa --- /dev/null +++ b/converters/cosy/cosy.txt @@ -0,0 +1,24 @@ +cosy manipulates files in the CDC1700 COSY format (note this is different from +the CDC3000 series COSY format). COSY is a format used for containing card +images as ASCII files using run length encoding of 3 or more spaces to reduce +the amount of space required. Multiple card decks may be present in a single +COSY file. File naming in the host environment makes use of the deck name +which is optionally present in the CSY/ cards. For decompression, if a deck +name is present, the host file will be named "deck_" otherwise it +will be named "nnnnn.deck" where nnnnn is the position number of the deck +within the COSY file. On compression, source files named "deck_" +will result in the CSY/ deckname being filled in otherwise an empty name wil +be used. + +To compress a COSY format file: + + cosy -c file ... + +To decompress a COSY format file: + + cosy -d + +When decompressing a COSY format file, CSY/ and END/ card images are removed +from the output. On compression, a CSY/ card image is inserted at the start +of the output and a END/ card is appended to the output. The resulting +compressed file will be padded, with NULLs, to be a multiple of 384 bytes. diff --git a/converters/mksimtape/mksimtape b/converters/mksimtape/mksimtape new file mode 100644 index 0000000..c3bf9e3 Binary files /dev/null and b/converters/mksimtape/mksimtape differ diff --git a/extracters/Makefile b/extracters/Makefile index ec72b86..272287c 100644 --- a/extracters/Makefile +++ b/extracters/Makefile @@ -10,36 +10,48 @@ CC=gcc # Omitted: backup, ods2: need more complicated Makefiles. all: cd ckabstape && $(MAKE) CFLAGS="$(CFLAGS)" BIN="$(BIN)" INSTALL="$(INSTALL)" CC="$(CC)" + cd cpytap && $(MAKE) CFLAGS="$(CFLAGS)" BIN="$(BIN)" INSTALL="$(INSTALL)" CC="$(CC)" + cd dbtap && $(MAKE) CFLAGS="$(CFLAGS)" BIN="$(BIN)" INSTALL="$(INSTALL)" CC="$(CC)" cd mmdir && $(MAKE) CFLAGS="$(CFLAGS)" BIN="$(BIN)" INSTALL="$(INSTALL)" CC="$(CC)" cd mtdump && $(MAKE) CFLAGS="$(CFLAGS)" BIN="$(BIN)" INSTALL="$(INSTALL)" CC="$(CC)" cd rawcopy && $(MAKE) CFLAGS="$(CFLAGS)" BIN="$(BIN)" INSTALL="$(INSTALL)" CC="$(CC)" + cd rawtap && $(MAKE) CFLAGS="$(CFLAGS)" BIN="$(BIN)" INSTALL="$(INSTALL)" CC="$(CC)" cd rstsflx && $(MAKE) CFLAGS="$(CFLAGS)" BIN="$(BIN)" INSTALL="$(INSTALL)" CC="$(CC)" cd sdsdump && $(MAKE) CFLAGS="$(CFLAGS)" BIN="$(BIN)" INSTALL="$(INSTALL)" CC="$(CC)" cd tpdump && $(MAKE) CFLAGS="$(CFLAGS)" BIN="$(BIN)" INSTALL="$(INSTALL)" CC="$(CC)" clean: cd ckabstape && $(MAKE) CFLAGS="$(CFLAGS)" BIN="$(BIN)" INSTALL="$(INSTALL)" CC="$(CC)" clean + cd cpytap && $(MAKE) CFLAGS="$(CFLAGS)" BIN="$(BIN)" INSTALL="$(INSTALL)" CC="$(CC)" clean + cd dbtap && $(MAKE) CFLAGS="$(CFLAGS)" BIN="$(BIN)" INSTALL="$(INSTALL)" CC="$(CC)" clean cd mmdir && $(MAKE) CFLAGS="$(CFLAGS)" BIN="$(BIN)" INSTALL="$(INSTALL)" CC="$(CC)" clean cd mtdump && $(MAKE) CFLAGS="$(CFLAGS)" BIN="$(BIN)" INSTALL="$(INSTALL)" CC="$(CC)" clean cd rawcopy && $(MAKE) CFLAGS="$(CFLAGS)" BIN="$(BIN)" INSTALL="$(INSTALL)" CC="$(CC)" clean + cd rawtap && $(MAKE) CFLAGS="$(CFLAGS)" BIN="$(BIN)" INSTALL="$(INSTALL)" CC="$(CC)" clean cd rstsflx && $(MAKE) CFLAGS="$(CFLAGS)" BIN="$(BIN)" INSTALL="$(INSTALL)" CC="$(CC)" clean cd sdsdump && $(MAKE) CFLAGS="$(CFLAGS)" BIN="$(BIN)" INSTALL="$(INSTALL)" CC="$(CC)" clean cd tpdump && $(MAKE) CFLAGS="$(CFLAGS)" BIN="$(BIN)" INSTALL="$(INSTALL)" CC="$(CC)" clean install: cd ckabstape && $(MAKE) CFLAGS="$(CFLAGS)" BIN="$(BIN)" INSTALL="$(INSTALL)" CC="$(CC)" install + cd cpytap && $(MAKE) CFLAGS="$(CFLAGS)" BIN="$(BIN)" INSTALL="$(INSTALL)" CC="$(CC)" install + cd dbtap && $(MAKE) CFLAGS="$(CFLAGS)" BIN="$(BIN)" INSTALL="$(INSTALL)" CC="$(CC)" install cd mmdir && $(MAKE) CFLAGS="$(CFLAGS)" BIN="$(BIN)" INSTALL="$(INSTALL)" CC="$(CC)" install cd mtdump && $(MAKE) CFLAGS="$(CFLAGS)" BIN="$(BIN)" INSTALL="$(INSTALL)" CC="$(CC)" install cd rawcopy && $(MAKE) CFLAGS="$(CFLAGS)" BIN="$(BIN)" INSTALL="$(INSTALL)" CC="$(CC)" install + cd rawtap && $(MAKE) CFLAGS="$(CFLAGS)" BIN="$(BIN)" INSTALL="$(INSTALL)" CC="$(CC)" install cd rstsflx && $(MAKE) CFLAGS="$(CFLAGS)" BIN="$(BIN)" INSTALL="$(INSTALL)" CC="$(CC)" install cd sdsdump && $(MAKE) CFLAGS="$(CFLAGS)" BIN="$(BIN)" INSTALL="$(INSTALL)" CC="$(CC)" install cd tpdump && $(MAKE) CFLAGS="$(CFLAGS)" BIN="$(BIN)" INSTALL="$(INSTALL)" CC="$(CC)" install uninstall: cd ckabstape && $(MAKE) CFLAGS="$(CFLAGS)" BIN="$(BIN)" INSTALL="$(INSTALL)" CC="$(CC)" uninstall + cd cpytap && $(MAKE) CFLAGS="$(CFLAGS)" BIN="$(BIN)" INSTALL="$(INSTALL)" CC="$(CC)" uninstall + cd dbtap && $(MAKE) CFLAGS="$(CFLAGS)" BIN="$(BIN)" INSTALL="$(INSTALL)" CC="$(CC)" uninstall cd mmdir && $(MAKE) CFLAGS="$(CFLAGS)" BIN="$(BIN)" INSTALL="$(INSTALL)" CC="$(CC)" uninstall cd mtdump && $(MAKE) CFLAGS="$(CFLAGS)" BIN="$(BIN)" INSTALL="$(INSTALL)" CC="$(CC)" uninstall cd rawcopy && $(MAKE) CFLAGS="$(CFLAGS)" BIN="$(BIN)" INSTALL="$(INSTALL)" CC="$(CC)" uninstall + cd rawtap && $(MAKE) CFLAGS="$(CFLAGS)" BIN="$(BIN)" INSTALL="$(INSTALL)" CC="$(CC)" uninstall cd rstsflx && $(MAKE) CFLAGS="$(CFLAGS)" BIN="$(BIN)" INSTALL="$(INSTALL)" CC="$(CC)" uninstall cd sdsdump && $(MAKE) CFLAGS="$(CFLAGS)" BIN="$(BIN)" INSTALL="$(INSTALL)" CC="$(CC)" uninstall cd tpdump && $(MAKE) CFLAGS="$(CFLAGS)" BIN="$(BIN)" INSTALL="$(INSTALL)" CC="$(CC)" uninstall diff --git a/extracters/cpytap/Makefile b/extracters/cpytap/Makefile new file mode 100644 index 0000000..140a094 --- /dev/null +++ b/extracters/cpytap/Makefile @@ -0,0 +1,21 @@ +# all of these can be over-ridden on the "make" command line if don't suit +# your environment +TOOL=cpytap +CFLAGS=-O2 -Wall -Wshadow -Wextra -pedantic -Woverflow -Wstrict-overflow +BIN=/usr/local/bin +INSTALL=install +CC=gcc + +$(TOOL): $(TOOL).c tapeio.c tapeio.h tap.h defs.h + $(CC) $(CPPFLAGS) $(CFLAGS) $(LDFLAGS) -o $(TOOL) $(TOOL).c tapeio.c $(LDLIBS) + +.PHONY: clean install uninstall + +clean: + rm -f $(TOOL) + +install: $(TOOL) + $(INSTALL) -p -m u=rx,g=rx,o=rx $(TOOL) $(BIN) + +uninstall: + rm -f $(BIN)/$(TOOL) diff --git a/extracters/cpytap/cpytap.c b/extracters/cpytap/cpytap.c new file mode 100644 index 0000000..b54da3c --- /dev/null +++ b/extracters/cpytap/cpytap.c @@ -0,0 +1,441 @@ +/* cpytap.c: copy SIMH .tap container with file changes + + Copyright (c) 2015-2017, John Forecast + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + JOHN FORECAST BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Except as contained in this notice, the name of John Forecast shall not + be used in advertising or otherwise to promote the sale, use or other dealings + in this Software without prior written authorization from John Forecast. + +*/ + +#include +#include +#include +#include +#include "tapeio.h" + +#define MINRECLEN 1 +#define MAXRECLEN 65536 +#define DEFRECLEN 10240 + +int reclen = DEFRECLEN; + +/* + * We maintain an array of requests indexed by the file # on the source tape. + */ +#define MAXFILES 100 /* Max files on tape */ +#define APPFILES 20 /* Max append files */ + +struct info { + int reclen; /* Record length to be used */ + char *filename; /* Filename */ +}; + +struct fileop { + int used; /* Use count */ + int skipfile; /* Skip this file */ + struct info repfile; /* Replace with this file */ + struct info insfiles[APPFILES]; /* Insert these files (in order) */ +} fileops[MAXFILES + 1]; + +struct fileop appendops; + +char *srcfile = NULL, *dstfile = NULL; + +FILE *src = NULL, *dst = NULL; + +/*++ + * usage + * + * Display a usage message on stderr and exit. + * + * Inputs: + * + * None + * + * Outputs: + * + * None + * + * Returns: + * + * Never returns + * + --*/ +void usage(void) +{ + fprintf(stderr, + "Usage: cpytap src dst [-r len] [-S n] [-I n,file] [-R n,file] [-A file]\n"); + fprintf(stderr, + " Copy src .tap file to destination maintaining the internal\n" + " record structure. The switches control file level changes to\n" + " destination tape.\n"); + fprintf(stderr, "\nSwitches:\n\n"); + fprintf(stderr, + "-r len - Specify max record size when writing new files.\n" + " There may be multiple \"-r\" switches on the\n" + " command line if the new files need different\n" + " record sizes. (%u <= len <= %u, default %u)\n" + " If len is outside the supported range, it will be\n" + " quietly modified to the minimum or maximum value.\n", + MINRECLEN, MAXRECLEN, DEFRECLEN); + fprintf(stderr, + "-I n,file - Insert specified file before file n of the source tape.\n"); + fprintf(stderr, + "-R n,file - Replace file n of the source tape with the specified file.\n"); + fprintf(stderr, + "-S n - Skip file n from the source tape.\n"); + fprintf(stderr, + "-A file - Append the specified file after all the files on\n" + " the source tape have been copied to the destination.\n"); + fprintf(stderr, + "\nFiles are number 1 - N according to their position on the\n" + "source tape.\n"); + exit(1); +} + +/*++ + * copyFile + * + * Copy the next file from the source tape to the destination tape + * replicating the record structure up to and including the tape mark. + * + * Inputs: + * + * None + * + * Outputs: + * + * ... + * + * Returns: + * + * ST_EOM - End of medium detected + * ST_TM - Tape mark detected + * + --*/ +static unsigned int copyFile(void) +{ + char record[MAXRCLNT]; + uint32 len; + + for (;;) { + switch (len = ReadTapeRecord(src, record, sizeof(record))) { + case ST_EOM: + return ST_EOM; + + case ST_TM: + if (WriteTapeMark(dst, 0) == 0) + return ST_TM; + fprintf(stderr, "Error writing tape mark to destination tape\n"); + exit(6); + + default: + if (WriteTapeRecord(dst, record, len) == 0) + continue; + fprintf(stderr, "Error writing record to destination tape\n"); + exit(6); + } + } +} + +/*++ + * writeFile + * + * Write a file at the current position on the destination tape. + * + * Inputs: + * + * filename - Pointer to name of file to be written + * rlen - Max record size to use + * + * Outputs: + * + * ... + * + * Returns: + * + * None + * + --*/ +static void writeFile( + char *filename, + int rlen +) +{ + FILE *file; + size_t datalen; + char record[MAXRCLNT]; + + if ((file = fopen(filename, "r")) != NULL) { + while ((datalen = fread(record, sizeof(char), rlen, file)) != 0) { + if (ferror(file)) { + fprintf(stderr, "Error reading %s\n", filename); + exit(4); + } + if (WriteTapeRecord(dst, record, rlen) != 0) { + fprintf(stderr, "Error writing record to destination tape\n"); + exit(5); + } + } + if (WriteTapeMark(dst, 0) != 0) { + fprintf(stderr, "Error writing tape mark to destination tape\n"); + exit(5); + } + fclose(file); + return; + } + fprintf(stderr, "Failed to open file - %s\n", filename); + exit(3); +} + +/*++ + * parse + * + * Parse a command line switch argument of the form "n,filename" where n + * is an integerin the range 1 - MAXFILES. + * + * Inputs: + * + * arg - Pointer to "n,filename" string + * file - Pointer to int to receive "n" + * name - Pointer to char* to receive filename + * + * Outputs: + * + * ... + * + * Returns: + * + * 0 - Success + * -1 - Invalid argument format + * + --*/ +int parse( + char *arg, + int *file, + char **name +) +{ + char *endptr; + + *file = strtoul(arg, &endptr, 0); + + if ((*endptr == ',') && (*++endptr == '\0')) + return -1; + if ((*file == 0) || (*file > MAXFILES)) + return -1; + + *name = endptr; + return 0; +} + +/*++ + * main + * + * Entry point for cpytap program. + * + * Inputs: + * + * argc - # of supplied arguments + * argv - Array of argument strings + * + * Outputs: + * + * None + * + * Returns: + * + * Exit status for cpytap + * + --*/ +int main( + int argc, + char *argv[] +) +{ + int ch, i, filenumber = 1, filenum; + char *filename; + + if (argc < 3) + usage(); + + srcfile = argv[1]; + dstfile = argv[2]; + argc -= 2; + argv += 2; + + memset(fileops, 0, sizeof(fileops)); + memset(&appendops, 0, sizeof(appendops)); + + while ((ch = getopt(argc, argv, "r:A:I:R:S:")) != -1) { + switch (ch) { + case 'r': + reclen = strtoul(optarg, NULL, 0); + if (reclen < MINRECLEN) + reclen = MINRECLEN; + if (reclen > MAXRECLEN) + reclen = MAXRECLEN; + done: + break; + + case 'A': + for (i = 0; i < APPFILES; i++) { + if (appendops.insfiles[i].filename == NULL) { + appendops.insfiles[i].reclen = reclen; + appendops.insfiles[i].filename = argv[optind]; + goto done; + } + } + fprintf(stderr, "No space for appending file - %s\n", argv[optind]); + return 5; + + case 'I': + if (parse(optarg, &filenum, &filename) != 0) { + fprintf(stderr, "Invalid argument - -I %s\n", optarg); + return 7; + } + + for (i = 0; i < APPFILES; i++) { + if (fileops[filenum].insfiles[i].filename == NULL) { + fileops[filenum].insfiles[i].reclen = reclen; + fileops[filenum].insfiles[i].filename = filename; + goto done; + } + } + fprintf(stderr, "No space for inserting file - %s\n", filename); + return 5; + + case 'R': + if (parse(optarg, &filenum, &filename) != 0) { + fprintf(stderr, "Invalid argument - -R %s\n", optarg); + return 7; + } + if (fileops[filenum].repfile.filename != NULL) { + fprintf(stderr, "File %u is already being replaced.\n", filenum); + return 8; + } + fileops[filenum].repfile.reclen = reclen; + fileops[filenum].repfile.filename = filename; + break; + + case 'S': + filenum = strtoul(optarg, NULL, 0); + if ((filenum == 0) || (filenum > MAXFILES)) { + fprintf(stderr, "Invalid file number - -D %u\n", filenum); + return 6; + } + fileops[filenum].skipfile = 1; + break; + + case '?': + default: + usage(); + } + } + + /* + * Begin processing the source tape. + */ + switch (OpenTapeForRead(&src, srcfile)) { + case TIO_SUCCESS: + break; + + case TIO_ERROR: + fprintf(stderr, "%s has errors and may not copy correctly\n", srcfile); + break; + + case TIO_CORRUPT: + fprintf(stderr, "%s is not a SIMH .tap container file\n", srcfile); + exit(2); + + case TIO_OPENFAIL: + fprintf(stderr, "%s open failed\n", srcfile); + exit(3); + } + + /* + * Begin processing the destination tape. + */ + switch (OpenTapeForWrite(&dst, dstfile)) { + case TIO_SUCCESS: + break; + + case TIO_IOERROR: + fprintf(stderr, "Error writing to destination tape - %s\n", dstfile); + exit(5); + + case TIO_CREATEFAIL: + fprintf(stderr, "Failed to create destination tape - %s\n", dstfile); + exit(3); + } + + /* + * Process the files on the tape. + */ + for (;;) { + if (filenumber <= MAXFILES) { + struct fileop *op = &fileops[filenumber]; + + /* + * Process possible repalcement. + */ + if (op->repfile.filename != NULL) + writeFile(op->repfile.filename, op->repfile.reclen); + + /* + * Process any insertions before the current file. + */ + for (i = 0; i < APPFILES; i++) + if (op->insfiles[i].filename != NULL) + writeFile(op->insfiles[i].filename, op->insfiles[i].reclen); + + /* + * Now either copy or skip over the next file on the source tape. + */ + if ((op->skipfile != 0) || (op->repfile.filename != 0)) { + if (SkipToNextTapeMark(src) == ST_EOM) + break; + } else { + if (copyFile() == ST_EOM) + break; + } + filenumber++; + } else { + /* + * If there are more than MAXFILES files on the source tape, just copy + * the remaining files. + */ + if (copyFile() == ST_EOM) + break; + } + } + /* + * Handle any appends + */ + for (i = 0; i < APPFILES; i++) + if (appendops.insfiles[i].filename != NULL) + writeFile(appendops.insfiles[i].filename, appendops.insfiles[i].reclen); + + WriteTapeMark(dst, 0); + CloseTape(src); + CloseTape(dst); + return 0; +} diff --git a/extracters/cpytap/cpytap.txt b/extracters/cpytap/cpytap.txt new file mode 100644 index 0000000..9eb6a74 --- /dev/null +++ b/extracters/cpytap/cpytap.txt @@ -0,0 +1,42 @@ +cpytap manipulates a .tap magtape container file used by SIMH. It copies an +existing .tap file to a newly created .tap while modifying its file level +contents. While performing the copy, individual files may be skipped or +replaced and new files may be inserted at specified positions or appended +after the last source file has been copied. For files which are copied between +the source and destination tapes, the internal record structure of each file +is maintained. Replacement or inserted/appended files may only be written +with a specified maximum record size. + +cpytap is invoked by: + + cpytap src dst [-r len] [-I n,file] [-R n,file] [-S n] [-A file] + +Where: + + -r len Max record size to be used when writing new files to the + destination tape. (1 <= len <= 65536, default 10240). + There may be multiple "-r" switches on the command line. + When a "-r" switch is specified, it takes effect on all + following editing commands. + + -I n,file Insert the specified file before file n of the source tape + + -R n,file Replace file n of the source tape with the specified file + + -S n Skip file n of the source tape + + -A file Append the specified fie after all the files on the source + tape have been copied to the destination tape. + +Files on the source tape are numbered 1 - n. + +When multiple -I commands reference the same source tape file or there are +multiple -A commands, the files will be written to the destination tape in +the order specified on the command line. If a -R command and a -I command +reference the same source tape file, the -R file will be written first +followed by the -I file(s). + +The editing control tables are pre-built into the executable. Edit commands +may be issued for files 1 - 100, subsequent files will just be copied from +source to destination. There may be up to 20 -I commands for each source tape +file and up to 20 -A commands. diff --git a/extracters/cpytap/defs.h b/extracters/cpytap/defs.h new file mode 100644 index 0000000..b66b3a4 --- /dev/null +++ b/extracters/cpytap/defs.h @@ -0,0 +1,40 @@ +/* defs.h: Common definitions + + Copyright (c) 2015, John Forecast + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + JOHN FORECAST BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Except as contained in this notice, the name of John Forecast shall not + be used in advertising or otherwise to promote the sale, use or other dealings + in this Software without prior written authorization from John Forecast. + +*/ + +#ifndef __DEFS_H__ +#define __DEFS_H__ +#if defined(VMS) +#include +#else +typedef signed char int8; +typedef signed short int16; +typedef signed int int32; +typedef unsigned char uint8; +typedef unsigned short uint16; +typedef unsigned int uint32; +#endif +#endif diff --git a/extracters/cpytap/tap.h b/extracters/cpytap/tap.h new file mode 100644 index 0000000..897cf48 --- /dev/null +++ b/extracters/cpytap/tap.h @@ -0,0 +1,50 @@ +/* tap.h: simh tape representation definitions + + Copyright (c) 2015, John Forecast + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + JOHN FORECAST BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Except as contained in this notice, the name of John Forecast shall not + be used in advertising or otherwise to promote the sale, use or other dealings + in this Software without prior written authorization from John Forecast. + +*/ + +/* + * Metadata markers + */ +#define ST_EOM 0xFFFFFFFF /* end of medium */ +#define ST_GAP 0xFFFFFFFE /* erase gap */ +#define ST_TM 0x00000000 /* tape mark */ + +/* + * Record length field layout + */ +#define ST_ERROR 0x80000000 /* record contains an error */ +#define ST_MBZ 0x7F000000 /* must be zero */ +#define ST_LENGTH 0x00FFFFFF /* record length */ + +/* + * Data in the .tap containe file is rounded up to an even number of bytes + */ +#define RECLEN(c) (((c) + 1) & ~1) + +/* + * The maximum record length supported by this code is 64K. + */ +#define MAXRCLNT 65536 diff --git a/extracters/cpytap/tapeio.c b/extracters/cpytap/tapeio.c new file mode 100644 index 0000000..16aae99 --- /dev/null +++ b/extracters/cpytap/tapeio.c @@ -0,0 +1,701 @@ +/* tapeio.c: Tape I/O routines + + Copyright (c) 2017, John Forecast + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + JOHN FORECAST BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Except as contained in this notice, the name of John Forecast shall not + be used in advertising or otherwise to promote the sale, use or other dealings + in this Software without prior written authorization from John Forecast. + +*/ + +#include +#include +#include +#include +#include +#include "tapeio.h" + +char buffer[MAXRCLNT]; +int rLength, occupied; + +static int verifyFormat(FILE *); + +/*++ + * OpenTapeForRead + * + * Open an existing SIMH .tap format file for read access. If the file is + * successfully opened, scan the file to determine if it is a valid .tap + * format file and whether there are error records present. + * + * Inputs: + * + * handle - file handle returned here + * name - name of the file + * + * Outputs: + * + * None + * + * Returns: + * + * TIO_SUCCESS - file successfully opened, format is valid + * TIO_ERROR - file successfully opened, error records present + * TIO_CORRUPT - file successfully opened, format is invalid + * TIO_OPENFAIL - file open failed + * + * Note the file remains open if the return status is TIO_SUCCESS or + * TIO_ERROR + * + --*/ +int OpenTapeForRead( + FILE **handle, + char *name +) +{ + FILE *tfile; + + if ((tfile = fopen(name, "r")) != NULL) { + int status; + + status = verifyFormat(tfile); + rewind(tfile); + if ((status != TIO_SUCCESS) && (status != TIO_ERROR)) + CloseTape(tfile); + else *handle = tfile; + return status; + } + return TIO_OPENFAIL; +} + +/*++ + * OpenTapeForWrite + * + * Create a new SIMH .tap format fiole for write access. Two tape marks are + * written to the file and the file handle is rewound to the beginning of the + * file. If the file already exists, and error (TIO_CREATEFAIL) is returned. + * + * Inputs: + * + * handle - file handle returned here + * name - name of the file + * + * Outputs: + * + * None + * + * Returns: + * + * TIO_SUCCESS - file successfully created + * TIO_IOERROR - I/O error writing the initial file contents + * TIO_CREATEFAIL - file create failed + * + --*/ +int OpenTapeForWrite( + FILE **handle, + char *name +) +{ + FILE *tfile; + + /* + * Fail if the file exists + */ + if (access(name, F_OK) == 0) + return TIO_CREATEFAIL; + + if ((tfile = fopen(name, "w+")) != NULL) { + uint32 tm = 0; + int status = TIO_SUCCESS; + + /* + * Write 2 tape marks + */ + if (fwrite(&tm, sizeof(tm), 2, tfile) != 2) + status = TIO_IOERROR; + + if (status == TIO_SUCCESS) { + rewind(tfile); + *handle = tfile; + return TIO_SUCCESS; + } + + /* + * Failed to write the 2 tape marks. Try to delete the file before + * returning an error. + */ + CloseTape(tfile); + unlink(name); + return TIO_IOERROR; + } + return TIO_CREATEFAIL; +} + +/*++ + * OpenTapeForAppend + * + * Open an existing SIMH .tap format file for write access, leaving the + * file handle positioned just before the final tape mark. If the file is + * successfully opened, scan the file to determine if it is a valid .tap + * format file and whether there are error records present. + * + * Inputs: + * + * handle - file handle returned here + * name - name of the file + * + * Outputs: + * + * None + * + * Returns: + * + * TIO_SUCCESS - file successfully opened, format is valid + * TIO_ERROR - file successfully opened, error records present + * TIO_CORRUPT - file successfully opened, format is invalid + * TIO_OPENFAIL - file open failed + * + * Note the file remains open if the return status is TIO_SUCCESS or + * TIO_ERROR + * + --*/ +int OpenTapeForAppend( + FILE **handle, + char *name +) +{ + FILE *tfile; + + if ((tfile =fopen(name, "r+")) != NULL) { + int status; + + status = verifyFormat(tfile); + if ((status != TIO_SUCCESS) && (status != TIO_ERROR)) + CloseTape(tfile); + else *handle = tfile; + return status; + } + return TIO_OPENFAIL; +} + +/*++ + * CloseTape + * + * If the tape is open, close it. + * + * Inputs: + * + * handle - file handle for the tape + * + * Outputs: + * + * None + * + * Returns: + * + * None + * + --*/ +void CloseTape( + FILE *handle +) +{ + if (handle != NULL) + fclose(handle); +} + +/*++ + * verifyFormat + * + * Verify the format of the SIMH .tap file. If the format is valid, leave + * the file handle positioned right before the last tape mark in the file. + * + * Inputs: + * + * handle - file handle for the tape + * + * Outputs: + * + * None + * + * Returns: + * + * TIO_SUCCESS - file format is correct + * TIO_ERROR - file format is correct, error records detected + * TIO_CORRUPT - file format is invalid + * TIO_IOERROR - I/O errror while processing file + * + --*/ +static int verifyFormat( + FILE *handle +) +{ + int errorCount = 0, tmSeen = 0; + uint8 meta[4]; + uint32 header, bc; + off_t position; + struct stat stat; + + /* + * Determine the size of the file. + */ + fstat(fileno(handle), &stat); + + for (;;) { + position = ftello(handle); + + /* + * If we are position at the end of file, there is a tape mark missing. + * Treat it as though there is one present. + */ + if (position == stat.st_size) + return TIO_SUCCESS; + + if (fread(meta, sizeof(meta), 1, handle) != 1) + return TIO_CORRUPT; + + bc = (((unsigned int)meta[3]) << 24) | + (((unsigned int)meta[2]) << 16) | + (((unsigned int)meta[1]) << 8) | + (unsigned int)meta[0]; + + switch (bc) { + case ST_TM: + if (++tmSeen <= 1) + break; + /* Treat second TM in a row as end of medium */ + /* FALLTHROUGH */ + + case ST_EOM: + if (fseek(handle, -sizeof(meta), SEEK_CUR) != 0) + return TIO_IOERROR; + return errorCount ? TIO_ERROR : TIO_SUCCESS; + + case ST_GAP: + break; + + default: + /* + * Record descriptor + */ + tmSeen = 0; + + header = bc; + if ((bc & ST_ERROR) != 0) + errorCount++; + if ((bc & ST_MBZ) != 0) + return TIO_CORRUPT; + + bc = RECLEN(bc & ST_LENGTH); + + /* + * Check if we are seeking outside of the file. If so, this is not + * a .tap container file. + */ + if ((position + bc + (2 * sizeof(meta))) > (unsigned long long)stat.st_size) + return TIO_CORRUPT; + + if (fseek(handle, bc, SEEK_CUR) != 0) + return TIO_CORRUPT; + if (fread(meta, sizeof(meta), 1, handle) != 1) + return TIO_CORRUPT; + + bc = (((unsigned int)meta[3]) << 24) | + (((unsigned int)meta[2]) << 16) | + (((unsigned int)meta[1]) << 8) | + (unsigned int)meta[0]; + if (header != bc) + return TIO_CORRUPT; + } + } +} + +/*++ + * ReadTapeRecord + * + * Read the next record from the tape into the specified buffer. If the + * buffer is smaller than the record, the entire record will be consumed. + * + * Inputs: + * + * handle - file handle for the tape + * buf - pointer to the buffer to receive the data + * len - length of the buffer + * + * Outputs: + * + * None + * + * Returns: + * + * ST_EOM - end of medium detected + * ST_TM - tape mark detected + * Other - record length (including error flag) + * if the buffer is smaller than the record, the + * length returned will be that of the buffer + * + --*/ +uint32 ReadTapeRecord( + FILE *handle, + void *buf, + int len +) +{ + long pos = ftell(handle); + uint8 meta[4]; + uint32 bc, erflag, length; + + /* + * Note: any I/O errors are treated as "end of medium" detection. + */ + if (fread(meta, sizeof(meta), 1, handle) != 1) + return ST_EOM; + + bc = (((unsigned int)meta[3]) << 24) | + (((unsigned int)meta[2]) << 16) | + (((unsigned int)meta[1]) << 8) | + (unsigned int)meta[0]; + + switch (bc) { + case ST_EOM: + case ST_TM: + return bc; + + default: + erflag = bc & ST_ERROR; + bc &= ST_LENGTH; + + length = (uint32)len; + if (bc < length) + length = bc; + + if (fread(buf, sizeof(uint8), length, handle) != length) + return ST_EOM; + + /* + * Now position the file after this record. + */ + pos += RECLEN(bc) + (2 * sizeof(meta)); + if (fseek(handle, pos, SEEK_SET) != 0) + return ST_EOM; + + return erflag | length; + } + return ST_EOM; +} + +/*++ + * ReadTapeRecordLength + * + * Get the length of the next record on the tape without actually reading + * the data. + * + * Inputs: + * + * handle - file handle for the tape + * + * Outputs: + * + * None + * + * Returns: + * + * ST_EOM - end of medium detected + * ST_TM - tape mark detected + * Other - record length (including error flag) + * if the buffer is smaller than the record, the + * length returned will be that of the buffer + * + --*/ +uint32 ReadTapeRecordLength( + FILE *handle +) + { + long pos = ftell(handle); + uint8 meta[4]; + uint32 bc, erflag; + + /* + * Note: any I/O errors are treated as "end of medium" detection. + */ + if (fread(meta, sizeof(meta), 1, handle) != 1) + return ST_EOM; + + bc = (((unsigned int)meta[3]) << 24) | + (((unsigned int)meta[2]) << 16) | + (((unsigned int)meta[1]) << 8) | + (unsigned int)meta[0]; + + switch (bc) { + case ST_EOM: + case ST_TM: + return bc; + + default: + erflag = bc & ST_ERROR; + bc &= ST_LENGTH; + + /* + * Now position the file after this record. + */ + pos += RECLEN(bc) + (2 * sizeof(meta)); + if (fseek(handle, pos, SEEK_SET) != 0) + return ST_EOM; + + return erflag | bc; + } + return ST_EOM; + } + +/*++ + * WriteTapeRecord + * + * Write a record to the tape at it's current position. + * + * Inputs: + * + * handle - file handle for the tape + * buf - pointer to the buffer to be written + * len - length of the buffer + * + * Outputs: + * + * None + * + * Returns: + * + * 0 if record was written successfully, -1 if write failed + * + --*/ +int WriteTapeRecord( + FILE *handle, + void *buf, + int len +) +{ + uint8 meta[4]; + int datalen; + + meta[0] = len & 0xFF; + meta[1] = (len >> 8) & 0xFF; + meta[2] = (len >> 16) & 0xFF; + meta[3] = (len >> 24) & 0xFF; + + datalen = ((len + 1) & ST_LENGTH) & ~1; + + if (fwrite(meta, sizeof(meta), 1, handle) != 1) + return -1; + + if (fwrite(buf, datalen, 1, handle) != 1) + return -1; + + if (fwrite(meta, sizeof(meta), 1, handle) != 1) + return -1; + + return 0; +} + +/*++ + * SkipToNextTapeMark + * + * Skip forward to the next tape mark and position the file just past the + * tape mark. + * + * Inputs: + * + * handle - file handle for the tape + * + * Outputs: + * + * None + * + * Returns: + * + * ST_EOM - end of medium detected + * ST_TM - tape mark detected + * + --*/ +unsigned int SkipToNextTapeMark( + FILE *handle +) +{ + long pos = ftell(handle); + uint8 meta[4]; + uint32 bc; + + for (;;) { + /* + * Note: any I/O errors are treated as "end of medium" detection. + */ + if (fread(meta, sizeof(meta), 1, handle) != 1) + return ST_EOM; + + bc = (((unsigned int)meta[3]) << 24) | + (((unsigned int)meta[2]) << 16) | + (((unsigned int)meta[1]) << 8) | + (unsigned int)meta[0]; + + switch (bc) { + case ST_EOM: + case ST_TM: + return bc; + + default: + bc &= ST_LENGTH; + + /* + * Now position the file after this record. + */ + pos += RECLEN(bc) + (2 * sizeof(meta)); + if (fseek(handle, pos, SEEK_SET) != 0) + return ST_EOM; + break; + } + } +} + +/*++ + * WriteTapeMark + * + * Write a tape mark to the tape at it's current position and, optionally, + * backup to before the tape mark. + * + * Inputs: + * + * handle - file handle for the tape + * backup - if 1, reposition the tape to before the tape mark + * + * Outputs: + * + * None + * + * Returns: + * + * 0 if tape mark was written successfully, -1 if write failed + * + --*/ +int WriteTapeMark( + FILE *handle, + int backup +) +{ + uint32 tm = 0; + + if (fwrite(&tm, sizeof(tm), 1, handle) != 1) + return -1; + + if (backup) + if (fseek(handle, -sizeof(tm), SEEK_CUR) != 0) + return -1; + + return 0; +} + +/*++ + * initTapeBuffering + * + * Initialize variables for writes to tape for ASCII mode transfers + * (translates LF -> CRLF). + * + * Inputs: + * + * reclen - size of the tape record buffer to use + * + * Outputs: + * + * None + * + * Returns: + * + * None + * + --*/ +void initTapeBuffering( + int reclen +) +{ + rLength = reclen; + occupied = 0; +} + +/*++ + * flushTapeBuffering + * + * Flush any pending data out to the current tape. + * + * Inputs: + * + * handle - file handle for the tape + * + * Outputs: + * + * None + * + * Returns: + * + * 0 if data was successfully flushed, -1 if write failed + * + --*/ +int flushTapeBuffering( + FILE *handle +) +{ + uint32 count = occupied; + + occupied = 0; + + if (count != 0) + return WriteTapeRecord(handle, buffer, count); + + return 0; +} + +/*++ + * writeTapeBuffering + * + * Write a character to the current tape, buffering the data into records. + * + * Inputs: + * + * handle - file handle for the tape + * ch - the character to be output + * + * Outputs: + * + * None + * + * Returns: + * + * 0 if character was successfully buffered or written to tape, -1 if + * write failed + * + --*/ +int writeTapeBuffering( + FILE *handle, + char ch +) +{ + buffer[occupied++] = ch; + + if (occupied == rLength) { + occupied = 0; + return WriteTapeRecord(handle, buffer, rLength); + } + return 0; +} diff --git a/extracters/cpytap/tapeio.h b/extracters/cpytap/tapeio.h new file mode 100644 index 0000000..806a68a --- /dev/null +++ b/extracters/cpytap/tapeio.h @@ -0,0 +1,63 @@ +/* tapeio.h: Tape I/O definitions + + Copyright (c) 2015, John Forecast + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + JOHN FORECAST BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Except as contained in this notice, the name of John Forecast shall not + be used in advertising or otherwise to promote the sale, use or other dealings + in this Software without prior written authorization from John Forecast. + +*/ + +#include "tap.h" +#include "defs.h" + +/* + * Tape open status return codes + */ +#define TIO_SUCCESS 0 /* operation successful */ +#define TIO_ERROR -1 /* error record seen */ +#define TIO_CORRUPT -2 /* tape format is corrupt */ +#define TIO_OPENFAIL -3 /* open operation failed */ +#define TIO_CREATEFAIL -4 /* create operation failed */ +#define TIO_IOERROR -5 /* I/O error */ + +/* + * Tape open/close routines. + */ +extern int OpenTapeForRead(FILE **, char *); +extern int OpenTapeForWrite(FILE **, char *); +extern int OpenTapeForAppend(FILE **, char *); +extern void CloseTape(FILE *); + +/* + * Tape I/O routines. + */ +uint32 ReadTapeRecord(FILE *, void *, int); +uint32 ReadTapeRecordLength(FILE *); +int WriteTapeRecord(FILE *, void *, int); +unsigned int SkipToNextTapeMark(FILE *); +int WriteTapeMark(FILE *, int); + +/* + * Buffered I/O routines + */ +void initTapeBuffering(int); +int flushTapeBuffering(FILE *); +int writeTapeBuffering(FILE *, char); diff --git a/extracters/dbtap/Makefile b/extracters/dbtap/Makefile new file mode 100644 index 0000000..bbd7339 --- /dev/null +++ b/extracters/dbtap/Makefile @@ -0,0 +1,21 @@ +# all of these can be over-ridden on the "make" command line if don't suit +# your environment +TOOL=dbtap +CFLAGS=-O2 -Wall -Wshadow -Wextra -pedantic -Woverflow -Wstrict-overflow +BIN=/usr/local/bin +INSTALL=install +CC=gcc + +$(TOOL): $(TOOL).c tapeio.c dos11.c tapeio.h dos11.h tap.h defs.h + $(CC) $(CPPFLAGS) $(CFLAGS) $(LDFLAGS) -o $(TOOL) $(TOOL).c tapeio.c dos11.c $(LDLIBS) + +.PHONY: clean install uninstall + +clean: + rm -f $(TOOL) + +install: $(TOOL) + $(INSTALL) -p -m u=rx,g=rx,o=rx $(TOOL) $(BIN) + +uninstall: + rm -f $(BIN)/$(TOOL) diff --git a/extracters/dbtap/dbtap.c b/extracters/dbtap/dbtap.c new file mode 100644 index 0000000..e259180 --- /dev/null +++ b/extracters/dbtap/dbtap.c @@ -0,0 +1,318 @@ +/* dbtap.c: process DOS/BATCH-11 tape files within a SIMH .tap container + + Copyright (c) 2015, John Forecast + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + JOHN FORECAST BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Except as contained in this notice, the name of John Forecast shall not + be used in advertising or otherwise to promote the sale, use or other dealings + in this Software without prior written authorization from John Forecast. + +*/ + +#include +#include +#include +#include "dos11.h" +#include "tapeio.h" +char *ascii = ""; +int extra = 0, strict = 0, reclen = 512; +uint8 prog = 1, proj = 1; + +int list = 0, create = 0, append = 0, extract = 0; + +/*++ + * usage + * + * Display a usage message on stderr and exit. + * + * Inputs: + * + * None + * + * Outputs: + * + * None + * + * Returns: + * + * None + * + --*/ +void usage(void) +{ + fprintf(stderr, + "Usage: dbtap -lcae [-r len -A -E -S -P pg,pj] container [...]\n\n"); + fprintf(stderr, + " List contents of container file(s):\n"); + fprintf(stderr, + " dbtap -l container ...\n\n"); + fprintf(stderr, + " Create new container and append file(s):\n"); + fprintf(stderr, + " dbtap -c [-r len -A -S -P pg,pj] container file ...\n\n"); + fprintf(stderr, + " Append file(s) to an existing container:\n"); + fprintf(stderr, + " dbtap -a [-r len -A -S -P pg,pj] container file ...\n\n"); + fprintf(stderr, + " Extract files from container(s):\n"); + fprintf(stderr, + " dbtap -e [-A -E] container ...\n\n"); + fprintf(stderr, "\nSwitches:\n\n"); + fprintf(stderr, + "-r len - Specifiy max tape record size (512 <= len <= 65536)\n"); + fprintf(stderr, + "-A - Specify filename extension for ASCII tranefer mode\n"); + fprintf(stderr, + " e.g. \".MAC,.BAT\" or \".MAC:.BAT\"\n"); + fprintf(stderr, + "-E - Include prog,proj in filename when extracting\n"); + fprintf(stderr, + " e.g. FILE_ggg_jjj.EXT\n"); + fprintf(stderr, + "-S - Use DOS/BATCH-11 6+3 filenames (rather than 9+3)\n"); + fprintf(stderr, + "-P pg,pj - Specify prog,proj number when writing to tape\n"); + fprintf(stderr, + " Numbers are in octal. Default is [1,1].\n"); + exit(1); +} + +/*++ + * main + * + * Entry point for the dbtap program. + * + * Inputs: + * + * argc - # of supplied arguments + * argv - array of argument strings + * + * Outputs: + * + * None + * + * Returns: + * + * Exit status for dbtap + * + --*/ +int main( + int argc, + char *argv[] +) +{ + int ch; + uint8 myprog, myproj; + + while ((ch = getopt(argc, argv, "lcaer:A:EP:S")) != -1) { + switch (ch) { + case 'l': + list = 1; + break; + + case 'c': + create = 1; + break; + + case 'a': + append = 1; + break; + + case 'e': + extract = 1; + break; + + case 'r': + reclen = strtoul(optarg, NULL, 0); + if (reclen < 512) + reclen = 512; + if (reclen > MAXRCLNT) + reclen = MAXRCLNT; + break; + + case 'A': + ascii = optarg; + break; + + case 'E': + extra = 1; + break; + + case 'P': + if (sscanf(optarg, "%hho,%hho", &myprog, &myproj) != 2) + usage(); + + prog = myprog; + proj = myproj; + break; + + case 'S': + strict = 1; + break; + + case '?': + default: + usage(); + } + } + argc -= optind; + argv += optind; + + /* + * Only one of -l, -c, -a, -e can be specified. + */ + if ((list + create + append + extract) != 1) + usage(); + + if (list != 0) { + /* + * List directories on one or more container files. + */ + if (argc == 0) + usage(); + + while (argc >= 1) { + switch (OpenTapeForRead(argv[0])) { + case TIO_SUCCESS: + case TIO_ERROR: + printf("%s:\n\n", argv[0]); + listDirectory(); + printf("\n"); + CloseTape(); + break; + + case TIO_CORRUPT: + fprintf(stderr, "%s is not a SIMH .tap container file\n", argv[0]); + exit(2); + + case TIO_OPENFAIL: + fprintf(stderr, "%s open failed\n", argv[0]); + exit(3); + } + argc--, argv++; + } + } else if (create != 0) { + /* + * Create a new container file and append 0 or more files to the tape. + */ + if (argc <= 1) + usage(); + + switch (OpenTapeForWrite(argv[0])) { + case TIO_SUCCESS: + argc--, argv++; + + while (argc >= 1) { + switch (appendFile(argv[0], ascii, prog, proj, reclen, strict)) { + case -1: + fprintf(stderr, "Failed to append %s to tape\n", argv[0]); + fprintf(stderr, "Container file is probably corrupt\n"); + exit(6); + + case 1: + fprintf(stderr, "Failed to open file %s - skipping\n", argv[0]); + /* FALLTHROUGH */ + + case 0: + break; + } + argc--, argv++; + } + CloseTape(); + break; + + case TIO_IOERROR: + fprintf(stderr, "Error writing to container file - %s\n", argv[0]); + exit(5); + + case TIO_CREATEFAIL: + fprintf(stderr, "Failed to create container file - %s\n", argv[0]); + exit(3); + } + } else if (append != 0) { + /* + * Open an existing container file and append 0 or more files to the tape. + */ + if (argc <= 1) + usage(); + + switch (OpenTapeForAppend(argv[0])) { + case TIO_SUCCESS: + case TIO_ERROR: + argc--, argv++; + + while (argc >= 1) { + switch (appendFile(argv[0], ascii, prog, proj, reclen, strict)) { + case -1: + fprintf(stderr, "Failed to append %s to tape\n", argv[0]); + fprintf(stderr, "Container file is probably corrupt\n"); + exit(6); + + case 1: + fprintf(stderr, "Failed to open file %s - skipping\n", argv[0]); + /* FALLTHROUGH */ + + case 0: + break; + } + argc--, argv++; + } + CloseTape(); + break; + + case TIO_CORRUPT: + fprintf(stderr, "%s is not a SIMH .tap container file\n", argv[0]); + exit(2); + + case TIO_OPENFAIL: + fprintf(stderr, "%s open failed\n", argv[0]); + exit(3); + } + } else if (extract != 0) { + /* + * Extract files from one or more container files. + */ + if (argc == 0) + usage(); + + while (argc >= 1) { + switch (OpenTapeForRead(argv[0])) { + case TIO_SUCCESS: + case TIO_ERROR: + printf("%s:\n\n", argv[0]); + extractFiles(ascii, extra); + printf("\n"); + CloseTape(); + break; + + case TIO_CORRUPT: + fprintf(stderr, "%s is not a SIMH .tap container file\n", argv[0]); + exit(2); + + case TIO_OPENFAIL: + fprintf(stderr, "%s open failed\n", argv[0]); + exit(3); + } + argc--, argv++; + } + } + + return 0; +} diff --git a/extracters/dbtap/dbtap.txt b/extracters/dbtap/dbtap.txt new file mode 100644 index 0000000..7570f03 --- /dev/null +++ b/extracters/dbtap/dbtap.txt @@ -0,0 +1,43 @@ +dbtap manipulates .tap magtape container files used by SIMH and written in +DOS/BATCH-11 file format. dbtap is invoked by: + + dbtap -lcae [-r len -A -E -S -P pg,pj] container [...] + +To list the contents of one or more container files: + + dbtap -l container ... + +To create a new container file and append file(s): + + dbtap -c [-r len -A -S -P pg,pj] container file ... + +To append file(s) to an existing container: + + dbtap -a [-r len -A -S -P pg,pj] container file ... + +To extract file(s) from container(s): + + dbtap -e [-A -E] container ... + +Switches: + + -r len Specify max tape record size when writing. + (512 <= len <= 65536). + + -A By default, files are transferred in Binary mode. This switch + is used to specify file extensions which should be transferred + in Ascii mode (e.g. ".MAC:.BAT" or ".MAC,.BAT"). + + -E Include prog,proj in filename when extracting. + (e.g. FILE_ggg_jjj.EXT"). + + -S DOS/BATCH-11 uses 6+3 (name+extension) for filenames including + on magtape. Over time, the magtape definition changed to + allow 9+3 filenames by adding an extra word to the filename + record. By default, this program uses the 9+3 format but + the -S switch may be used to force 6+3 filenames when writing + to tape. + + -P pg,pj Specify prog,proj number when writing to tape. Numbers are in + octal. Default is [1,1]. + diff --git a/extracters/dbtap/defs.h b/extracters/dbtap/defs.h new file mode 100644 index 0000000..b66b3a4 --- /dev/null +++ b/extracters/dbtap/defs.h @@ -0,0 +1,40 @@ +/* defs.h: Common definitions + + Copyright (c) 2015, John Forecast + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + JOHN FORECAST BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Except as contained in this notice, the name of John Forecast shall not + be used in advertising or otherwise to promote the sale, use or other dealings + in this Software without prior written authorization from John Forecast. + +*/ + +#ifndef __DEFS_H__ +#define __DEFS_H__ +#if defined(VMS) +#include +#else +typedef signed char int8; +typedef signed short int16; +typedef signed int int32; +typedef unsigned char uint8; +typedef unsigned short uint16; +typedef unsigned int uint32; +#endif +#endif diff --git a/extracters/dbtap/dos11.c b/extracters/dbtap/dos11.c new file mode 100644 index 0000000..d02c46f --- /dev/null +++ b/extracters/dbtap/dos11.c @@ -0,0 +1,661 @@ +/* dos11.c: Handle DOS/BATCH-11 tape file structure operations + + Copyright (c) 2015, John Forecast + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + JOHN FORECAST BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Except as contained in this notice, the name of John Forecast shall not + be used in advertising or otherwise to promote the sale, use or other dealings + in this Software without prior written authorization from John Forecast. + +*/ + +#include +#include +#include +#include +#include "dos11.h" +#include "tapeio.h" +/* + * Record buffer. + */ +char record[MAXRCLNT]; + +FILE *file = NULL; + +int CRpending = 0; + +char rad50[] = " ABCDEFGHIJKLMNOPQRSTUVWXYZ$.%0123456789"; + +/* + * Table of days/month for both normal and leap years. + */ +unsigned short mnth[12] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; +unsigned short mnthl[12] = { 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; + +char *month[12] = { + "Jan", "Feb", "Mar", "Apr", "May", "Jun", + "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" +}; + +/*++ + * r50Asc + * + * Convert 1 16-bit rad50 value into 3 ASCII characters. + * + * Inputs: + * + * value - rad50 value to be converted + * outbuf - pointer to buffer to receive the characters + * + * Outputs: + * + * None + * + * Returns: + * + * None + * + --*/ +static void r50Asc( + uint16 value, + char *outbuf +) +{ + outbuf[2] = rad50[value % 050]; + value /= 050; + outbuf[1] = rad50[value % 050]; + outbuf[0] = rad50[value / 050]; +} + +/*++ + * r50AscNoSpace + * + * Convert 1 16-bit value into up to 3 ASCII characters, spaces are dropped + * from the conversion. + * + * Inputs: + * + * value - rad50 value to be converted + * outbuf - pointer to the buffer to receive the characters + * + * Outputs: + * + * None + * + * Returns: + * + * # of none space characters converted + * + --*/ +static int r50AscNoSpace( + uint16 value, + char *outbuf +) +{ + int count = 0; + int value2 = value / 050; + + /* + * The rad50 representation of ' ' is zero. + */ + if ((value2 / 050) != 0) + outbuf[count++] = rad50[value2 / 050]; + if ((value2 % 050) != 0) + outbuf[count++] = rad50[value2 % 050]; + if ((value % 050) != 0) + outbuf[count++] = rad50[value % 050]; + + return count; +} + +/*++ + * AscR50 + * + * Converts 3 characters into a single 16-bit rad50 value. If an input + * character is not in the rad50 character set it is converted to '%'. + * + * Inputs: + * + * inbuf - pointer to the buffer with the 3 characters + * + * Outputs: + * + * None + * + * Returns: + * + * rad50 value for the 3 characters + * + --*/ +static uint16 AscR50( + char *inbuf +) +{ + uint16 value; + char *ptr; + + if ((ptr = strchr(rad50, toupper(*inbuf++))) == NULL) + ptr = strchr(rad50, '%'); + + value = (ptr - rad50) * 03100; + + if ((ptr = strchr(rad50, toupper(*inbuf++))) == NULL) + ptr = strchr(rad50, '%'); + + value += (ptr - rad50) * 050; + + if ((ptr = strchr(rad50, toupper(*inbuf))) == NULL) + ptr = strchr(rad50, '%'); + + value += ptr - rad50; + + return value; +} + +/*++ + * dos11Date + * + * Convert a DOS/BATCH-11 date value into an ASCII string. + * + * Inputs: + * + * value - DOS/BATCH-11 date value + * buf - buffer to receive the string (requires 12 bytes) + * + * Outputs: + * + * None + * + * Returns: + * + * None + * + --*/ +static void dos11Date( + uint16 value, + char *buf +) +{ + unsigned short year, doyr, leapyr; + unsigned short *table; + + /* + * The DOS/BATCH-11 date format covers a range of 1970 - 2035. + */ + year = 1970 + value / 1000; + doyr = value % 1000; + leapyr = ((year % 4) == 0) && (year != 2000); + + table = leapyr ? mnthl : mnth; + + /* + * Check for valid day of year. + */ + if (doyr < (leapyr ? 366 : 365)) { + int i = 0; + + while (doyr > table[i]) + doyr -= table[i++]; + + sprintf(buf, "%2d-%s-%4d", doyr, month[i], year); + } else strcpy(buf, "xx-yyy-xxxx"); +} + +/*++ + * initAscii + * + * Initialize variables for ASCII mode transfers (translates CRLF -> LF). + * + * Inputs: + * + * None + * + * Outputs: + * + * None + * + * Returns: + * + * None + * + --*/ +static void initAscii(void) +{ + CRpending = 0; +} + +/*++ + * xferAscii + * + * Write data to the current output file while performing CRLF translation. + * + * Inputs: + * + * ch - the next character to be output to the file + * + * Outputs: + * + * None + * + * Returns: + * + * # of errors returned by fwrite + * + --*/ +static char cr = '\r'; + +static int xferAscii( + char ch +) +{ + int count = 0; + + if (ch == '\r') { + if (CRpending != 0) + if (fwrite(&cr, sizeof(char), 1, file) != 1) + count++; + CRpending = 1; + return count; + } + + if (CRpending != 0) { + CRpending = 0; + if (ch != '\n') + if (fwrite(&cr, sizeof(char), 1, file) != 1) + count++; + } + if (fwrite(&ch, sizeof(ch), 1, file) != 1) + count++; + + return count; +} + +/*++ + * flushAscii + * + * Flush any pending CR to the output file + * + * Inputs: + * + * None + * + * Outputs: + * + * None + * + * Returns: + * + * # of errors returned by fwrite (0 or 1) + * + --*/ +static int flushAscii(void) +{ + if (CRpending != 0) + if (fwrite(&cr, sizeof(char), 1, file) != 1) + return 1; + + return 0; +} + +/*++ + * appendFile + * + * Append a file to the current open tape. + * + * Inputs: + * + * name - pointer to the filename string to append + * ascii - pointer to string containing extensions whose files + * are to be appended in ASCII mode. + * e.g. ".MAC:.BAT" or ".MAC.BAT" + * prog - programmer number for the directory entry + * proj - project number for the directory entry + * reclen - record length to use on the 'tape' + * strict - if 1, use strict DOS/BATCH-11 format file headers + * i.e. dos11hdr1 + * + * Outputs: + * + * None + * + * Returns: + * + * 0 if file successfully appended, + * 1 if failed to open the input file + * -1 if some data may have been written to the tape which is now corrupt + * + --*/ +int appendFile( + char *name, + char *ascii, + uint8 prog, + uint8 proj, + int reclen, + int strict +) +{ + size_t hdrSz = strict ? sizeof(struct dos11hdr1) : sizeof(struct dos11hdr2); + struct dos11hdr2 hdr; + char *fname, *ext, *useAscii; + time_t now; + struct tm *tm; + + memset(&hdr, 0, sizeof(hdr)); + + if ((file = fopen(name, "r")) != NULL) { + char filename[16], extension[8], exten[8]; + int i, len; + size_t datalen; + + /* + * We now need to convert the supplied filename into something that + * fits into the DOS/BATCH-11 file header structure(s). + */ + if ((fname = strrchr(name, '/')) == NULL) + fname = name; + + ext = strrchr(fname, '.'); + + /* + * Fill int the first 'n' bytes of the filename and extension with the + * remaining bytes set to ' '. + */ + memset(&filename, ' ', sizeof(filename)); + memset(&extension, ' ', sizeof(extension)); + memset(&exten, 0, sizeof(exten)); + + if (ext != NULL) + len = ext - fname; + else len = strlen(fname); + + if (len > (strict ? 6 : 9)) + len = strict ? 6 : 9; + + for (i = 0; i < len; i++) + filename[i] = toupper(fname[i]); + + if (ext != NULL) { + len = strlen(&ext[1]); + if (len > 3) + len = 3; + + for (i = 0; i < len + 1; i++) { + extension[i] = toupper(ext[i]); + exten[i] = toupper(ext[i]); + } + } + + /* + * Construct the directory entry + */ + hdr.fname[0] = AscR50(&filename[0]); + hdr.fname[1] = AscR50(&filename[3]); + if (!strict) + hdr.fname3 = AscR50(&filename[6]); + if (ext != NULL) + hdr.ext = AscR50(&extension[1]); + + hdr.proj = proj; + hdr.prog = prog; + hdr.prot = 0233; + + now = time(NULL); + tm = localtime(&now); + + hdr.date = ((tm->tm_year - 70) * 1000) + tm->tm_yday + 1; + + useAscii = strstr(ascii, exten); + + if (WriteTapeRecord(&hdr, hdrSz) == 0) { + initTapeBuffering(reclen); + while ((datalen = fread(record, sizeof(char), reclen, file)) != 0) { + if (ferror(file)) + goto failed; + + if (useAscii != NULL) { + size_t j; + + for (j = 0; j < datalen; i++) { + if (record[j] == '\n') + if (writeTapeBuffering('\r') != 0) + goto failed; + if (writeTapeBuffering(record[j]) != 0) + goto failed; + } + } else { + if (WriteTapeRecord(record, datalen) != 0) + goto failed; + } + } + if (flushTapeBuffering() != 0) + goto failed; + + if ((WriteTapeMark(0) == 0) && (WriteTapeMark(1) == 0)) { + fclose(file); + return 0; + } + } + failed: + fclose(file); + return -1; + } + return 1; +} + +/*++ + * extractFiles + * + * Extract files from the current open tape to the current working + * directory. + * + * Inputs: + * + * ascii - pointer to string containing extensions whose files + * are to be extracted in ASCII mode. + * e.g. ".MAC:.BAT" or ".MAC.BAT" + * ppn - if non-zero, append proj, prog numbers to the name: + * filename_prog_proj.ext + * + * Outputs: + * + * None + * + * Returns: + * + * None + * + --*/ +void extractFiles( + char *ascii, + int ppn +) +{ + unsigned int status; + + do { + switch (status = ReadTapeRecord(record, sizeof(record))) { + case ST_EOM: + break; + + case ST_TM: + /* Second tape mark in a row - treat as end of medium */ + status = ST_EOM; + break; + + default: + if ((status & ST_ERROR) == 0) { + status &= ST_LENGTH; + + if ((status == sizeof(struct dos11hdr1)) || + (status == sizeof(struct dos11hdr2))) { + char filename[32]; + struct dos11hdr2 *hdr = (struct dos11hdr2 *)record; + int offset = 0, extension; + char *useAscii; + uint32 errorCount = 0, length, i; + + /* + * Construct the output filename + */ + offset += r50AscNoSpace(hdr->fname[0], &filename[offset]); + offset += r50AscNoSpace(hdr->fname[1], &filename[offset]); + if (status == sizeof(struct dos11hdr2)) + offset += r50AscNoSpace(hdr->fname3, &filename[offset]); + if (ppn) { + sprintf(&filename[offset], "_%o_%o", hdr->prog, hdr->proj); + offset = strlen(filename); + } + extension = offset; + filename[offset++] = '.'; + offset += r50AscNoSpace(hdr->ext, &filename[offset]); + filename[offset++] = '\0'; + + useAscii = strstr(ascii, &filename[extension]); + + printf(" Extracting: %s ", filename); + + initAscii(); + + if ((file = fopen(filename, "w")) != NULL) { + do { + switch (status = ReadTapeRecord(record, sizeof(record))) { + case ST_EOM: + case ST_TM: + break; + + default: + if ((status & ST_ERROR) != 0) + errorCount++; + length = status & ST_LENGTH; + + if (useAscii != NULL) { + /* + * Process each character separately in order to map + * CRLF -> LF. + */ + for (i = 0; i < length; i++) + xferAscii(record[i]); + } else { + if (fwrite(record, sizeof(char), length, file) != length) + errorCount++; + } + } + } while ((status != ST_EOM) && (status != ST_TM)); + flushAscii(); + printf("%s \n", errorCount ? "Errors detected" : "Done"); + fclose(file); + break; + } else printf("*** File open failure\n"); + } else fprintf(stderr, " *** Unexpected record size\n"); + } else fprintf(stderr, " *** Directory entry contains error\n"); + + /* + * Scan forward to the end of this file + */ + do { + status = ReadTapeRecordLength(); + } while ((status != ST_EOM) && (status != ST_TM)); + } + } while (status != ST_EOM); +} + +/*++ + * listDirectory + * + * List the directory from the current open tape to stdout. + * + * Inputs: + * + * None + * + * Outputs: + * + * None + * + * Returns: + * + * None + * + --*/ +void listDirectory(void) +{ + unsigned int status; + + do { + switch (status = ReadTapeRecord(record, sizeof(record))) { + case ST_EOM: + break; + + case ST_TM: + /* Second tape mark in a row - treat as end of medium */ + status = ST_EOM; + break; + + default: + if ((status & ST_ERROR) == 0) { + status &= ST_LENGTH; + + if ((status == sizeof(struct dos11hdr1)) || + (status == sizeof(struct dos11hdr2))) { + char filename[14], sdate[12]; + struct dos11hdr2 *hdr = (struct dos11hdr2 *)record; + uint32 errorCount = 0, length = 0; + + memset(filename, ' ', sizeof(filename)); + filename[13] = '\0'; + + r50Asc(hdr->fname[0], &filename[0]); + r50Asc(hdr->fname[1], &filename[3]); + if (status == sizeof(struct dos11hdr2)) + r50Asc(hdr->fname3, &filename[6]); + filename[9] = '.'; + r50Asc(hdr->ext, &filename[10]); + + dos11Date(hdr->date, sdate); + + printf("%s %s <%03o> [%3o,%3o] ", + filename, sdate, hdr->prot, hdr->prog, hdr->proj); + + do { + switch (status = ReadTapeRecordLength()) { + case ST_EOM: + case ST_TM: + printf("%u bytes%s\n", length, errorCount ? "(E)" : ""); + break; + + default: + if ((status & ST_ERROR) != 0) + errorCount++; + length += status & ST_LENGTH; + break; + } + } while ((status != ST_EOM) && (status != ST_TM)); + break; + } else fprintf(stderr, " *** Unexpected record size\n"); + } else fprintf(stderr, " *** Directory entry contains error\n"); + + /* + * Scan forward to the end of this file + */ + do { + status = ReadTapeRecordLength(); + } while ((status != ST_EOM) && (status != ST_TM)); + } + } while (status != ST_EOM); +} diff --git a/extracters/dbtap/dos11.h b/extracters/dbtap/dos11.h new file mode 100644 index 0000000..64d8a79 --- /dev/null +++ b/extracters/dbtap/dos11.h @@ -0,0 +1,60 @@ +/* dos11.h: DOS/BATCH-11 tape file format definitions + + Copyright (c) 2015, John Forecast + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + JOHN FORECAST BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Except as contained in this notice, the name of John Forecast shall not + be used in advertising or otherwise to promote the sale, use or other dealings + in this Software without prior written authorization from John Forecast. + +*/ + +#include "defs.h" + +/* + * Actual file header as used in DOS/BATCH-11. + */ +struct dos11hdr1 { + uint16 fname[2]; /* first 6 chars of filename (RAD50) */ + uint16 ext; /* 3 letter file extension (RAD50) */ + uint8 proj; /* project # (octal) */ + uint8 prog; /* programmer # (octal) */ + uint16 prot; /* protection code (octal) */ + uint16 date; /* (year-1970) * 1000 + day of year */ +}; + +/* + * File header as seen on many archive tapes. + */ +struct dos11hdr2 { + uint16 fname[2]; /* first 6 chars of filename (RAD50) */ + uint16 ext; /* 3 letter file extension (RAD50) */ + uint8 proj; /* project # (octal) */ + uint8 prog; /* programmer # (octal) */ + uint16 prot; /* protection code (octal) */ + uint16 date; /* (year-1970) * 1000 + day of year */ + uint16 fname3; /* optional, letters 7 - 9 of name */ +}; + +/* + * DOS/BATCH-11 processing functions + */ +int appendFile(char *, char *, uint8, uint8, int, int); +void extractFiles(char *, int); +void listDirectory(void); diff --git a/extracters/dbtap/tap.h b/extracters/dbtap/tap.h new file mode 100644 index 0000000..897cf48 --- /dev/null +++ b/extracters/dbtap/tap.h @@ -0,0 +1,50 @@ +/* tap.h: simh tape representation definitions + + Copyright (c) 2015, John Forecast + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + JOHN FORECAST BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Except as contained in this notice, the name of John Forecast shall not + be used in advertising or otherwise to promote the sale, use or other dealings + in this Software without prior written authorization from John Forecast. + +*/ + +/* + * Metadata markers + */ +#define ST_EOM 0xFFFFFFFF /* end of medium */ +#define ST_GAP 0xFFFFFFFE /* erase gap */ +#define ST_TM 0x00000000 /* tape mark */ + +/* + * Record length field layout + */ +#define ST_ERROR 0x80000000 /* record contains an error */ +#define ST_MBZ 0x7F000000 /* must be zero */ +#define ST_LENGTH 0x00FFFFFF /* record length */ + +/* + * Data in the .tap containe file is rounded up to an even number of bytes + */ +#define RECLEN(c) (((c) + 1) & ~1) + +/* + * The maximum record length supported by this code is 64K. + */ +#define MAXRCLNT 65536 diff --git a/extracters/dbtap/tapeio.c b/extracters/dbtap/tapeio.c new file mode 100644 index 0000000..ddb4159 --- /dev/null +++ b/extracters/dbtap/tapeio.c @@ -0,0 +1,614 @@ +/* tapeio.c: Tape I/O routines + + Copyright (c) 2017, John Forecast + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + JOHN FORECAST BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Except as contained in this notice, the name of John Forecast shall not + be used in advertising or otherwise to promote the sale, use or other dealings + in this Software without prior written authorization from John Forecast. + +*/ + +#include +#include +#include +#include +#include +#include "tapeio.h" + +FILE *tfile = NULL; + +char buffer[MAXRCLNT]; +int rLength, occupied; + +static int verifyFormat(void); + +/*++ + * OpenTapeForRead + * + * Open an existing SIMH .tap format file for read access. If the file is + * successfully opened, scan the file to determine if it is a valid .tap + * format file and whether there are error records present. + * + * Inputs: + * + * name - name of the file + * + * Outputs: + * + * None + * + * Returns: + * + * TIO_SUCCESS - file successfully opened, format is valid + * TIO_ERROR - file successfully opened, error records present + * TIO_CORRUPT - file successfully opened, format is invalid + * TIO_OPENFAIL - file open failed + * + * Note the file remains open if the return status is TIO_SUCCESS or + * TIO_ERROR + * + --*/ +int OpenTapeForRead( + char *name +) +{ + if ((tfile = fopen(name, "r")) != NULL) { + int status; + + status = verifyFormat(); + rewind(tfile); + if ((status != TIO_SUCCESS) && (status != TIO_ERROR)) + CloseTape(); + return status; + } + return TIO_OPENFAIL; +} + +/*++ + * OpenTapeForWrite + * + * Create a new SIMH .tap format fiole for write access. Two tape marks are + * written to the file and the file handle is rewound to the beginning of the + * file. If the file already exists, and error (TIO_CREATEFAIL) is returned. + * + * Inputs: + * + * name - name of the file + * + * Outputs: + * + * None + * + * Returns: + * + * TIO_SUCCESS - file successfully created + * TIO_IOERROR - I/O error writing the initial file contents + * TIO_CREATEFAIL - file create failed + * + --*/ +int OpenTapeForWrite( + char *name +) +{ + /* + * Fail if the file exists + */ + if (access(name, F_OK) == 0) + return TIO_CREATEFAIL; + + if ((tfile = fopen(name, "w+")) != NULL) { + uint32 tm = 0; + int status = TIO_SUCCESS; + + /* + * Write 2 tape marks + */ + if (fwrite(&tm, sizeof(tm), 2, tfile) != 2) + status = TIO_IOERROR; + + if (status == TIO_SUCCESS) { + rewind(tfile); + return TIO_SUCCESS; + } + + /* + * Failed to write the 2 tape marks. Try to delete the file before + * returning an error. + */ + CloseTape(); + unlink(name); + return TIO_IOERROR; + } + return TIO_CREATEFAIL; +} + +/*++ + * OpenTapeForAppend + * + * Open an existing SIMH .tap format file for write access, leaving the + * file handle positioned just before the final tape mark. If the file is + * successfully opened, scan the file to determine if it is a valid .tap + * format file and whether there are error records present. + * + * Inputs: + * + * name - name of the file + * + * Outputs: + * + * None + * + * Returns: + * + * TIO_SUCCESS - file successfully opened, format is valid + * TIO_ERROR - file successfully opened, error records present + * TIO_CORRUPT - file successfully opened, format is invalid + * TIO_OPENFAIL - file open failed + * + * Note the file remains open if the return status is TIO_SUCCESS or + * TIO_ERROR + * + --*/ +int OpenTapeForAppend( + char *name +) +{ + if ((tfile =fopen(name, "r+")) != NULL) { + int status; + + status = verifyFormat(); + if ((status != TIO_SUCCESS) && (status != TIO_ERROR)) + CloseTape(); + return status; + } + return TIO_OPENFAIL; +} + +/*++ + * CloseTape + * + * If the tape is open, close it. + * + * Inputs: + * + * None + * + * Outputs: + * + * None + * + * Returns: + * + * None + * + --*/ +void CloseTape(void) +{ + if (tfile != NULL) + fclose(tfile); + tfile = NULL; +} + +/*++ + * verifyFormat + * + * Verify the format of the SIMH .tap file. If the format is valid, leave + * the file handle positioned right before the last tape mark in the file. + * + * Inputs: + * + * None + * + * Outputs: + * + * None + * + * Returns: + * + * TIO_SUCCESS - file format is correct + * TIO_ERROR - file format is correct, error records detected + * TIO_CORRUPT - file format is invalid + * TIO_IOERROR - I/O errror while processing file + * + --*/ +static int verifyFormat(void) +{ + int errorCount = 0, tmSeen = 0; + uint8 meta[4]; + uint32 header, bc; + off_t position; + struct stat stat; + + /* + * Determine the size of the file. + */ + fstat(fileno(tfile), &stat); + + for (;;) { + position = ftello(tfile); + + /* + * If we are position at the end of file, there is a tape mark missing. + * Treat it as though there is one present. + */ + if (position == stat.st_size) + return TIO_SUCCESS; + + if (fread(meta, sizeof(meta), 1, tfile) != 1) + return TIO_CORRUPT; + + bc = (((unsigned int)meta[3]) << 24) | + (((unsigned int)meta[2]) << 16) | + (((unsigned int)meta[1]) << 8) | + (unsigned int)meta[0]; + + switch (bc) { + case ST_TM: + if (++tmSeen <= 1) + break; + /* Treat second TM in a row as end of medium */ + /* FALLTHROUGH */ + + case ST_EOM: + if (fseek(tfile, -sizeof(meta), SEEK_CUR) != 0) + return TIO_IOERROR; + return errorCount ? TIO_ERROR : TIO_SUCCESS; + + case ST_GAP: + break; + + default: + /* + * Record descriptor + */ + tmSeen = 0; + + header = bc; + if ((bc & ST_ERROR) != 0) + errorCount++; + if ((bc & ST_MBZ) != 0) + return TIO_CORRUPT; + + bc = RECLEN(bc & ST_LENGTH); + + /* + * Check if we are seeking outside of the file. If so, this is not + * a .tap container file. + */ + if ((position + bc + (2 * sizeof(meta))) > (unsigned long long)stat.st_size) + return TIO_CORRUPT; + + if (fseek(tfile, bc, SEEK_CUR) != 0) + return TIO_CORRUPT; + if (fread(meta, sizeof(meta), 1, tfile) != 1) + return TIO_CORRUPT; + + bc = (((unsigned int)meta[3]) << 24) | + (((unsigned int)meta[2]) << 16) | + (((unsigned int)meta[1]) << 8) | + (unsigned int)meta[0]; + if (header != bc) + return TIO_CORRUPT; + } + } +} + +/*++ + * ReadTapeRecord + * + * Read the next record from the tape into the specified buffer. If the + * buffer is smaller than the record, the entire record will be consumed. + * + * Inputs: + * + * buf - pointer to the buffer to receive the data + * len - length of the buffer + * + * Outputs: + * + * None + * + * Returns: + * + * ST_EOM - end of medium detected + * ST_TM - tape mark detected + * Other - record length (including error flag) + * if the buffer is smaller than the record, the + * length returned will be that of the buffer + * + --*/ +uint32 ReadTapeRecord( + void *buf, + int len +) +{ + long pos = ftell(tfile); + uint8 meta[4]; + uint32 bc, erflag, length; + + /* + * Note: any I/O errors are treated as "end of medium" detection. + */ + if (fread(meta, sizeof(meta), 1, tfile) != 1) + return ST_EOM; + + bc = (((unsigned int)meta[3]) << 24) | + (((unsigned int)meta[2]) << 16) | + (((unsigned int)meta[1]) << 8) | + (unsigned int)meta[0]; + + switch (bc) { + case ST_EOM: + case ST_TM: + return bc; + + default: + erflag = bc & ST_ERROR; + bc &= ST_LENGTH; + + length = (uint32)len; + if (bc < length) + length = bc; + + if (fread(buf, sizeof(uint8), length, tfile) != length) + return ST_EOM; + + /* + * Now position the file after this record. + */ + pos += RECLEN(bc) + (2 * sizeof(meta)); + if (fseek(tfile, pos, SEEK_SET) != 0) + return ST_EOM; + + return erflag | length; + } + return ST_EOM; +} + +/*++ + * ReadTapeRecordLength + * + * Get the length of the next record on the tape without actually reading + * the data. + * + * Inputs: + * + * None + * + * Outputs: + * + * None + * + * Returns: + * + * ST_EOM - end of medium detected + * ST_TM - tape mark detected + * Other - record length (including error flag) + * if the buffer is smaller than the record, the + * length returned will be that of the buffer + * + --*/ + uint32 ReadTapeRecordLength(void) + { + long pos = ftell(tfile); + uint8 meta[4]; + uint32 bc, erflag; + + /* + * Note: any I/O errors are treated as "end of medium" detection. + */ + if (fread(meta, sizeof(meta), 1, tfile) != 1) + return ST_EOM; + + bc = (((unsigned int)meta[3]) << 24) | + (((unsigned int)meta[2]) << 16) | + (((unsigned int)meta[1]) << 8) | + (unsigned int)meta[0]; + + switch (bc) { + case ST_EOM: + case ST_TM: + return bc; + + default: + erflag = bc & ST_ERROR; + bc &= ST_LENGTH; + + /* + * Now position the file after this record. + */ + pos += RECLEN(bc) + (2 * sizeof(meta)); + if (fseek(tfile, pos, SEEK_SET) != 0) + return ST_EOM; + + return erflag | bc; + } + return ST_EOM; + } + +/*++ + * WriteTapeRecord + * + * Write a record to the tape at it's current position. + * + * Inputs: + * + * buf - pointer to the buffer to be written + * len - length of the buffer + * + * Outputs: + * + * None + * + * Returns: + * + * 0 if record was written successfully, -1 if write failed + * + --*/ +int WriteTapeRecord( + void *buf, + int len +) +{ + uint8 meta[4]; + int datalen; + + meta[0] = len & 0xFF; + meta[1] = (len >> 8) & 0xFF; + meta[2] = (len >> 16) & 0xFF; + meta[3] = (len >> 24) & 0xFF; + + datalen = (len + 1) & ~1; + + if (fwrite(meta, sizeof(meta), 1, tfile) != 1) + return -1; + + if (fwrite(buf, datalen, 1, tfile) != 1) + return -1; + + if (fwrite(meta, sizeof(meta), 1, tfile) != 1) + return -1; + + return 0; +} + +/*++ + * WriteTapeMark + * + * Write a tape mark to the tape at it's current position and, optionally, + * backup to before the tape mark. + * + * Inputs: + * + * backup - if 1, reposition the tape to before the tape mark + * + * Outputs: + * + * None + * + * Returns: + * + * 0 if tape mark was written successfully, -1 if write failed + * + --*/ +int WriteTapeMark( + int backup +) +{ + uint32 tm = 0; + + if (fwrite(&tm, sizeof(tm), 1, tfile) != 1) + return -1; + + if (backup) + if (fseek(tfile, -sizeof(tm), SEEK_CUR) != 0) + return -1; + + return 0; +} + +/*++ + * initTapeBuffering + * + * Initialize variables for writes to tape for ASCII mode transfers + * (translates LF -> CRLF). + * + * Inputs: + * + * reclen - size of the tape record buffer to use + * + * Outputs: + * + * None + * + * Returns: + * + * None + * + --*/ +void initTapeBuffering( + int reclen +) +{ + rLength = reclen; + occupied = 0; +} + +/*++ + * flushTapeBuffering + * + * Flush any pending data out to the current tape. + * + * Inputs: + * + * None + * + * Outputs: + * + * None + * + * Returns: + * + * 0 if data was successfully flushed, -1 if write failed + * + --*/ +int flushTapeBuffering(void) +{ + uint32 count = occupied; + + occupied = 0; + + if (count != 0) + return WriteTapeRecord(buffer, count); + + return 0; +} + +/*++ + * writeTapeBuffering + * + * Write a character to the current tape, buffering the data into records. + * + * Inputs: + * + * ch - the character to be output + * + * Outputs: + * + * None + * + * Returns: + * + * 0 if character was successfully buffered or written to tape, -1 if + * write failed + * + --*/ +int writeTapeBuffering( + char ch +) +{ + buffer[occupied++] = ch; + + if (occupied == rLength) { + occupied = 0; + return WriteTapeRecord(buffer, rLength); + } + return 0; +} diff --git a/extracters/dbtap/tapeio.h b/extracters/dbtap/tapeio.h new file mode 100644 index 0000000..2888781 --- /dev/null +++ b/extracters/dbtap/tapeio.h @@ -0,0 +1,62 @@ +/* tapeio.h: Tape I/O definitions + + Copyright (c) 2015, John Forecast + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + JOHN FORECAST BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Except as contained in this notice, the name of John Forecast shall not + be used in advertising or otherwise to promote the sale, use or other dealings + in this Software without prior written authorization from John Forecast. + +*/ + +#include "tap.h" +#include "defs.h" + +/* + * Tape open status return codes + */ +#define TIO_SUCCESS 0 /* operation successful */ +#define TIO_ERROR -1 /* error record seen */ +#define TIO_CORRUPT -2 /* tape format is corrupt */ +#define TIO_OPENFAIL -3 /* open operation failed */ +#define TIO_CREATEFAIL -4 /* create operation failed */ +#define TIO_IOERROR -5 /* I/O error */ + +/* + * Tape open/close routines. + */ +extern int OpenTapeForRead(char *); +extern int OpenTapeForWrite(char *); +extern int OpenTapeForAppend(char *); +extern void CloseTape(void); + +/* + * Tape I/O routines. + */ +uint32 ReadTapeRecord(void *, int); +uint32 ReadTapeRecordLength(void); +int WriteTapeRecord(void *, int); +int WriteTapeMark(int); + +/* + * Buffered I/O routines + */ +void initTapeBuffering(int); +int flushTapeBuffering(void); +int writeTapeBuffering(char); diff --git a/extracters/rawtap/Makefile b/extracters/rawtap/Makefile new file mode 100644 index 0000000..8cc5ef7 --- /dev/null +++ b/extracters/rawtap/Makefile @@ -0,0 +1,21 @@ +# all of these can be over-ridden on the "make" command line if don't suit +# your environment +TOOL=rawtap +CFLAGS=-O2 -Wall -Wshadow -Wextra -pedantic -Woverflow -Wstrict-overflow +BIN=/usr/local/bin +INSTALL=install +CC=gcc + +$(TOOL): $(TOOL).c tapeio.c tapeio.h tap.h defs.h + $(CC) $(CPPFLAGS) $(CFLAGS) $(LDFLAGS) -o $(TOOL) $(TOOL).c tapeio.c $(LDLIBS) + +.PHONY: clean install uninstall + +clean: + rm -f $(TOOL) + +install: $(TOOL) + $(INSTALL) -p -m u=rx,g=rx,o=rx $(TOOL) $(BIN) + +uninstall: + rm -f $(BIN)/$(TOOL) diff --git a/extracters/rawtap/defs.h b/extracters/rawtap/defs.h new file mode 100644 index 0000000..d002bad --- /dev/null +++ b/extracters/rawtap/defs.h @@ -0,0 +1,40 @@ +/* defs.h: Common definitions + + Copyright (c) 2017, John Forecast + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + JOHN FORECAST BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Except as contained in this notice, the name of John Forecast shall not + be used in advertising or otherwise to promote the sale, use or other dealings + in this Software without prior written authorization from John Forecast. + +*/ + +#ifndef __DEFS_H__ +#define __DEFS_H__ +#if defined(VMS) +#include +#else +typedef signed char int8; +typedef signed short int16; +typedef signed int int32; +typedef unsigned char uint8; +typedef unsigned short uint16; +typedef unsigned int uint32; +#endif +#endif diff --git a/extracters/rawtap/rawtap.c b/extracters/rawtap/rawtap.c new file mode 100644 index 0000000..e475bb0 --- /dev/null +++ b/extracters/rawtap/rawtap.c @@ -0,0 +1,293 @@ +/* rawtap.c: process tape files within a SIMH .tap container + + Copyright (c) 2017, John Forecast + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + JOHN FORECAST BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Except as contained in this notice, the name of John Forecast shall not + be used in advertising or otherwise to promote the sale, use or other dealings + in this Software without prior written authorization from John Forecast. + +*/ + +#include +#include +#include +#include +#include "tapeio.h" + +#define MINRECLEN 1 +#define MAXRECLEN 65536 +#define DEFRECLEN 10240 + +int reclen = DEFRECLEN, seqno = 1; +int create = 0, append = 0, extract = 0; + +char record[MAXRCLNT]; + +/*++ + * usage + * + * Display a usage message on stderr and exit. + * + * Inputs: + * + * None + * + * Outputs: + * + * None + * + * Returns: + * + * None + * + --*/ +void usage(void) +{ + fprintf(stderr, "Usage: rawtap -cae container [-r len] [...]\n\n"); + fprintf(stderr, + " Create new container and append file(s):\n"); + fprintf(stderr, + " rawtap -c container [-r len] file ...\n\n"); + fprintf(stderr, + " Append file(s) to an existing container:\n"); + fprintf(stderr, + " rawtap -a container [-r len] file ...\n\n"); + fprintf(stderr, + " Extract files from container(s):\n"); + fprintf(stderr, + " rawtap -e container ...\n"); + fprintf(stderr, + " - Extracted files will be name 00001.dat, 00002.dat etc\n\n"); + fprintf(stderr, + "\nSwitches:\n\n"); + fprintf(stderr, + "-r len - Specify max tape record size (%u <= len <= %u)\n", + MINRECLEN, MAXRECLEN); + fprintf(stderr, + " The record size applies to all following files and\n"); + fprintf(stderr, + " there may be several \"-r nnn\" present on the command line.\n"); + fprintf(stderr, + " The default record size is 10240 bytes.\n\n"); + exit(1); +} + +/*++ + * main + * + * Entry point for the rawtap program. + * + * Inputs: + * + * argc - # of supplied arguments + * argv - array of argument strings + * + * Outputs: + * + * None + * + * Returns: + * + * Exit status for rawtap + * + --*/ +int main( + int argc, + char *argv[] +) +{ + int readError = 0, writeError = 0; + char *s, filename[16]; + FILE *file; + unsigned int status; + + if ((argc < 2) || (argv[0] == NULL)) + usage(); + + argc--, argv++; + + s = argv[0]; + if ((s != NULL) && (*s++ == '-')) { + ++argv, --argc; + + switch (*s) { + case 'c': + create = 1; + break; + + case 'a': + append = 1; + break; + + case 'e': + extract = 1; + break; + + default: + fprintf(stderr, "Bad option: %c\n", *s); + usage(); + } + } + + if (create != 0) { + /* + * Create a new container file and append 0 or more files to the tape. + */ + if (argc <= 1) + usage(); + + switch (OpenTapeForWrite(argv[0])) { + case TIO_SUCCESS: + appendFiles: + argc--, argv++; + + while (argc >= 1) { + if (strcmp(argv[0], "-r") == 0) { + argc--, argv++; + + if (argc >= 1) { + reclen = strtol(argv[0], NULL, 10); + if (reclen < MINRECLEN) + reclen = MINRECLEN; + if (reclen > MAXRECLEN) + reclen = MAXRECLEN; + argc--, argv++; + } + continue; + } + if ((file = fopen(argv[0], "r")) != NULL) { + size_t datalen; + + while ((datalen = fread(record, sizeof(char), reclen, file)) != 0) { + if (ferror(file)) { + readError++; + break; + } + if (WriteTapeRecord(record, datalen) != 0) { + writeError++; + break; + } + } + fclose(file); + if ((WriteTapeMark(0) != 0) || (WriteTapeMark(1) != 0)) + writeError++; + } else { + writeError++; + break; + } + argc--, argv++; + } + CloseTape(); + break; + + case TIO_IOERROR: + fprintf(stderr, "Error writing to container file %s\n", argv[0]); + exit(5); + + case TIO_CREATEFAIL: + fprintf(stderr, "Failed to create container file - %s\n", argv[0]); + exit(3); + } + } else if (append != 0) { + /* + * Open an existing container file and append 0 or more files to the tape. + */ + if (argc <= 1) + usage(); + + switch (OpenTapeForAppend(argv[0])) { + case TIO_SUCCESS: + case TIO_ERROR: + goto appendFiles; + + case TIO_CORRUPT: + fprintf(stderr, "%s is not a SIMH .tap container file\n", argv[0]); + exit(2); + + case TIO_OPENFAIL: + fprintf(stderr, "%s open failed\n", argv[0]); + exit(3); + } + } else if (extract != 0) { + /* + * Extract files from one or more container files. + */ + if (argc == 0) + usage(); + + while (argc >= 1) { + switch (OpenTapeForRead(argv[0])) { + case TIO_SUCCESS: + case TIO_ERROR: + /* + * Extract files from the container file. + */ + do { + switch (status = ReadTapeRecord(record, sizeof(record))) { + case ST_EOM: + break; + + case ST_TM: + /* Second tape mark in a row - treat as end of medium */ + status = ST_EOM; + break; + + default: + if ((status & ST_ERROR) == 0) { + sprintf(filename, "%05u.dat", seqno++); + if ((file = fopen(filename, "w")) != NULL) { + while ((status != ST_EOM) && (status != ST_TM)) { + size_t length = status & ST_LENGTH; + + if ((status & ST_ERROR) != 0) + readError++; + + if (fwrite(record, sizeof(char), length, file) != length) + writeError++; + + status = ReadTapeRecord(record, sizeof(record)); + } + } else writeError++; + } + break; + } + } while (status != ST_EOM); + CloseTape(); + break; + + case TIO_CORRUPT: + fprintf(stderr, "%s is not a SIMH .tap container file\n", argv[0]); + exit(2); + + case TIO_OPENFAIL: + fprintf(stderr, "%s open failed\n", argv[0]); + exit(3); + } + argc--, argv++; + } + } + + if ((readError != 0) || (writeError != 0)) { + fprintf(stderr, "Read Errors: %d, Write Errors: %d\n", + readError, writeError); + return 4; + } + return 0; +} diff --git a/extracters/rawtap/rawtap.txt b/extracters/rawtap/rawtap.txt new file mode 100644 index 0000000..edffabe --- /dev/null +++ b/extracters/rawtap/rawtap.txt @@ -0,0 +1,24 @@ +rawtap manipulates .tap magtape container files used by SIMH. rawtap is +invoked by: + + rawtap -cae container [-r len] [...] + +To create a new container file and append file(s): + + rawtap -c container [-r len] file ... + +To append file(s) to an existing container file: + + rawtap -a container [-r len] file ... + +To extract files from container file(s): + + rawtap -e container ... + +Switches: + + -r len Specify max tape record size when writing to tape. + (1 <= len <= 65536). The new record size applies to all + following files on the command line. There may be multiple + "-r len" on the command line if the record size needs to be + different across multiple files. diff --git a/extracters/rawtap/tap.h b/extracters/rawtap/tap.h new file mode 100644 index 0000000..003a966 --- /dev/null +++ b/extracters/rawtap/tap.h @@ -0,0 +1,50 @@ +/* tap.h: simh tape representation definitions + + Copyright (c) 2017, John Forecast + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + JOHN FORECAST BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Except as contained in this notice, the name of John Forecast shall not + be used in advertising or otherwise to promote the sale, use or other dealings + in this Software without prior written authorization from John Forecast. + +*/ + +/* + * Metadata markers + */ +#define ST_EOM 0xFFFFFFFF /* end of medium */ +#define ST_GAP 0xFFFFFFFE /* erase gap */ +#define ST_TM 0x00000000 /* tape mark */ + +/* + * Record length field layout + */ +#define ST_ERROR 0x80000000 /* record contains an error */ +#define ST_MBZ 0x7F000000 /* must be zero */ +#define ST_LENGTH 0x00FFFFFF /* record length */ + +/* + * Data in the .tap container file is rounded up to an even number of bytes + */ +#define RECLEN(c) (((c) + 1) & ~1) + +/* + * The maximum record length supported by this code is 64K. + */ +#define MAXRCLNT 65536 diff --git a/extracters/rawtap/tapeio.c b/extracters/rawtap/tapeio.c new file mode 100644 index 0000000..ddb4159 --- /dev/null +++ b/extracters/rawtap/tapeio.c @@ -0,0 +1,614 @@ +/* tapeio.c: Tape I/O routines + + Copyright (c) 2017, John Forecast + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + JOHN FORECAST BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Except as contained in this notice, the name of John Forecast shall not + be used in advertising or otherwise to promote the sale, use or other dealings + in this Software without prior written authorization from John Forecast. + +*/ + +#include +#include +#include +#include +#include +#include "tapeio.h" + +FILE *tfile = NULL; + +char buffer[MAXRCLNT]; +int rLength, occupied; + +static int verifyFormat(void); + +/*++ + * OpenTapeForRead + * + * Open an existing SIMH .tap format file for read access. If the file is + * successfully opened, scan the file to determine if it is a valid .tap + * format file and whether there are error records present. + * + * Inputs: + * + * name - name of the file + * + * Outputs: + * + * None + * + * Returns: + * + * TIO_SUCCESS - file successfully opened, format is valid + * TIO_ERROR - file successfully opened, error records present + * TIO_CORRUPT - file successfully opened, format is invalid + * TIO_OPENFAIL - file open failed + * + * Note the file remains open if the return status is TIO_SUCCESS or + * TIO_ERROR + * + --*/ +int OpenTapeForRead( + char *name +) +{ + if ((tfile = fopen(name, "r")) != NULL) { + int status; + + status = verifyFormat(); + rewind(tfile); + if ((status != TIO_SUCCESS) && (status != TIO_ERROR)) + CloseTape(); + return status; + } + return TIO_OPENFAIL; +} + +/*++ + * OpenTapeForWrite + * + * Create a new SIMH .tap format fiole for write access. Two tape marks are + * written to the file and the file handle is rewound to the beginning of the + * file. If the file already exists, and error (TIO_CREATEFAIL) is returned. + * + * Inputs: + * + * name - name of the file + * + * Outputs: + * + * None + * + * Returns: + * + * TIO_SUCCESS - file successfully created + * TIO_IOERROR - I/O error writing the initial file contents + * TIO_CREATEFAIL - file create failed + * + --*/ +int OpenTapeForWrite( + char *name +) +{ + /* + * Fail if the file exists + */ + if (access(name, F_OK) == 0) + return TIO_CREATEFAIL; + + if ((tfile = fopen(name, "w+")) != NULL) { + uint32 tm = 0; + int status = TIO_SUCCESS; + + /* + * Write 2 tape marks + */ + if (fwrite(&tm, sizeof(tm), 2, tfile) != 2) + status = TIO_IOERROR; + + if (status == TIO_SUCCESS) { + rewind(tfile); + return TIO_SUCCESS; + } + + /* + * Failed to write the 2 tape marks. Try to delete the file before + * returning an error. + */ + CloseTape(); + unlink(name); + return TIO_IOERROR; + } + return TIO_CREATEFAIL; +} + +/*++ + * OpenTapeForAppend + * + * Open an existing SIMH .tap format file for write access, leaving the + * file handle positioned just before the final tape mark. If the file is + * successfully opened, scan the file to determine if it is a valid .tap + * format file and whether there are error records present. + * + * Inputs: + * + * name - name of the file + * + * Outputs: + * + * None + * + * Returns: + * + * TIO_SUCCESS - file successfully opened, format is valid + * TIO_ERROR - file successfully opened, error records present + * TIO_CORRUPT - file successfully opened, format is invalid + * TIO_OPENFAIL - file open failed + * + * Note the file remains open if the return status is TIO_SUCCESS or + * TIO_ERROR + * + --*/ +int OpenTapeForAppend( + char *name +) +{ + if ((tfile =fopen(name, "r+")) != NULL) { + int status; + + status = verifyFormat(); + if ((status != TIO_SUCCESS) && (status != TIO_ERROR)) + CloseTape(); + return status; + } + return TIO_OPENFAIL; +} + +/*++ + * CloseTape + * + * If the tape is open, close it. + * + * Inputs: + * + * None + * + * Outputs: + * + * None + * + * Returns: + * + * None + * + --*/ +void CloseTape(void) +{ + if (tfile != NULL) + fclose(tfile); + tfile = NULL; +} + +/*++ + * verifyFormat + * + * Verify the format of the SIMH .tap file. If the format is valid, leave + * the file handle positioned right before the last tape mark in the file. + * + * Inputs: + * + * None + * + * Outputs: + * + * None + * + * Returns: + * + * TIO_SUCCESS - file format is correct + * TIO_ERROR - file format is correct, error records detected + * TIO_CORRUPT - file format is invalid + * TIO_IOERROR - I/O errror while processing file + * + --*/ +static int verifyFormat(void) +{ + int errorCount = 0, tmSeen = 0; + uint8 meta[4]; + uint32 header, bc; + off_t position; + struct stat stat; + + /* + * Determine the size of the file. + */ + fstat(fileno(tfile), &stat); + + for (;;) { + position = ftello(tfile); + + /* + * If we are position at the end of file, there is a tape mark missing. + * Treat it as though there is one present. + */ + if (position == stat.st_size) + return TIO_SUCCESS; + + if (fread(meta, sizeof(meta), 1, tfile) != 1) + return TIO_CORRUPT; + + bc = (((unsigned int)meta[3]) << 24) | + (((unsigned int)meta[2]) << 16) | + (((unsigned int)meta[1]) << 8) | + (unsigned int)meta[0]; + + switch (bc) { + case ST_TM: + if (++tmSeen <= 1) + break; + /* Treat second TM in a row as end of medium */ + /* FALLTHROUGH */ + + case ST_EOM: + if (fseek(tfile, -sizeof(meta), SEEK_CUR) != 0) + return TIO_IOERROR; + return errorCount ? TIO_ERROR : TIO_SUCCESS; + + case ST_GAP: + break; + + default: + /* + * Record descriptor + */ + tmSeen = 0; + + header = bc; + if ((bc & ST_ERROR) != 0) + errorCount++; + if ((bc & ST_MBZ) != 0) + return TIO_CORRUPT; + + bc = RECLEN(bc & ST_LENGTH); + + /* + * Check if we are seeking outside of the file. If so, this is not + * a .tap container file. + */ + if ((position + bc + (2 * sizeof(meta))) > (unsigned long long)stat.st_size) + return TIO_CORRUPT; + + if (fseek(tfile, bc, SEEK_CUR) != 0) + return TIO_CORRUPT; + if (fread(meta, sizeof(meta), 1, tfile) != 1) + return TIO_CORRUPT; + + bc = (((unsigned int)meta[3]) << 24) | + (((unsigned int)meta[2]) << 16) | + (((unsigned int)meta[1]) << 8) | + (unsigned int)meta[0]; + if (header != bc) + return TIO_CORRUPT; + } + } +} + +/*++ + * ReadTapeRecord + * + * Read the next record from the tape into the specified buffer. If the + * buffer is smaller than the record, the entire record will be consumed. + * + * Inputs: + * + * buf - pointer to the buffer to receive the data + * len - length of the buffer + * + * Outputs: + * + * None + * + * Returns: + * + * ST_EOM - end of medium detected + * ST_TM - tape mark detected + * Other - record length (including error flag) + * if the buffer is smaller than the record, the + * length returned will be that of the buffer + * + --*/ +uint32 ReadTapeRecord( + void *buf, + int len +) +{ + long pos = ftell(tfile); + uint8 meta[4]; + uint32 bc, erflag, length; + + /* + * Note: any I/O errors are treated as "end of medium" detection. + */ + if (fread(meta, sizeof(meta), 1, tfile) != 1) + return ST_EOM; + + bc = (((unsigned int)meta[3]) << 24) | + (((unsigned int)meta[2]) << 16) | + (((unsigned int)meta[1]) << 8) | + (unsigned int)meta[0]; + + switch (bc) { + case ST_EOM: + case ST_TM: + return bc; + + default: + erflag = bc & ST_ERROR; + bc &= ST_LENGTH; + + length = (uint32)len; + if (bc < length) + length = bc; + + if (fread(buf, sizeof(uint8), length, tfile) != length) + return ST_EOM; + + /* + * Now position the file after this record. + */ + pos += RECLEN(bc) + (2 * sizeof(meta)); + if (fseek(tfile, pos, SEEK_SET) != 0) + return ST_EOM; + + return erflag | length; + } + return ST_EOM; +} + +/*++ + * ReadTapeRecordLength + * + * Get the length of the next record on the tape without actually reading + * the data. + * + * Inputs: + * + * None + * + * Outputs: + * + * None + * + * Returns: + * + * ST_EOM - end of medium detected + * ST_TM - tape mark detected + * Other - record length (including error flag) + * if the buffer is smaller than the record, the + * length returned will be that of the buffer + * + --*/ + uint32 ReadTapeRecordLength(void) + { + long pos = ftell(tfile); + uint8 meta[4]; + uint32 bc, erflag; + + /* + * Note: any I/O errors are treated as "end of medium" detection. + */ + if (fread(meta, sizeof(meta), 1, tfile) != 1) + return ST_EOM; + + bc = (((unsigned int)meta[3]) << 24) | + (((unsigned int)meta[2]) << 16) | + (((unsigned int)meta[1]) << 8) | + (unsigned int)meta[0]; + + switch (bc) { + case ST_EOM: + case ST_TM: + return bc; + + default: + erflag = bc & ST_ERROR; + bc &= ST_LENGTH; + + /* + * Now position the file after this record. + */ + pos += RECLEN(bc) + (2 * sizeof(meta)); + if (fseek(tfile, pos, SEEK_SET) != 0) + return ST_EOM; + + return erflag | bc; + } + return ST_EOM; + } + +/*++ + * WriteTapeRecord + * + * Write a record to the tape at it's current position. + * + * Inputs: + * + * buf - pointer to the buffer to be written + * len - length of the buffer + * + * Outputs: + * + * None + * + * Returns: + * + * 0 if record was written successfully, -1 if write failed + * + --*/ +int WriteTapeRecord( + void *buf, + int len +) +{ + uint8 meta[4]; + int datalen; + + meta[0] = len & 0xFF; + meta[1] = (len >> 8) & 0xFF; + meta[2] = (len >> 16) & 0xFF; + meta[3] = (len >> 24) & 0xFF; + + datalen = (len + 1) & ~1; + + if (fwrite(meta, sizeof(meta), 1, tfile) != 1) + return -1; + + if (fwrite(buf, datalen, 1, tfile) != 1) + return -1; + + if (fwrite(meta, sizeof(meta), 1, tfile) != 1) + return -1; + + return 0; +} + +/*++ + * WriteTapeMark + * + * Write a tape mark to the tape at it's current position and, optionally, + * backup to before the tape mark. + * + * Inputs: + * + * backup - if 1, reposition the tape to before the tape mark + * + * Outputs: + * + * None + * + * Returns: + * + * 0 if tape mark was written successfully, -1 if write failed + * + --*/ +int WriteTapeMark( + int backup +) +{ + uint32 tm = 0; + + if (fwrite(&tm, sizeof(tm), 1, tfile) != 1) + return -1; + + if (backup) + if (fseek(tfile, -sizeof(tm), SEEK_CUR) != 0) + return -1; + + return 0; +} + +/*++ + * initTapeBuffering + * + * Initialize variables for writes to tape for ASCII mode transfers + * (translates LF -> CRLF). + * + * Inputs: + * + * reclen - size of the tape record buffer to use + * + * Outputs: + * + * None + * + * Returns: + * + * None + * + --*/ +void initTapeBuffering( + int reclen +) +{ + rLength = reclen; + occupied = 0; +} + +/*++ + * flushTapeBuffering + * + * Flush any pending data out to the current tape. + * + * Inputs: + * + * None + * + * Outputs: + * + * None + * + * Returns: + * + * 0 if data was successfully flushed, -1 if write failed + * + --*/ +int flushTapeBuffering(void) +{ + uint32 count = occupied; + + occupied = 0; + + if (count != 0) + return WriteTapeRecord(buffer, count); + + return 0; +} + +/*++ + * writeTapeBuffering + * + * Write a character to the current tape, buffering the data into records. + * + * Inputs: + * + * ch - the character to be output + * + * Outputs: + * + * None + * + * Returns: + * + * 0 if character was successfully buffered or written to tape, -1 if + * write failed + * + --*/ +int writeTapeBuffering( + char ch +) +{ + buffer[occupied++] = ch; + + if (occupied == rLength) { + occupied = 0; + return WriteTapeRecord(buffer, rLength); + } + return 0; +} diff --git a/extracters/rawtap/tapeio.h b/extracters/rawtap/tapeio.h new file mode 100644 index 0000000..a2c1a40 --- /dev/null +++ b/extracters/rawtap/tapeio.h @@ -0,0 +1,62 @@ +/* tapeio.h: Tape I/O definitions + + Copyright (c) 2017, John Forecast + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + JOHN FORECAST BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Except as contained in this notice, the name of John Forecast shall not + be used in advertising or otherwise to promote the sale, use or other dealings + in this Software without prior written authorization from John Forecast. + +*/ + +#include "tap.h" +#include "defs.h" + +/* + * Tape open status return codes + */ +#define TIO_SUCCESS 0 /* operation successful */ +#define TIO_ERROR -1 /* error record seen */ +#define TIO_CORRUPT -2 /* tape format is corrupt */ +#define TIO_OPENFAIL -3 /* open operation failed */ +#define TIO_CREATEFAIL -4 /* create operation failed */ +#define TIO_IOERROR -5 /* I/O error */ + +/* + * Tape open/close routines. + */ +extern int OpenTapeForRead(char *); +extern int OpenTapeForWrite(char *); +extern int OpenTapeForAppend(char *); +extern void CloseTape(void); + +/* + * Tape I/O routines. + */ +uint32 ReadTapeRecord(void *, int); +uint32 ReadTapeRecordLength(void); +int WriteTapeRecord(void *, int); +int WriteTapeMark(int); + +/* + * Buffered I/O routines + */ +void initTapeBuffering(int); +int flushTapeBuffering(void); +int writeTapeBuffering(char);