diff --git a/.gitignore b/.gitignore index 9dafbed..2ce195a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,8 @@ \#* *~ *.bak +*.d +*.dep *.o *.orig *.vhd diff --git a/Makefile b/Makefile index fa4b0e2..65ad220 100644 --- a/Makefile +++ b/Makefile @@ -5,29 +5,17 @@ BIN=/usr/local/bin INSTALL=install CC=gcc +SUBDIRS=config11 converters crossassemblers extracters + .PHONY: all clean install uninstall # Omitted: putr: has no sources. all: - cd config11 && $(MAKE) CFLAGS="$(CFLAGS)" BIN="$(BIN)" INSTALL="$(INSTALL)" CC="$(CC)" - cd converters && $(MAKE) CFLAGS="$(CFLAGS)" BIN="$(BIN)" INSTALL="$(INSTALL)" CC="$(CC)" - cd crossassemblers && $(MAKE) CFLAGS="$(CFLAGS)" BIN="$(BIN)" INSTALL="$(INSTALL)" CC="$(CC)" - cd extracters && $(MAKE) CFLAGS="$(CFLAGS)" BIN="$(BIN)" INSTALL="$(INSTALL)" CC="$(CC)" + for subdir in $(SUBDIRS); do \ + $(MAKE) -C $$subdir CFLAGS="$(CFLAGS)" BIN="$(BIN)" INSTALL="$(INSTALL)" CC="$(CC)"; \ + done -clean: - cd config11 && $(MAKE) CFLAGS="$(CFLAGS)" BIN="$(BIN)" INSTALL="$(INSTALL)" CC="$(CC)" clean - cd converters && $(MAKE) CFLAGS="$(CFLAGS)" BIN="$(BIN)" INSTALL="$(INSTALL)" CC="$(CC)" clean - cd crossassemblers && $(MAKE) CFLAGS="$(CFLAGS)" BIN="$(BIN)" INSTALL="$(INSTALL)" CC="$(CC)" clean - cd extracters && $(MAKE) CFLAGS="$(CFLAGS)" BIN="$(BIN)" INSTALL="$(INSTALL)" CC="$(CC)" clean - -install: - cd config11 && $(MAKE) CFLAGS="$(CFLAGS)" BIN="$(BIN)" INSTALL="$(INSTALL)" CC="$(CC)" install - cd converters && $(MAKE) CFLAGS="$(CFLAGS)" BIN="$(BIN)" INSTALL="$(INSTALL)" CC="$(CC)" install - cd crossassemblers && $(MAKE) CFLAGS="$(CFLAGS)" BIN="$(BIN)" INSTALL="$(INSTALL)" CC="$(CC)" install - cd extracters && $(MAKE) CFLAGS="$(CFLAGS)" BIN="$(BIN)" INSTALL="$(INSTALL)" CC="$(CC)" install - -uninstall: - cd config11 && $(MAKE) CFLAGS="$(CFLAGS)" BIN="$(BIN)" INSTALL="$(INSTALL)" CC="$(CC)" uninstall - cd converters && $(MAKE) CFLAGS="$(CFLAGS)" BIN="$(BIN)" INSTALL="$(INSTALL)" CC="$(CC)" uninstall - cd crossassemblers && $(MAKE) CFLAGS="$(CFLAGS)" BIN="$(BIN)" INSTALL="$(INSTALL)" CC="$(CC)" uninstall - cd extracters && $(MAKE) CFLAGS="$(CFLAGS)" BIN="$(BIN)" INSTALL="$(INSTALL)" CC="$(CC)" uninstall +clean install uninstall: + for subdir in $(SUBDIRS); do \ + $(MAKE) -C $$subdir CFLAGS="$(CFLAGS)" BIN="$(BIN)" INSTALL="$(INSTALL)" CC="$(CC)" $@; \ + done diff --git a/README.md b/README.md index 20baf48..da2361f 100644 --- a/README.md +++ b/README.md @@ -50,7 +50,7 @@ hpasm | Assembler for the HP2100 macro1 | Assembler for the PDP-1 macro7 | Assembler for the PDP-7 macro8x | Assembler for the PDP-8 -macro11 | Assembler for the PDP-11 +macro11 | Assembler for the PDP-11. Synchronized from git `https://gitlab.com/Rhialto/macro11.git`; most recently from tag `macro11-v0.7.2`. ## Extracters @@ -66,6 +66,7 @@ mmdir | List directory of Interdata MDM tape mtdump | Dump the record structure of a SIMH, E11, TPC, or P7B ods2 | Directory, Copy & Search commands for VMS ODS2 disk images rawcopy | Create SIMH disk image from physical media on RAW device. +rstsflx | Manipulate PDP11 RSTS file systems. sdsdump | Disassemble SDS SDS paper tape tpdump | Dump files on IBM 1401 tape diff --git a/converters/Makefile b/converters/Makefile index 79af5d5..3238b2c 100644 --- a/converters/Makefile +++ b/converters/Makefile @@ -1,92 +1,22 @@ # all of these can be over-ridden on the "make" command line if they don't suit your environment. -CFLAGS="-O2 -Wall -Wshadow -Wextra -pedantic -Woverflow -Wstrict-overflow" +CFLAGS=-O2 -Wall -Wshadow -Wextra -pedantic -Woverflow -Wstrict-overflow BIN=/usr/local/bin INSTALL=install CC=gcc +SUBDIRS=asc cosy decsys dtos8cvt fsio gt7cvt hpconvert indent littcvt m8376 \ + mksimtape mt2tpc mtcvt23 mtcvtfix mtcvtodd noff sfmtcvt strrem strsub \ + tar2mt tp512cvt tpc2mt + .PHONY: all clean install uninstall all: - cd asc && $(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)" - cd mt2tpc && $(MAKE) CFLAGS="$(CFLAGS)" BIN="$(BIN)" INSTALL="$(INSTALL)" CC="$(CC)" - cd mtcvtfix && $(MAKE) CFLAGS="$(CFLAGS)" BIN="$(BIN)" INSTALL="$(INSTALL)" CC="$(CC)" - cd noff && $(MAKE) CFLAGS="$(CFLAGS)" BIN="$(BIN)" INSTALL="$(INSTALL)" CC="$(CC)" - cd strrem && $(MAKE) CFLAGS="$(CFLAGS)" BIN="$(BIN)" INSTALL="$(INSTALL)" CC="$(CC)" - cd tar2mt && $(MAKE) CFLAGS="$(CFLAGS)" BIN="$(BIN)" INSTALL="$(INSTALL)" CC="$(CC)" - cd tpc2mt && $(MAKE) CFLAGS="$(CFLAGS)" BIN="$(BIN)" INSTALL="$(INSTALL)" CC="$(CC)" - cd decsys && $(MAKE) CFLAGS="$(CFLAGS)" BIN="$(BIN)" INSTALL="$(INSTALL)" CC="$(CC)" - cd gt7cvt && $(MAKE) CFLAGS="$(CFLAGS)" BIN="$(BIN)" INSTALL="$(INSTALL)" CC="$(CC)" - cd indent && $(MAKE) CFLAGS="$(CFLAGS)" BIN="$(BIN)" INSTALL="$(INSTALL)" CC="$(CC)" - cd m8376 && $(MAKE) CFLAGS="$(CFLAGS)" BIN="$(BIN)" INSTALL="$(INSTALL)" CC="$(CC)" - cd mtcvt23 && $(MAKE) CFLAGS="$(CFLAGS)" BIN="$(BIN)" INSTALL="$(INSTALL)" CC="$(CC)" - cd mtcvtodd && $(MAKE) CFLAGS="$(CFLAGS)" BIN="$(BIN)" INSTALL="$(INSTALL)" CC="$(CC)" - cd sfmtcvt && $(MAKE) CFLAGS="$(CFLAGS)" BIN="$(BIN)" INSTALL="$(INSTALL)" CC="$(CC)" - cd strsub && $(MAKE) CFLAGS="$(CFLAGS)" BIN="$(BIN)" INSTALL="$(INSTALL)" CC="$(CC)" - cd tp512cvt && $(MAKE) CFLAGS="$(CFLAGS)" BIN="$(BIN)" INSTALL="$(INSTALL)" CC="$(CC)" + for subdir in $(SUBDIRS); do \ + $(MAKE) -C $$subdir CFLAGS="$(CFLAGS)" BIN="$(BIN)" INSTALL="$(INSTALL)" CC="$(CC)"; \ + done -clean: - cd asc && $(MAKE) CFLAGS="$(CFLAGS)" BIN="$(BIN)" INSTALL="$(INSTALL)" CC="$(CC)" clean - cd dtos8cvt && $(MAKE) CFLAGS="$(CFLAGS)" BIN="$(BIN)" INSTALL="$(INSTALL)" CC="$(CC)" clean - cd hpconvert && $(MAKE) CFLAGS="$(CFLAGS)" BIN="$(BIN)" INSTALL="$(INSTALL)" CC="$(CC)" clean - cd littcvt && $(MAKE) CFLAGS="$(CFLAGS)" BIN="$(BIN)" INSTALL="$(INSTALL)" CC="$(CC)" clean - cd mt2tpc && $(MAKE) CFLAGS="$(CFLAGS)" BIN="$(BIN)" INSTALL="$(INSTALL)" CC="$(CC)" clean - cd mtcvtfix && $(MAKE) CFLAGS="$(CFLAGS)" BIN="$(BIN)" INSTALL="$(INSTALL)" CC="$(CC)" clean - cd noff && $(MAKE) CFLAGS="$(CFLAGS)" BIN="$(BIN)" INSTALL="$(INSTALL)" CC="$(CC)" clean - cd strrem && $(MAKE) CFLAGS="$(CFLAGS)" BIN="$(BIN)" INSTALL="$(INSTALL)" CC="$(CC)" clean - cd tar2mt && $(MAKE) CFLAGS="$(CFLAGS)" BIN="$(BIN)" INSTALL="$(INSTALL)" CC="$(CC)" clean - cd tpc2mt && $(MAKE) CFLAGS="$(CFLAGS)" BIN="$(BIN)" INSTALL="$(INSTALL)" CC="$(CC)" clean - cd decsys && $(MAKE) CFLAGS="$(CFLAGS)" BIN="$(BIN)" INSTALL="$(INSTALL)" CC="$(CC)" clean - cd gt7cvt && $(MAKE) CFLAGS="$(CFLAGS)" BIN="$(BIN)" INSTALL="$(INSTALL)" CC="$(CC)" clean - cd indent && $(MAKE) CFLAGS="$(CFLAGS)" BIN="$(BIN)" INSTALL="$(INSTALL)" CC="$(CC)" clean - cd m8376 && $(MAKE) CFLAGS="$(CFLAGS)" BIN="$(BIN)" INSTALL="$(INSTALL)" CC="$(CC)" clean - cd mtcvt23 && $(MAKE) CFLAGS="$(CFLAGS)" BIN="$(BIN)" INSTALL="$(INSTALL)" CC="$(CC)" clean - cd mtcvtodd && $(MAKE) CFLAGS="$(CFLAGS)" BIN="$(BIN)" INSTALL="$(INSTALL)" CC="$(CC)" clean - cd sfmtcvt && $(MAKE) CFLAGS="$(CFLAGS)" BIN="$(BIN)" INSTALL="$(INSTALL)" CC="$(CC)" clean - cd strsub && $(MAKE) CFLAGS="$(CFLAGS)" BIN="$(BIN)" INSTALL="$(INSTALL)" CC="$(CC)" clean - cd tp512cvt && $(MAKE) CFLAGS="$(CFLAGS)" BIN="$(BIN)" INSTALL="$(INSTALL)" CC="$(CC)" clean - -install: - cd asc && $(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 - cd mt2tpc && $(MAKE) CFLAGS="$(CFLAGS)" BIN="$(BIN)" INSTALL="$(INSTALL)" CC="$(CC)" install - cd mtcvtfix && $(MAKE) CFLAGS="$(CFLAGS)" BIN="$(BIN)" INSTALL="$(INSTALL)" CC="$(CC)" install - cd noff && $(MAKE) CFLAGS="$(CFLAGS)" BIN="$(BIN)" INSTALL="$(INSTALL)" CC="$(CC)" install - cd strrem && $(MAKE) CFLAGS="$(CFLAGS)" BIN="$(BIN)" INSTALL="$(INSTALL)" CC="$(CC)" install - cd tar2mt && $(MAKE) CFLAGS="$(CFLAGS)" BIN="$(BIN)" INSTALL="$(INSTALL)" CC="$(CC)" install - cd tpc2mt && $(MAKE) CFLAGS="$(CFLAGS)" BIN="$(BIN)" INSTALL="$(INSTALL)" CC="$(CC)" install - cd decsys && $(MAKE) CFLAGS="$(CFLAGS)" BIN="$(BIN)" INSTALL="$(INSTALL)" CC="$(CC)" install - cd gt7cvt && $(MAKE) CFLAGS="$(CFLAGS)" BIN="$(BIN)" INSTALL="$(INSTALL)" CC="$(CC)" install - cd indent && $(MAKE) CFLAGS="$(CFLAGS)" BIN="$(BIN)" INSTALL="$(INSTALL)" CC="$(CC)" install - cd m8376 && $(MAKE) CFLAGS="$(CFLAGS)" BIN="$(BIN)" INSTALL="$(INSTALL)" CC="$(CC)" install - cd mtcvt23 && $(MAKE) CFLAGS="$(CFLAGS)" BIN="$(BIN)" INSTALL="$(INSTALL)" CC="$(CC)" install - cd mtcvtodd && $(MAKE) CFLAGS="$(CFLAGS)" BIN="$(BIN)" INSTALL="$(INSTALL)" CC="$(CC)" install - cd sfmtcvt && $(MAKE) CFLAGS="$(CFLAGS)" BIN="$(BIN)" INSTALL="$(INSTALL)" CC="$(CC)" install - cd strsub && $(MAKE) CFLAGS="$(CFLAGS)" BIN="$(BIN)" INSTALL="$(INSTALL)" CC="$(CC)" install - cd tp512cvt && $(MAKE) CFLAGS="$(CFLAGS)" BIN="$(BIN)" INSTALL="$(INSTALL)" CC="$(CC)" install - -uninstall: - cd asc && $(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 - cd mt2tpc && $(MAKE) CFLAGS="$(CFLAGS)" BIN="$(BIN)" INSTALL="$(INSTALL)" CC="$(CC)" uninstall - cd mtcvtfix && $(MAKE) CFLAGS="$(CFLAGS)" BIN="$(BIN)" INSTALL="$(INSTALL)" CC="$(CC)" uninstall - cd noff && $(MAKE) CFLAGS="$(CFLAGS)" BIN="$(BIN)" INSTALL="$(INSTALL)" CC="$(CC)" uninstall - cd strrem && $(MAKE) CFLAGS="$(CFLAGS)" BIN="$(BIN)" INSTALL="$(INSTALL)" CC="$(CC)" uninstall - cd tar2mt && $(MAKE) CFLAGS="$(CFLAGS)" BIN="$(BIN)" INSTALL="$(INSTALL)" CC="$(CC)" uninstall - cd tpc2mt && $(MAKE) CFLAGS="$(CFLAGS)" BIN="$(BIN)" INSTALL="$(INSTALL)" CC="$(CC)" uninstall - cd decsys && $(MAKE) CFLAGS="$(CFLAGS)" BIN="$(BIN)" INSTALL="$(INSTALL)" CC="$(CC)" uninstall - cd gt7cvt && $(MAKE) CFLAGS="$(CFLAGS)" BIN="$(BIN)" INSTALL="$(INSTALL)" CC="$(CC)" uninstall - cd indent && $(MAKE) CFLAGS="$(CFLAGS)" BIN="$(BIN)" INSTALL="$(INSTALL)" CC="$(CC)" uninstall - cd m8376 && $(MAKE) CFLAGS="$(CFLAGS)" BIN="$(BIN)" INSTALL="$(INSTALL)" CC="$(CC)" uninstall - cd mtcvt23 && $(MAKE) CFLAGS="$(CFLAGS)" BIN="$(BIN)" INSTALL="$(INSTALL)" CC="$(CC)" uninstall - cd mtcvtodd && $(MAKE) CFLAGS="$(CFLAGS)" BIN="$(BIN)" INSTALL="$(INSTALL)" CC="$(CC)" uninstall - cd sfmtcvt && $(MAKE) CFLAGS="$(CFLAGS)" BIN="$(BIN)" INSTALL="$(INSTALL)" CC="$(CC)" uninstall - cd strsub && $(MAKE) CFLAGS="$(CFLAGS)" BIN="$(BIN)" INSTALL="$(INSTALL)" CC="$(CC)" uninstall - cd tp512cvt && $(MAKE) CFLAGS="$(CFLAGS)" BIN="$(BIN)" INSTALL="$(INSTALL)" CC="$(CC)" uninstall +clean install uninstall: + for subdir in $(SUBDIRS); do \ + $(MAKE) -C $$subdir CFLAGS="$(CFLAGS)" BIN="$(BIN)" INSTALL="$(INSTALL)" CC="$(CC)" $@; \ + done 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..e6472d1 --- /dev/null +++ b/converters/cosy/cosy.c @@ -0,0 +1,590 @@ +/* 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) { + if (ch == 0) + continue; + + 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/fsio/Changes b/converters/fsio/Changes new file mode 100644 index 0000000..338a1f9 --- /dev/null +++ b/converters/fsio/Changes @@ -0,0 +1,35 @@ +Changes Since Alpha Release: + +1. mount: Fix memory leak when file system verification fails + +2. dos11: Fix hang when parsing wildcard programmer number + +3. Migrate to using GNU readline library for command line editing etc + +4. local: Fix buffering so that local "type" command works + +5. Add DOS-11 magtape support using file system type "dosmt" + +6. newfs: Remove -a switch. Replace with "-t type" switch to allow more + flexibility. The equivalent of "-a" is now "-t rl02". + +12-Jun-19 + +- Added support for sector interleave on RX01/RX02 drives + + - Now supports creating RX01-sized container files + - Now works with RTFLX on TOPS-10 with RX02-sized container files + (documentation updated) + +25-Jun-19 + +- Added support for OS/8 file systems on RX01, RX02 and RK05 drives using + file system type "os8" + +- Added support for creating RT-11 file systems with additional space + allocated to each directory entry + +9-Jan-20 + +- Merged change from tvrusso to fix compilation on FreeBSD + diff --git a/converters/fsio/Makefile b/converters/fsio/Makefile new file mode 100644 index 0000000..9fa9330 --- /dev/null +++ b/converters/fsio/Makefile @@ -0,0 +1,55 @@ +CFLAGS=-O2 -Wall -Wshadow -Wextra -pedantic -Woverflow -Wstrict-overflow +#CFLAGS=-g -Wall -Wshadow -Wextra -pedantic -Woverflow -Wstrict-overflow +DEFINES=-DDEBUG +BIN=/usr/local/bin +MAN=/usr/local/man/man1 +INSTALL=install +CC=gcc + +EXECUTABLE=fsio +SOURCES=fsio.c declib.c tape.c dos11.c rt11.c dosmt.c local.c os8.c +INCLUDES=fsio.h declib.h tape.h dos11.h rt11.h dosmt.h os8.h +LIBS=-lreadline +MANPAGE=fsio.1 +MANPAGE_DOS=fsio-dos11.1 +MANPAGE_RT=fsio-rt11.1 +MANPAGE_DOSMT=fsio-dosmt.1 +MANPAGE_OS8=fsio-os8.1 +ARCHIVE=fsio.tgz + +RELEASEFILES=$(BIN)/$(EXECUTABLE) +RELEASEFILES+=$(MAN)/$(MANPAGE) +RELEASEFILES+=$(MAN)/$(MANPAGE_DOS) +RELEASEFILES+=$(MAN)/$(MANPAGE_RT) +RELEASEFILES+=$(MAN)/$(MANPAGE_DOSMT) +RELEASEFILES+=$(MAN)/$(MANPAGE_OS8) +RELEASEFILES+=./fsio.txt ./fsioSimh.txt + +$(EXECUTABLE): $(SOURCES) $(INCLUDES) Makefile + $(CC) $(CFLAGS) $(DEFINES) -o $(EXECUTABLE) $(SOURCES) $(LIBS) + +.phony: clean install uninstall + +clean: + rm -f $(EXECUTABLE) + +install: $(EXECUTABLE) $(MANPAGE) $(MANPAGE_DOS) $(MANPAGE_RT) + $(INSTALL) -p -m u=rx,g=rx,o=rx $(EXECUTABLE) $(BIN) + mkdir -p $(MAN) + $(INSTALL) -p -m u=r,g=r,o=r $(MANPAGE) $(MAN) + $(INSTALL) -p -m u=r,g=r,o=r $(MANPAGE_DOS) $(MAN) + $(INSTALL) -p -m u=r,g=r,o=r $(MANPAGE_RT) $(MAN) + $(INSTALL) -p -m u=r,g=r,o=r $(MANPAGE_DOSMT) $(MAN) + $(INSTALL) -p -m u=r,g=r,o=r $(MANPAGE_OS8) $(MAN) + +uninstall: + rm -f $(BIN)/$(EXECUTABLE) + rm -f $(MAN)/$(MANPAGE) + rm -f $(MAN)/$(MANPAGE_DOS) + rm -f $(MAN)/$(MANPAGE_RT) + rm -f $(MAN)/$(MANPAGE_DOSMT) + rm -f $(MAN)/$(MANPAGE_OS8) + +# This assumes that fsio has been "installed" on the current system +archive: $(RELEASEFILES) + tar czvPf $(ARCHIVE) $(RELEASEFILES) diff --git a/converters/fsio/declib.c b/converters/fsio/declib.c new file mode 100644 index 0000000..4568f72 --- /dev/null +++ b/converters/fsio/declib.c @@ -0,0 +1,242 @@ +/* + * Copyright (C) 2018 John Forecast. All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY JOHN FORECAST "AS IS" AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/* + * Routines/data which are common to multiple DEC file systems. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "fsio.h" + +char rad50[] = " ABCDEFGHIJKLMNOPQRSTUVWXYZ$.%0123456789"; + +char *month[12] = { + "Jan", "Feb", "Mar", "Apr", "May", "Jun", + "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" +}; + +/* + * 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 }; + +/* + * ASCII character set + */ +char *Ascii[128] = { + "NUL", "SOH", "STX", "ETX", "EOT", "ENQ", "ACK", "BEL", + " BS", " HT", " LF", " VT", " FF", " CR", " SO", " SI", + "DLE", "DC1", "DC2", "DC3", "DC4", "NAK", "SYN", "ETB", + "CAN", " EM", "SUB", "ESC", " FS", " GS", " RS", " US", + " ", " ! ", " \" ", " # ", " $ ", " % ", " & ", " \' ", + " ( ", " ) ", " * ", " + ", " , ", " - ", " . ", " / ", + " 0 ", " 1 ", " 2 ", " 3 ", " 4 ", " 5 ", " 6 ", " 7 ", + " 8 ", " 9 ", " : ", " ; ", " < ", " = ", " > ", " ? ", + " @ ", " A ", " B ", " C ", " D ", " E ", " F ", " G ", + " H ", " I ", " J ", " K ", " L ", " M ", " N ", " O ", + " P ", " Q ", " R ", " S ", " T ", " U ", " V ", " W ", + " X ", " Y ", " Z ", " [ ", " \\ ", " ] ", " ^ ", " _ ", + " ` ", " a ", " b ", " c ", " d ", " e ", " f ", " g ", + " h ", " i ", " j ", " k ", " l ", " m ", " n ", " o ", + " p ", " q ", " r ", " s ", " t ", " u ", " v ", " w ", + " x ", " y ", " z ", " { ", " | ", " } ", " ~ ", "DEL" +}; + +/*++ + * r 5 0 A s c + * + * Convert a 16-bit rad50 value into 3 ASCII characters. + * + * Inputs: + * + * value - rad50 value to be converted + * outbuf - pointer to 3 character buffer to receive the + * converted output + * + * Outputs: + * + * None + * + * Returns: + * + * None + * + --*/ +void r50Asc( + uint16_t value, + char *outbuf +) +{ + outbuf[2] = rad50[value % 050]; + value /= 050; + outbuf[1] = rad50[value % 050]; + outbuf[0] = rad50[value / 050]; +} + +/*++ + * r 5 0 A s c N o S p a c e + * + * Convert a 16-bit rad50 value into up to 3 ASCII characters. Spaces are + * dropped from the conversion. + * + * Inputs: + * + * value - rad50 value to be converted + * outbuf - pointer to 3 character buffer to receive the + * converted output + * + * Outputs: + * + * None + * + * Returns: + * + * None + * + --*/ +void r50AscNoSpace( + uint16_t value, + char *outbuf +) +{ + uint16_t value2 = value / 050; + + /* + * The rad50 representation of ' ' is zero. + */ + if ((value2 / 050) != 0) + *outbuf++ = rad50[value2 / 050]; + if ((value2 % 050) != 0) + *outbuf++ = rad50[value2 % 050]; + if ((value % 050) != 0) + *outbuf++ = rad50[value % 050]; +} + +/*++ + * a s c R 5 0 + * + * Convert 3 ASCII characters into a single 16-bit rad50 value. If an input + * character is not in the rad50 character set it is quietly converted to '%'. + * + * Inputs: + * + * inbuf - pointer to the buffer with the 3 characters + * + * Outputs: + * + * None + * + * Returns: + * + * rad50 value for the 3 characters + * + --*/ +uint16_t ascR50( + char *inbuf +) +{ + uint16_t 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; +} + +/*++ + * d o s 1 1 D a t e + * + * 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: + * + * Pointer to the date string + * + --*/ +char *dos11Date( + uint16_t value, + char *buf +) +{ + unsigned short year, doyr, leapyr; + unsigned short *table; + + /* + * The DOS/BATCH-11 date format is documented as having 3 reserved bit. + * Version 9.20C (and later?) makes use of these bits to allow dates up + * to 2035. Unfortunately, the date input routine only allows up to 1999! + */ + 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"); + return buf; +} diff --git a/converters/fsio/declib.h b/converters/fsio/declib.h new file mode 100644 index 0000000..f59bdd7 --- /dev/null +++ b/converters/fsio/declib.h @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2018 John Forecast. All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY JOHN FORECAST "AS IS" AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#ifndef __DECLIB_H__ +#define __DECLIB_H__ + +extern char rad50[]; +extern char *month[]; +extern char *Ascii[]; + +extern void r50Asc(uint16_t, char *); +extern void r50AscNoSpace(uint16_t, char *); +extern uint16_t ascR50(char *); +extern char *dos11Date(uint16_t, char *); + +#endif diff --git a/converters/fsio/dos11.c b/converters/fsio/dos11.c new file mode 100644 index 0000000..96e44bb --- /dev/null +++ b/converters/fsio/dos11.c @@ -0,0 +1,2490 @@ +/* + * Copyright (C) 2018 John Forecast. All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY JOHN FORECAST "AS IS" AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/* + * Support routines for handling DOS/BATCH-11 file systems under fsio + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "fsio.h" + +extern uint16_t bits[], lowbits[], highbits[]; +extern uint8_t zeroes[]; + +/* + * Table of "set" commands + */ +static char *setCmds[] = { + "uic", + "ufd", + NULL +}; +#define DOS11SET_UIC 0 +#define DOS11SET_UFD 1 + +static uint16_t bitmapAllocBlock(struct mountedFS *); +static uint16_t bitmapAllocContiguous(struct mountedFS *, uint16_t); +static int bitmapFlush(struct mountedFS *); +static int bitmapLoad(struct mountedFS *, uint16_t); +static int bitmapReleaseBlock(struct mountedFS *, uint16_t); +static int bitmapScan(struct mountedFS *, uint16_t, uint16_t, uint16_t *); +static int bitmapClrBit(struct mountedFS *, uint16_t); +static int bitmapSetBit(struct mountedFS *, uint16_t); +static int bitmapGetWord(struct mountedFS *, uint16_t, uint16_t *); + +int dos11CreateFile(struct mountedFS *, struct dos11FileSpec *, struct dos11OpenFile *, unsigned long); +int dos11LookupFile(struct mountedFS *, struct dos11FileSpec *, struct dos11OpenFile *); +void dos11UpdateFile(struct mountedFS *, struct dos11OpenFile *); +uint16_t dos11XtndDirectory(struct mountedFS *); + +static void dos11CloseFile(void *); + +int dos11ReadBlock(struct mountedFS *, unsigned int, void *); +int dos11WriteBlock(struct mountedFS *, unsigned int, void *); + +extern int args; +extern char **words; +extern int quiet; + +/*++ + * b i t m a p A l l o c B l o c k + * + * Find an unsed block and allocate it. Ths scan will start at the first + * known location of a free block (or 0 if this is the first scan) and will + * will remember this location to reduce the amount of scanning required + * for subsequent allocations. + * + * Inputs: + * + * mount - pointer to a mounted file system descriptor + * + * Outputs: + * + * If the allocation is successful, the bitmap will be updated in + * memory and the bitmap buffer marked as dirty. + * + * Returns: + * + * Allocated block # if successful, 0 otherwise + * + --*/ +static uint16_t bitmapAllocBlock( + struct mountedFS *mount +) +{ + struct DOS11data *data = &mount->dos11data; + uint16_t map = data->bmscan / MAP_BLOCKS; + uint16_t offset = data->bmscan % MAP_BLOCKS; + + /* + * Iterate across all available bitmaps. + */ + while (map < data->bitmaps) { + uint16_t wrd = offset / 16; + uint16_t bit = offset % 16; + + if (bitmapLoad(mount, map) == 0) + return 0; + + /* + * Iterate across a single bitmap. + */ + while (wrd < MAP_LEN) { + uint16_t val = le16toh(data->bmbuf[MAP_BMSTART + wrd]); + + if (val != 0177777) { + data->bmscan = (map * MAP_BLOCKS) + (wrd * 16); + + while (bit < 16) { + if ((val & bits[bit]) == 0) { + val |= bits[bit]; + data->bmbuf[MAP_BMSTART + wrd] = htole16(val); + data->bmdirty = 1; + return (map * MAP_BLOCKS) + (wrd * 16) + bit; + } + bit++; + } + } + wrd++; + bit = 0; + } + map++; + offset = 0; + } + return 0; +} + +/*++ + * b i t m a p A l l o c C o n t i g u o u s + * + * Find a sequence of contiguous bits in the bitmaps and allocate them. + * + * Inputs: + * + * mount - pointer to a mounted file system descriptor + * count - # of contiguous blocks to allocate + * + * Outputs: + * + * If the allocation is successful, the bitmap will be updated in + * memory and possibly on disk. The last referenced bitmap will be + * marked as dirty. + * + * Returns: + * + * First allocated block # if successful, 0 otherwise + * + --*/ +static uint16_t bitmapAllocContiguous( + struct mountedFS *mount, + uint16_t count +) +{ + struct DOS11data *data = &mount->dos11data; + uint16_t map, offset, scan = data->bmscan; + uint16_t i; + uint8_t first = 1; + + /* + * Check that the request is reasonable + */ + if (count >= data->blocks) + return 0; + + restart: + /* + * Make sure that there may be sufficient space to satisfy the request. + */ + if (((data->blocks - 1) - scan) < count) + return 0; + + map = scan / MAP_BLOCKS; + offset = scan % MAP_BLOCKS; + + /* + * Iterate across all available bitmaps. + */ + while (map < data->bitmaps) { + uint16_t wrd = offset / 16; + + if (bitmapLoad(mount, map) == 0) + return 0; + + /* + * Iterate across a single bitmap. + */ + while (wrd < MAP_LEN) { + uint16_t val = le16toh(data->bmbuf[MAP_BMSTART + wrd]); + + if (val != 0177777) { + if (first) { + /* + * Update cache of where to start scanning + */ + data->bmscan = (map * MAP_BLOCKS) + (wrd * 16); + first = 0; + } + + if (count <= 16) { + /* + * The required blocks could fit into a single. See if that is the + * case. + */ + uint16_t mask = lowbits[count - 1]; + + for (i = 0; i < (17 - count); i++) + if ((val & (mask << i)) == 0) { + /* + * We have a fit. Allocate the space. + */ + val |= (mask << i); + data->bmbuf[MAP_BMSTART + wrd] = htole16(val); + data->bmdirty = 1; + return (map * MAP_BLOCKS) + (wrd * 16) + i; + } + } + + /* + * Let's see if we can make use of the last blocks described by + * this word. + */ + if ((val & 0100000) == 0) { + uint16_t base = (map * MAP_BLOCKS) + (wrd * 16); + + /* + * See how many bits we can use + */ + for (i = 1; i <= 16; i++) + if ((val & highbits[i]) != 0) + break; + + if (bitmapScan(mount, count - i, base + 16, &scan) == 0) + goto restart; + + base = base + 16 - i; + + /* + * Allocate the blocks + */ + for (i = 0; i < count; i++) + if (bitmapSetBit(mount, base + i) == 0) + return 0; + + return base; + } + } + wrd++; + } + map++; + offset = 0; + } + return 0; +} + +/*++ + * b i t m a p F l u s h + * + * If the currently loaded bitmap is dirty, write it back to disk. + * + * Inputs: + * + * mount - pointer to a mounted file system descriptor + * + * Outputs: + * + * None + * + * Returns: + * + * 1 if successful, 0 otherwise + * + --*/ +static int bitmapFlush( + struct mountedFS *mount +) +{ + struct DOS11data *data = &mount->dos11data; + + if (data->bmdirty != 0) { + if (dos11WriteBlock(mount, data->bmblk[data->bmindex], data->bmbuf) == 0) + return 0; + data->bmdirty = 0; + } + return 1; +} + +/*++ + * b i t m a p L o a d + * + * Load the specified bitmap into the bitmap buffer. If the buffer is dirty, + * write the current contents of the buffer back to disk before loading the + * new bitmap. + * + * Inputs: + * + * mount - pointer to a mounted file system descriptor + * map - logical bitmap # in the range 0 - N + * + * Outputs: + * + * None + * + * Returns: + * + * 1 if successful, 0 otherwise + * + --*/ +static int bitmapLoad( + struct mountedFS *mount, + uint16_t map +) +{ + struct DOS11data *data = &mount->dos11data; + + if (map >= data->bitmaps) { + ERROR("Invalid bitmap # (%d), max is %d\n", map, data->bitmaps); + return 0; + } + + /* + * If the map is already loaded, nothing to do. + */ + if (map != data->bmindex) { + if (data->bmdirty != 0) { + if (dos11WriteBlock(mount, data->bmblk[data->bmindex], data->bmbuf) == 0) + return 0; + data->bmdirty = 0; + } + + data->bmindex = map; + if (dos11ReadBlock(mount, data->bmblk[map], data->bmbuf) == 0) + return 0; + } + + return 1; +} + +/*++ + * b i t m a p R e l e a s e B l o c k + * + * Release a specified block in the bitmap. + * + * Inputs: + * + * mount - pointer to a mounted file system descriptor + * block - block # to be released + * + * Outputs: + * + * The bitmap will be updated by releasing the block and the bitmap + * will be marked as dirty. + * + * Returns: + * + * 1 if successful, 0 otherwise + * + --*/ +static int bitmapReleaseBlock( + struct mountedFS *mount, + uint16_t block +) +{ + return bitmapClrBit(mount, block); +} + +/*++ + * b i t m a p S c a n + * + * Scan forward from a word boundary to check whether a sequence of blocks + * is free. + * + * Inputs: + * + * mount - pointer to a mounted file system descriptor + * count - # of contiguous blocks to allocate + * start - starting block # (always aligned on a 16 block + * boundary) + * restart - return restart point here on failure + * + * Outputs: + * + * None + * + * Returns: + * + * 1 on success, 0 otherwise + * + --*/ +static int bitmapScan( + struct mountedFS *mount, + uint16_t count, + uint16_t start, + uint16_t *restart +) +{ + uint16_t value; + + while (count >= 16) { + if ((bitmapGetWord(mount, start, &value) == 0) || + (value != 0)) { + *restart = start; + return 0; + } + start += 16; + count -= 16; + } + + if (count != 0) + if ((bitmapGetWord(mount, start, &value) == 0) || + ((value & lowbits[count - 1]) != 0)) { + *restart = start; + return 0; + } + + return 1; +} + +/*++ + * b i t m a p C l r B i t + * + * Modify the bitmap by clearing the bit associated with a specific block. + * + * Inputs: + * + * mount - pointer to a mounted file system descriptor + * block - block # whose associated bit is to be cleared + * + * Outputs: + * + * The bitmap will be updated by clearing the bit and the bitmap will + * be marked as dirty. + * + * Returns: + * + * 1 if successful, 0 otherwise + * + --*/ +static int bitmapClrBit( + struct mountedFS *mount, + uint16_t block +) +{ + struct DOS11data *data = &mount->dos11data; + uint16_t map = block / MAP_BLOCKS; + uint16_t offset = block % MAP_BLOCKS; + uint16_t wrd = offset / 16; + uint16_t bit = offset % 16; + + if (bitmapLoad(mount, map) == 0) + return 0; + + data->bmbuf[MAP_BMSTART + wrd] = + htole16(le16toh(data->bmbuf[MAP_BMSTART + wrd]) & ~bits[bit]); + data->bmdirty = 1; + return 1; +} + +/*++ + * b i t m a p S e t B i t + * + * Modify the bitmap by setting the bit associated with a specific block. + * + * Inputs: + * + * mount - pointer to a mounted file system descriptor + * block - block # whose associated bit is to be set + * + * Outputs: + * + * The bitmap will be updated by setting the bit and the bitmap will + * be marked as dirty. + * + * Returns: + * + * 1 if successful, 0 otherwise + * + --*/ +static int bitmapSetBit( + struct mountedFS *mount, + uint16_t block +) +{ + struct DOS11data *data = &mount->dos11data; + uint16_t map = block / MAP_BLOCKS; + uint16_t offset = block % MAP_BLOCKS; + uint16_t wrd = offset / 16; + uint16_t bit = offset % 16; + + if (bitmapLoad(mount, map) == 0) + return 0; + + data->bmbuf[MAP_BMSTART + wrd] = + htole16(le16toh(data->bmbuf[MAP_BMSTART + wrd]) | bits[bit]); + data->bmdirty = 1; + return 1; +} + +/*++ + * b i t m a p G e t W o r d + * + * Get the bitmap word which contains the bit associated with a specified + * block. + * + * Inputs: + * + * mount - pointer to a mounted file system descriptor + * block - block # associated with the bitmap word + * value - bitmap word is returned here + * + * Outputs: + * + * None + * + * Returns: + * + * 1 if successful, 0 otherwise + * + --*/ +static int bitmapGetWord( + struct mountedFS *mount, + uint16_t block, + uint16_t *value +) +{ + struct DOS11data *data = &mount->dos11data; + uint16_t map = block / MAP_BLOCKS; + uint16_t offset = block % MAP_BLOCKS; + uint16_t wrd = offset / 16; + + if (bitmapLoad(mount, map) == 0) + return 0; + + *value = le16toh(data->bmbuf[MAP_BMSTART + wrd]); + return 1; +} + +/*++ + * d o s 1 1 C r e a t e U F D + * + * Create a UFD. + * + * Inputs: + * + * mount - pointer to a mounted file system descriptor + * group - group number + * user - user number + * + * Outputs: + * + * The mount specific buffer will be modified. + * + * Returns: + * + * 1 if UFD alread exists or successfully created, 0 otherwise + * + --*/ +static int dos11CreateUFD( + struct mountedFS *mount, + uint8_t group, + uint8_t user +) +{ + struct DOS11data *data = &mount->dos11data; + uint16_t mfdblk, mfdsav, uic = (group << 8) | user; + unsigned int i; + + if (dos11ReadBlock(mount, MFD1_BLOCK, NULL) == 0) + return 0; + + /* + * Search the MFD to see if the UFD already exists + */ + mfdblk = mfdsav = le16toh(data->buf[MFD1_MFD2BLOCK]); + do { + if (dos11ReadBlock(mount, mfdblk, NULL) == 0) + return 0; + + for (i = MFD2_HEADER; i < EODIR(mount, MFD2_SIZE); i += MFD2_SIZE) { + if (le16toh(data->buf[i + MFD2_UFDUIC]) != 0) + if (le16toh(data->buf[i + MFD2_UFDUIC] == uic)) + return 1; + } + mfdblk = le16toh(data->buf[MFD2_LINK]); + } while (mfdblk != 0); + + /* + * The UFD does not currently exist, create a new one without a data + * block assigned. The first file creation operation will allocate a + * data block. + */ + mfdblk = mfdsav; + for (;;) { + if (dos11ReadBlock(mount, mfdblk, NULL) == 0) + return 0; + + for (i = MFD2_HEADER; i < EODIR(mount, MFD2_SIZE); i += MFD2_SIZE) { + if (le16toh(data->buf[i + MFD2_UFDUIC]) == 0) { + data->buf[i + MFD2_UFDUIC] = htole16(uic); + data->buf[i + MFD2_UFDSTART] = 0; + data->buf[i + MFD2_UFDSIZE] = htole16(UFD_LEN); + data->buf[i + MFD2_UFDZERO] = 0; + + dos11WriteBlock(mount, mfdblk, NULL); + return 1; + } + } + + if (le16toh(data->buf[MFD2_LINK]) == 0) { + /* + * We have reached the end of the MFD, append a new block if available. + */ + uint16_t buf[512], newblk; + + if ((newblk = bitmapAllocBlock(mount)) == 0) { + ERROR("No space available to extend MFD on \"%s:\"\n", mount->name); + return 0; + } + + /* + * The newly created block consists only of unused entries. + */ + memset(buf, 0, sizeof(buf)); + if (dos11WriteBlock(mount, newblk, buf) == 0) + return 0; + + data->buf[MFD2_LINK] = htole16(newblk); + + if (dos11WriteBlock(mount, mfdblk, NULL) == 0) + return 0; + } + mfdblk = le16toh(data->buf[MFD2_LINK]); + } + return 0; +} + +/*++ + * d o s 1 1 D i s p l a y D i r + * + * Print a directory entry on stdout. + * + * Inputs: + * + * dir - pointer to the UFD entry for the file + * full - if non-zero, print a full directory entry + * + * Outputs: + * + * None + * + * Returns: + * + * None + * + --*/ +void dos11DisplayDir( + uint16_t *dir, + int full +) +{ + char temp[64], creation[16]; + + r50Asc(le16toh(dir[UFD_FILENAME]), &temp[0]); + r50Asc(le16toh(dir[UFD_FILENAME + 1]), &temp[3]); + if (le16toh(dir[UFD_EXTENSION]) != 0) { + temp[6] = '.'; + r50Asc(le16toh(dir[UFD_EXTENSION]), &temp[7]); + } else { + temp[6] = ' '; + temp[7] = ' '; + temp[8] = ' '; + temp[9] = ' '; + } + + if (full) { + sprintf(&temp[10], " %5u%c %s <%03o>", + le16toh(dir[UFD_FILELENGTH]), + (le16toh(dir[UFD_CREATION]) & UFD_TYPE) ? 'C' : ' ', + dos11Date(le16toh(dir[UFD_CREATION]) & UFD_DATE, creation), + le16toh(dir[UFD_LUP]) & UFD_PROT); + } else temp[10] ='\0'; + puts(temp); +} + +/*++ + * d o s 1 1 C r e a t e F i l e + * + * Create a new file within the DOS-11 file system. The new file will be + * marked as "locked" since the resources allocated to the file may be + * in flux. The caller must check that the file does not already exist + * before calling this routine. + * + * Inputs: + * + * mount - pointer to a mounted file system descriptor + * spec - pointer to the file specification block + * file - pointer to open file descriptor to receive results + * contig - # of contiguous blocks + * + * Outputs: + * + * The mount point specific buffer area will be modified. + * + * Returns: + * + * 1 if file successfully created, 0 otherwise + * + --*/ +int dos11CreateFile( + struct mountedFS *mount, + struct dos11FileSpec *spec, + struct dos11OpenFile *file, + unsigned long contig +) +{ + struct DOS11data *data = &mount->dos11data; + uint16_t mfdblk, ufdblk, ufdblk2; + unsigned int i, j; + struct tm tm; + time_t now = time(NULL); + uint16_t today; + + /* + * Compute a suitable year for file creation date. This year will have + * the same calendar as the current year but will be in the 20th century + * so that DOS/BATCH-11 will be able to enter the date successfully (It is + * not Y2K compliant!). + */ + localtime_r(&now, &tm); + tm.tm_year -= 28; + + today = ((tm.tm_year - 70) * 1000) + tm.tm_yday + 1; + + if (dos11ReadBlock(mount, MFD1_BLOCK, NULL) == 0) + return 0; + + /* + * Search the MFD for the specified UIC. + */ + mfdblk = le16toh(data->buf[MFD1_MFD2BLOCK]); + do { + if (dos11ReadBlock(mount, mfdblk, NULL) == 0) + return 0; + + for (i = MFD2_HEADER; i < EODIR(mount, MFD2_SIZE); i += MFD2_SIZE) { + uint16_t uic = le16toh(data->buf[i + MFD2_UFDUIC]); + uint16_t entrysz = le16toh(data->buf[i + MFD2_UFDSIZE]); + + if (uic != 0) { + if (uic == ((spec->group << 8) | spec->user)) { + ufdblk = le16toh(data->buf[i + MFD2_UFDSTART]); + + if (ufdblk == 0) { + if ((ufdblk = dos11XtndDirectory(mount)) == 0) { + ERROR("%s: Unable to create initial directory block\n", + mount->name); + return 0; + } + + data->buf[i + MFD2_UFDSTART] = htole16(ufdblk); + if (dos11WriteBlock(mount, mfdblk, NULL) == 0) + return 0; + } + for (;;) { + if (dos11ReadBlock(mount, ufdblk, NULL) == 0) + return 0; + + for (j = 1; j < EODIR(mount, entrysz); j += entrysz) { + if ((le16toh(data->buf[j + UFD_FILENAME]) == 0) && + (le16toh(data->buf[j + UFD_FILENAME + 1]) == 0) && + (le16toh(data->buf[j + UFD_EXTENSION]) == 0)) { + data->buf[j + UFD_FILENAME] = + file->name[0] = htole16(spec->name[0]); + data->buf[j + UFD_FILENAME + 1] = + file->name[1] = htole16(spec->name[1]); + data->buf[j + UFD_EXTENSION] = file->ext = htole16(spec->ext); + data->buf[j + UFD_CREATION] = + file->creation = + htole16(today | (SWISSET('c') ? UFD_TYPECONTIGUOUS : 0)); + data->buf[j + UFD_NEXTFREEBYTE] = file->nfb = 0; + data->buf[j + UFD_FILESTART] = file->start = 0; + data->buf[j + UFD_FILELENGTH] = file->length = 0; + data->buf[j + UFD_LASTBLOCKWRITTEN] = file->last = 0; + data->buf[j + UFD_LUP] = file->lup = htole16(UFD_LOCK + 0233); + + file->ufdblk = ufdblk; + file->ufdoffset = j; + + file-> mount = mount; + + if (SWISSET('c')) { + /* + * Allocate contiguous disk space for the file. + */ + uint16_t base = bitmapAllocContiguous(mount, contig); + + if (base == 0) { + ERROR("%s: Unable to allocate contiguous space\n", + mount->name); + return 0; + } + + data->buf[j + UFD_FILESTART] = + file->start = htole16(base); + data->buf[j + UFD_FILELENGTH] = + file->length = htole16(contig); + } + + /* + * Write the UFD block back to disk. The file will be marked + * as "locked" indicating that it is being written and the + * directory entry may not be accurate. + */ + if (dos11WriteBlock(mount, ufdblk, NULL) == 0) + return 0; + + return 1; + } + } + + if ((ufdblk = le16toh(data->buf[UFD_LINK])) == 0) { + if ((ufdblk2 = dos11XtndDirectory(mount)) == 0) { + ERROR("%s: Unable to extend UFD\n", mount->name); + return 0; + } + + data->buf[UFD_LINK] = htole16(ufdblk2); + if (dos11WriteBlock(mount, ufdblk, NULL) == 0) + return 0; + ufdblk = ufdblk2; + } + } + } + } + } + mfdblk = le16toh(data->buf[MFD2_LINK]); + } while (mfdblk != 0); + + return 0; +} + +/*++ + * d o s 1 1 L o o k u p F i l e + * + * Lookup a specific file within the DOS-11 file system. This routine + * fills in an open file descriptor with information about the file and + * the user directory it resides in. + * + * Inputs: + * + * mount - pointer to a mounted file system descriptor + * spec - pointer to the file specification block + * file - pointer to open file descriptor to receive results + * + * Outputs: + * + * The mount point specific buffer area will be modified. + * + * Returns: + * + * 1 if file found, 0 otherwise + * + --*/ +int dos11LookupFile( + struct mountedFS *mount, + struct dos11FileSpec *spec, + struct dos11OpenFile *file +) +{ + struct DOS11data *data = &mount->dos11data; + unsigned int mfdblk, ufdblk, i, j; + + if (dos11ReadBlock(mount, MFD1_BLOCK, NULL) == 0) + return 0; + + /* + * Search the MFD for the specified UIC. + */ + mfdblk = le16toh(data->buf[MFD1_MFD2BLOCK]); + do { + if (dos11ReadBlock(mount, mfdblk, NULL) == 0) + return 0; + + for (i = MFD2_HEADER; i < EODIR(mount, MFD2_SIZE); i += MFD2_SIZE) { + uint16_t uic = le16toh(data->buf[i + MFD2_UFDUIC]); + uint16_t entrysz = le16toh(data->buf[i + MFD2_UFDSIZE]); + + if (uic != 0) { + if (uic == ((spec->group << 8) | spec->user)) { + ufdblk = le16toh(data->buf[i + MFD2_UFDSTART]); + + if (ufdblk != 0) { + do { + if (dos11ReadBlock(mount, ufdblk, NULL) == 0) + return 0; + + for (j = UFD_HEADER; j < EODIR(mount, entrysz); j += entrysz) { + if ((le16toh(data->buf[j + UFD_FILENAME]) == 0) && + (le16toh(data->buf[j + UFD_FILENAME + 1]) == 0) && + (le16toh(data->buf[j + UFD_EXTENSION]) == 0)) + continue; + + if ((le16toh(data->buf[j + UFD_FILENAME]) != spec->name[0]) || + (le16toh(data->buf[j + UFD_FILENAME + 1]) != spec->name[1]) || + (le16toh(data->buf[j + UFD_EXTENSION]) != spec->ext)) + continue; + /* + * Save the directory entry and it's location in the open + * file descriptor. + */ + file->name[0] = data->buf[j + UFD_FILENAME]; + file->name[1] = data->buf[j + UFD_FILENAME + 1]; + file->ext = data->buf[j + UFD_EXTENSION]; + file->creation = data->buf[j + UFD_CREATION]; + file->nfb = data->buf[j + UFD_NEXTFREEBYTE]; + file->start = data->buf[j + UFD_FILESTART]; + file->length = data->buf[j + UFD_FILELENGTH]; + file->last = data->buf[j + UFD_LASTBLOCKWRITTEN]; + file->lup = data->buf[j + UFD_LUP]; + + file->ufdblk = ufdblk; + file->ufdoffset = j; + + file->mount = mount; + return 1; + } + ufdblk = le16toh(data->buf[UFD_LINK]); + } while (ufdblk != 0); + return 0; + } + } + } + } + mfdblk = le16toh(data->buf[MFD2_LINK]); + } while (mfdblk != 0); + + return 0; +} + +/*++ + * d o s 1 1 U p d a t e F i l e + * + * Update a DOS-11 file by writing back the UFD entry associated with the + * file. + * + * Inputs: + * + * mount - pointer to a mounted file system descriptor + * file - pointer to open file descriptor + * + * Outputs: + * + * The mount point specific buffer area will be modified. + * + * Returns: + * + * None + * + --*/ +void dos11UpdateFile( + struct mountedFS *mount, + struct dos11OpenFile *file +) +{ + struct DOS11data *data = &mount->dos11data; + + if (dos11ReadBlock(mount, file->ufdblk, NULL) == 0) + return; + + /* + * Update the directory entry. + */ + data->buf[file->ufdoffset + UFD_FILENAME] = file->name[0]; + data->buf[file->ufdoffset + UFD_FILENAME + 1] = file->name[1]; + data->buf[file->ufdoffset + UFD_EXTENSION] = file->ext; + data->buf[file->ufdoffset + UFD_CREATION] = file->creation; + data->buf[file->ufdoffset + UFD_NEXTFREEBYTE] = file->nfb; + data->buf[file->ufdoffset + UFD_FILESTART] = file->start; + data->buf[file->ufdoffset + UFD_FILELENGTH] = file->length; + data->buf[file->ufdoffset + UFD_LASTBLOCKWRITTEN] = file->last; + data->buf[file->ufdoffset + UFD_LUP] = file->lup & ~htole16(UFD_LOCK);; + +#ifdef DEBUG + if ((mount->flags & FS_DEBUG) != 0) { + if (file->ufdoffset != 1) + dos11DisplayDir(&data->buf[file->ufdoffset - UFD_LEN], 1); + dos11DisplayDir(&data->buf[file->ufdoffset], 1); + } +#endif + + dos11WriteBlock(mount, file->ufdblk, NULL); +} + +/*++ + * d o s 1 1 X t n d D i r e c t o r y + * + * Extend a directory by one block. The new directory block will be zeroed + * which indicates that all directory entries are empty and this is the last + * block in the directory chain. + * + * Inputs: + * + * mount - pointer to a mounted file system descriptor + * + * Outputs: + * + * None + * + * Returns: + * + * Block # allocated and written for the directory, 0 if failure + * + --*/ +uint16_t dos11XtndDirectory( + struct mountedFS *mount +) +{ + uint16_t buf[512], ufdblk; + + memset(buf, 0, sizeof(buf)); + + if ((ufdblk = bitmapAllocBlock(mount)) != 0) { + if (dos11WriteBlock(mount, ufdblk, buf) == 0) + return 0; + + if (bitmapFlush(mount) == 0) + return 0; + } + return ufdblk; +} + +/*++ + * d o s 1 1 P a r s e F i l e s p e c + * + * Parse a character string representing a DOS-11 file specification. + * + * Inputs: + * + * ptr - pointer to the file specification string + * spec - pointer to the file specification block + * user - default user number + * group - default group number + * wildcard - wildcard processing options: + * 0 (DOS11_M_NONE) - wildcards not allowed + * 1 (DOS11_M_ALLOW) - wildcards allowed + * 2 (DOS11_M_NONAME) - wildcards allowed + * if filename + ext not + * present default to *.* + * + * Outputs: + * + * The file specification block will be filled with the file information. + * + * Returns: + * + * 1 if parse is successful, 0 otherwise + * + --*/ +#define P_DONE 0 +#define P_NAME 1 +#define P_EXT 2 +#define P_GROUP 3 +#define P_USER 4 + +int dos11ParseFilespec( + char *ptr, + struct dos11FileSpec *spec, + unsigned char user, + unsigned char group, + int wildcard +) +{ + char term[] = { '\0', '.', '[', ',', ']' }; + char flags[] = + { 0, DOS11_WC_NAME, DOS11_WC_EXT, DOS11_WC_GROUP, DOS11_WC_USER }; + int state = P_NAME; + unsigned int uic, i; + char filename[6], ext[3]; + + memset(spec, 0, sizeof(struct dos11FileSpec)); + spec->user = user; + spec->group = group; + + memset(&filename, ' ', sizeof(filename)); + memset(&ext, ' ', sizeof(ext)); + + if (wildcard == DOS11_M_NONAME) + if ((*ptr == '\0') || (*ptr == '[')) + spec->flags = DOS11_WC_NAME | DOS11_WC_EXT; + + while (state != P_DONE) { + if (wildcard) { + if (*ptr == '*') { + spec->flags |= flags[state]; + ptr++; + if (*ptr == '\0') + state = P_DONE; + else if (*ptr == term[state]) { + if (state == P_USER) + state = P_DONE; + else state++; + } else return 0; + ptr++; + continue; + } + } + + i = 0; + + switch (state) { + case P_NAME: + while ((*ptr != '\0') && + (*ptr != '.') && + (strchr(rad50, toupper(*ptr)) != NULL) && + (i < sizeof(filename))) + filename[i++] = toupper(*ptr++); + + switch (*ptr++) { + case '\0': + state = P_DONE; + break; + + case '.': + state = P_EXT; + break; + + case '[': + state = P_GROUP; + break; + + default: + return 0; + } + break; + + case P_EXT: + while ((*ptr != '\0') && + (strchr(rad50, toupper(*ptr)) != NULL) && + (i < sizeof(ext))) + ext[i++] = toupper(*ptr++); + + switch (*ptr++) { + case '\0': + state = P_DONE; + break; + + case '[': + state = P_GROUP; + break; + + default: + return 0; + } + break; + + case P_GROUP: + uic = 0; + while ((strchr("01234567", *ptr) != NULL) && (i < 3)) { + uic = (uic << 3) | (*ptr++ - '0'); + i++; + } + if ((uic == 0) || (uic > 0377)) + return 0; + spec->group = uic & 0377; + + if (*ptr++ != ',') + return 0; + + state = P_USER; + break; + + case P_USER: + uic = 0; + while ((strchr("01234567", *ptr) != NULL) && (i < 3)) { + uic = (uic << 3) | (*ptr++ - '0'); + i++; + } + if ((uic == 0) || (uic > 0377)) + return 0; + spec->user = uic & 0377; + + if (*ptr++ != ']') + return 0; + + state = P_DONE; + break; + } + } + + spec->name[0] = ascR50(&filename[0]); + spec->name[1] = ascR50(&filename[3]); + spec->ext = ascR50(&ext[0]); + return 1; +} + +/*++ + * d o s 1 1 R e a d B l o c k + * + * Read a block from a DOS-11 file system. + * + * Inputs: + * + * mount - pointer to a mounted file system descriptor + * block - logical block # in the range 0 - N + * buf - buffer to receive data, if NULL use the mount + * point specific buffer + * + * Outputs: + * + * The block will be read into the specified buffer + * + * Returns: + * + * 1 if successful, 0 otherwise + * + --*/ +int dos11ReadBlock( + struct mountedFS *mount, + unsigned int block, + void *buf +) +{ + struct DOS11data *data = &mount->dos11data; + void *buffer = buf == NULL ? data->buf : buf; + int status; + + if (block >= data->blocks) { + ERROR("Attempt to read block (%u) outside file system \"%s\"\n", + block, mount->name); + return 0; + } + +#ifdef DEBUG + if ((mount->flags & FS_DEBUG) != 0) + fprintf(DEBUGout, ">> %s: (dos11) Reading logical block %o\n", + mount->name, block); +#endif + + status = FSioReadBlock(mount, block, buffer); + + if (status == 0) + ERROR("I/O error on \"%s\"\n", mount->name); + + return status; +} + +/*++ + * d o s 1 1 W r i t e B l o c k + * + * Write a block to a DOS-11 file system. + * + * Inputs: + * + * mount - pointer to a mounted file system descriptor + * block - logical block # in the range 0 - N + * buf - buffer to containing data, if NULL use the mount + * point specific buffer + * + * Outputs: + * + * None + * + * Returns: + * + * 1 if successful, 0 otherwise + * + --*/ +int dos11WriteBlock( + struct mountedFS *mount, + unsigned int block, + void *buf +) +{ + struct DOS11data *data = &mount->dos11data; + void *buffer = buf == NULL ? data->buf : buf; + int status; + + if (block >= data->blocks) { + ERROR("Attempt to write block (%u) outside file system \"%s\"\n", + block, mount->name); + return 0; + } + +#ifdef DEBUG + if ((mount->flags & FS_DEBUG) != 0) + fprintf(DEBUGout, ">> %s: (dos11) Writing logical block %o\n", + mount->name, block); +#endif + + status = FSioWriteBlock(mount, block, buffer); + + if (status == 0) + ERROR("I/O error on \"%s\"\n", mount->name); + + return status; +} + +/*++ + * d o s 1 1 R e a d B y t e s + * + * Read a sequence of bytes from an open file. The file may be either + * "Linked" or "Contiguous". + * + * Inputs: + * + * file - pointer to an open file descriptor + * buf - pointer to a buffer to receive the data + * len - # of bytes of data to read + * + * Outputs: + * + * The buffer will be overwritten by up to "len" bytes + * + * Returns: + * + * # of bytes read from the file (may be less than "len"), 0 if EOF + * + --*/ +int dos11ReadBytes( + struct dos11OpenFile *file, + char *buf, + int len +) +{ + int count = 0; + + if (file->current == 0) { + file->current = le16toh(file->start); + + if (dos11ReadBlock(file->mount, file->current, file->buffer) == 0) + return 0; + + file->nab = (le16toh(file->creation) & UFD_TYPE) == 0 ? 2 : 0; + file->eob = file->current == le16toh(file->last) ? + le16toh(file->nfb) : file->mount->blocksz; + } + + while (len) { + if (file->nab == file->eob) { + if (file->current == le16toh(file->last)) + break; + + if ((le16toh(file->creation) & UFD_TYPE) == 0) + file->current = le16toh(*((uint16_t *)(file->buffer))); + else file->current++; + + if (dos11ReadBlock(file->mount, file->current, file->buffer) == 0) + return 0; + + file->nab = (le16toh(file->creation) & UFD_TYPE) == 0 ? 2 : 0; + file->eob = file->current == le16toh(file->last) ? + le16toh(file->nfb) : file->mount->blocksz; + continue; + } + *buf++ = file->buffer[file->nab++]; + len--; + count++; + } + return count; +} + +/*++ + * d o s 1 1 W r i t e B y t e s + * + * Write a sequence of bytes to an open file. The file may be either + * "Linked" or "Contiguous". Linked files will be automatically extended + * as new data is written. + * + * Inputs: + * + * file - pointer to an open file descriptor + * buf - pointer to a buffer to receive the data + * len - # of bytes of data to read + * + * Outputs: + * + * None + * + * Returns: + * + * # of bytes written to the file (may be less than "len"), 0 if error + * + --*/ +int dos11WriteBytes( + struct dos11OpenFile *file, + char *buf, + int len +) +{ + int count = 0; + uint16_t next; + + if (file->current == 0) { + file->eob = file->mount->blocksz; + + if ((le16toh(file->creation) & UFD_TYPE) == 0) { + /* + * Linked file - allocate initial block + */ + if ((next = bitmapAllocBlock(file->mount)) == 0) { + ERROR("Free disk space exhausted\n"); + return 0; + } + file->current = next; + file->start = file->last = htole16(next); + file->length = htole16(1); + *((uint16_t *)(file->buffer)) = 0; + file->nab = 2; + } else { + file->current = le16toh(file->start); + file->last = file->start; + file->nab = 0; + } + } + + while (len) { + if (file->nab == file->eob) { + if ((le16toh(file->creation) & UFD_TYPE) == 0) { + /* + * Linked file - extend it by 1 block + */ + if ((next = bitmapAllocBlock(file->mount)) == 0) { + ERROR("Free disk space exhausted extending file\n"); + return count; + } + *((uint16_t *)(file->buffer)) = htole16(next); + + if (dos11WriteBlock(file->mount, file->current, file->buffer) == 0) + return 0; + + file->current = next; + file->last = htole16(next); + file->length = htole16(le16toh(file->length) + 1); + file->nab = 2; + } else { + if (dos11WriteBlock(file->mount, file->current, file->buffer) == 0) + return 0; + + if (++file->current == (le16toh(file->last) + le16toh(file->length))) { + ERROR("Contiguous disk space allocation exceeded\n"); + return 0; + } + file->nab = 0; + file->last = htole16(file->current); + } + } + + file->buffer[file->nab++] = *buf++; + len--; + count++; + } + return count; +} + +/*++ + * d o s 1 1 M o u n t + * + * Verify that the open container file is a DOS-11 file system. We check that + * we can read all MFD entries and the UFD entries have the correct size + * (9 words). We also verify that we can read all bitmaps and they have the + * correct size (60 words). + * + * Inputs: + * + * mount - pointer to a mounted file system descriptor + * (not in the mounted file system list) + * + * Outputs: + * + * None + * + * Returns: + * + * 1 if a valid DOS-11 file system, 0 otherwise + * + --*/ +static int dos11Mount( + struct mountedFS *mount +) +{ + struct DOS11data *data = &mount->dos11data; + struct stat stat; + uint16_t mfdblk, mapblk, interleave; + unsigned int i, freeblocks = 0; + + if (fstat(fileno(mount->container), &stat) == 0) { + if (stat.st_blocks < DISKSIZE_RK05) + mount->blocksz = BLOCKSIZE_RF11 * 2; + if (stat.st_blocks > DISKSIZE_RK05) + mount->blocksz = BLOCKSIZE_RP03 * 2; + + data->blocks = stat.st_size / mount->blocksz; + + /* + * Build a list of the bitmap blocks. + */ + if (dos11ReadBlock(mount, MFD1_BLOCK, NULL) == 0) + return 0; + + mfdblk = le16toh(data->buf[MFD1_MFD2BLOCK]); + interleave = le16toh(data->buf[MFD1_INTERLEAVE]); + + data->bitmaps = 0; + mapblk = le16toh(data->buf[MFD1_BMSTART]); + + do { + if (dos11ReadBlock(mount, mapblk, NULL) == 0) + return 0; + + if (le16toh(data->buf[MAP_WORDS]) != MAP_LEN) { + ERROR("mount: wrong bitmap size (%d) for bitmap %d\n", + le16toh(data->buf[MAP_WORDS]), + data->bitmaps + 1); + return 0; + } + + /* + * Compute the # of free blocks in the bitmap. + */ + for (i = 0; i < MAP_LEN; i++) { + uint16_t bmentry = le16toh(data->buf[i + MAP_BMSTART]); + + if (bmentry != 0177777) { + freeblocks += zeroes[(bmentry >> 8) & 0377]; + freeblocks += zeroes[bmentry & 0377]; + } + } + data->bmblk[data->bitmaps++] = mapblk; + + mapblk = le16toh(data->buf[MAP_LINK]); + } while (mapblk != 0); + + do { + if (dos11ReadBlock(mount, mfdblk, NULL) == 0) + return 0; + + for (i = MFD2_HEADER; i < EODIR(mount, MFD2_SIZE); i += MFD2_SIZE) + if (le16toh(data->buf[i + MFD2_UFDUIC]) != 0) + if (le16toh(data->buf[i + MFD2_UFDSIZE]) != UFD_LEN) { + ERROR("mount: wrong directory size (%d) for [%3o,%3o]\n", + le16toh(data->buf[i + MFD2_UFDSIZE]), + (le16toh(data->buf[i + MFD2_UFDUIC]) >> 8) & 0377, + le16toh(data->buf[i + MFD2_UFDUIC]) & 0377); + return 0; + } + mfdblk = le16toh(data->buf[MFD2_LINK]); + } while (mfdblk != 0); + + /* + * Preload bitmap 0 + */ + data->bmindex = 0; + data->bmscan = 0; + data->bmdirty = 0; + + if (dos11ReadBlock(mount, data->bmblk[0], data->bmbuf) == 0) + return 0; + + if (!quiet) { + printf("%s: successfully mounted\n", mount->name); + printf("Total blocks: %d, Free blocks: %d, Interleave: %d\n", + data->blocks, freeblocks, interleave); + } + + /* + * Set up default parameters + */ + data->group = 1; + data->user = 1; + return 1; + } + return 0; +} + +/*++ + * d o s 1 1 U m o u n t + * + * Unmount the DOS-11 file system, releasing any storage allocated. + * + * Inputs: + * + * mount - pointer to the mounted file system descriptor + * + * Outputs: + * + * None + * + * Returns: + * + * None + * + --*/ +static void dos11Umount( + struct mountedFS *UNUSED(mount) +) +{ +} + +/*++ + * d o s 1 1 S i z e + * + * Return the size of a DOS-11 container file (RK05). + * + * Inputs: + * + * None + * + * Outputs: + * + * None + * + * Returns: + * + * Default size of the container file in bytes (RK05). + * + --*/ +static size_t dos11Size(void) +{ + return DISKSIZE_RK05 * BLOCKSIZE_RK11; +} + +/*++ + * d o s 1 1 N e w f s + * + * Create an empty DOS11 file system. + * + * Inputs: + * + * mount - pointer to a mounted file system descriptor + * (not in the mounted file system list) + * size - the size (in blocks) of the file system + * + * Outputs: + * + * None + * + * Returns: + * + * 1 if the file system was successfully created, 0 otherwise + * + --*/ +static int dos11Newfs( + struct mountedFS *mount, + size_t size +) +{ + struct DOS11data *data = &mount->dos11data; + int i; + +#define MFD2_BLOCK 2 +#define MAP_BLOCK 3 + + memset(data, 0, sizeof(*data)); + + data->blocks = size / BLOCKSIZE_RK11; + data->bitmaps = 5; + data->bmblk[0] = MAP_BLOCK; + data->bmblk[1] = MAP_BLOCK + 1; + data->bmblk[2] = MAP_BLOCK + 2; + data->bmblk[3] = MAP_BLOCK + 3; + data->bmblk[4] = MAP_BLOCK + 4; + data->bmindex = 0177777; + + /* + * Build and write MFD Block #1: + * + * - second MFD block is at block 2 + * - 5 bitmaps starting at block (filesys->blocks - 5). + */ + memset(data->buf, 0, mount->blocksz); + data->buf[MFD1_MFD2BLOCK] = htole16(MFD2_BLOCK); + data->buf[MFD1_INTERLEAVE] = htole16(1); + data->buf[MFD1_BMSTART] = htole16(data->bmblk[0]); + data->buf[MFD1_BMSTART + 1] = htole16(data->bmblk[1]); + data->buf[MFD1_BMSTART + 2] = htole16(data->bmblk[2]); + data->buf[MFD1_BMSTART + 3] = htole16(data->bmblk[3]); + data->buf[MFD1_BMSTART + 4] = htole16(data->bmblk[4]); + data->buf[MFD1_BMSTART + 5] = htole16(0); + + if (dos11WriteBlock(mount, MFD1_BLOCK, NULL) == 0) + return 0; + + /* + * Build and write MFD Block #2: + * + * - no UFDs present + */ + memset(data->buf, 0, mount->blocksz); + + if (dos11WriteBlock(mount, MFD2_BLOCK, NULL) == 0) + return 0; + + /* + * Build and write 5 bitmap blocks. + */ + memset(data->buf, 0, mount->blocksz); + + for (i = 1; i < 6; i++) { + data->buf[MAP_LINK] = + i == 5 ? 0 : htole16(data->bmblk[i]); + data->buf[MAP_MAP] = htole16(i); + data->buf[MAP_WORDS] = htole16(MAP_LEN); + data->buf[MAP_FIRST] = htole16(MAP_BLOCK); + + if (dos11WriteBlock(mount, data->bmblk[i - 1], NULL) == 0) + return 0; + } + + /* + * Reserve used blocks in the bitmap(s). + */ + if (bitmapSetBit(mount, BOOT_BLOCK) == 0) + return 0; + if (bitmapSetBit(mount, MFD1_BLOCK) == 0) + return 0; + if (bitmapSetBit(mount, MFD2_BLOCK) == 0) + return 0; + + for (i = 1; i < 6; i++) + if (bitmapSetBit(mount, MAP_BLOCK + i - 1) == 0) + return 0; + + /* + * Reserve all blocks past the end of the disk. + */ + for (i = 4800; i < (5 * MAP_BLOCKS); i++) + if (bitmapSetBit(mount, i) == 0) + return 0; + + return bitmapFlush(mount); +} + +/*++ + * d o s 1 1 S e t + * + * Set mount point specific values. + * + * Inputs: + * + * mount - pointer to the mounted file system descriptor + * unit - device unit number (unused) + * present - device unit number present (unused) + * + * Outputs: + * + * None + * + * Returns: + * + * None + * + --*/ +static void dos11Set( + struct mountedFS *mount, + uint8_t UNUSED(unit), + uint8_t UNUSED(present) +) +{ + struct DOS11data *data = &mount->dos11data; + int idx = 0; + uint8_t user, group; + + while (setCmds[idx] != NULL) { + if (strcmp(words[1], setCmds[idx]) == 0) { + switch (idx) { + case DOS11SET_UIC: + if (args == 3) { + if (sscanf(words[2], "[%hho,%hho]", &group, &user) == 2) { + data->group = group; + data->user = user; + } else fprintf(stderr, + "dos11: UIC syntax error \"%s\"\n", words[2]); + } else fprintf(stderr, "dos11: Invalid syntax for \"set uic\"\n"); + return; + + case DOS11SET_UFD: + if (args == 3) { + if (sscanf(words[2], "[%hho,%hho]", &group, &user) == 2) { + if (dos11CreateUFD(mount, group, user) != 0) { + data->group = group; + data->user = user; + } + } else fprintf(stderr, + "dos11: UIC syntax error \"%s\"\n", words[2]); + } else fprintf(stderr, "dos11: Invalid syntax for \"set ufd\"\n"); + return; + + default: + fprintf(stderr, "dos11: \"%s\" not implemented\n", words[1]); + return; + } + } + idx++; + } + fprintf(stderr, "dos11: Unknown set command \"%s\"\n", words[1]); +} + +/*++ + * d o s 1 1 I n f o + * + * Display infomation about the internal structure of the DOS-11 file system. + * This functions generates a display similar to the output provided by the + * LIST operation of DOS/BATCH-11 V09.20C + * + * Inputs: + * + * mount - pointer to the mounted file system descriptor + * unit - device unit number (unused) + * present - device unit number present (unused) + * + * Outputs: + * + * None + * + * Returns: + * + * None + * + --*/ +static void dos11Info( + struct mountedFS *mount, + uint8_t UNUSED(unit), + uint8_t UNUSED(present) +) +{ + struct DOS11data *data = &mount->dos11data; + struct tm tm; + char datetime[32], temp[32]; + time_t now = time(NULL); + unsigned int mfd2blk, mfdblk, ufdblk, mapblk, i, j; + uint16_t buf[512]; + + localtime_r(&now, &tm); + strftime(datetime, sizeof(datetime), "%d-%b-%C at %I:%M:%S", &tm); + + printf("* * * * * * Listing of MFD for %s * * * * * * on %s\n", + mount->name, datetime); + printf(" UIC First UFD Block UFD Entry Size\n\n"); + + if (dos11ReadBlock(mount, MFD1_BLOCK, NULL) == 0) + return; + + /* + * Get some useful block addresses + */ + mfd2blk = le16toh(data->buf[MFD1_MFD2BLOCK]); + mapblk = le16toh(data->buf[MFD1_BMSTART]); + + /* + * Display the MFD entries + */ + mfdblk = mfd2blk; + do { + if (dos11ReadBlock(mount, mfdblk, NULL) == 0) + return; + + for (i = MFD2_HEADER; i < EODIR(mount, MFD2_SIZE); i += MFD2_SIZE) + if (le16toh(data->buf[i + MFD2_UFDUIC]) != 0) { + printf("[%3o,%3o] %5o %5d.\n", + (le16toh(data->buf[i + MFD2_UFDUIC]) >> 8) & 0377, + le16toh(data->buf[i + MFD2_UFDUIC]) & 0377, + le16toh(data->buf[i + MFD2_UFDSTART]), + le16toh(data->buf[i + MFD2_UFDSIZE])); + } + mfdblk = le16toh(data->buf[MFD2_LINK]); + } while (mfdblk != 0); + + printf("\n"); + + /* + * Display the individual UFD entries + */ + mfdblk = mfd2blk; + do { + if (dos11ReadBlock(mount, mfdblk, NULL) == 0) + return; + + for (i = MFD2_HEADER; i < EODIR(mount, MFD2_SIZE); i += MFD2_SIZE) + if (le16toh(data->buf[i + MFD2_UFDUIC]) != 0) { + unsigned int entrysz = le16toh(data->buf[i + MFD2_UFDSIZE]); + + printf("* * * Listing of [%3o,%3o] User Directory * * *\n\n", + (le16toh(data->buf[i + MFD2_UFDUIC]) >> 8) & 0377, + le16toh(data->buf[i + MFD2_UFDUIC]) & 0377); + printf(" File Ext Date Type Usage Lock Start Length End Prot EBP\n\n"); + + if ((ufdblk = le16toh(data->buf[i + MFD2_UFDSTART])) != 0) { + do { + if (dos11ReadBlock(mount, ufdblk, buf) == 0) + return; + + for (j = UFD_HEADER; j < EODIR(mount, entrysz); j += entrysz) { + /* + * Skip over deleted files + */ + if ((le16toh(buf[j + UFD_FILENAME]) == 0) && + (le16toh(buf[j + UFD_FILENAME + 1]) == 0) && + (le16toh(buf[j + UFD_EXTENSION]) == 0)) + continue; + + r50Asc(le16toh(buf[j + UFD_FILENAME]), &temp[0]); + r50Asc(le16toh(buf[j + UFD_FILENAME + 1]), &temp[3]); + temp[6] = '.'; + r50Asc(le16toh(buf[j + UFD_EXTENSION]), &temp[7]); + temp[10] = ' '; + temp[11] = ' '; + dos11Date(le16toh(buf[j + UFD_CREATION]) & UFD_DATE, &temp[12]); + temp[25] = '\0'; + + printf("%s %c %3o %c %6o %5u. %6o %3o %6o\n", + temp, + (le16toh(buf[j + UFD_CREATION]) & UFD_TYPE) ? 'C' : 'L', + (le16toh(buf[j + UFD_LUP]) & UFD_USAGE) >> 8, + (le16toh(buf[j + UFD_LUP]) & UFD_LOCK) ? '1' : '0', + le16toh(buf[j + UFD_FILESTART]), + le16toh(buf[j + UFD_FILELENGTH]), + le16toh(buf[j + UFD_LASTBLOCKWRITTEN]), + le16toh(buf[j + UFD_LUP]) & UFD_PROT, + le16toh(buf[j + UFD_NEXTFREEBYTE])); + } + ufdblk = le16toh(buf[UFD_LINK]); + } while (ufdblk != 0); + printf("\n"); + } + } + mfdblk = le16toh(data->buf[MFD2_LINK]); + } while (mfdblk != 0); + + /* + * Display bitmap information + */ + printf("* * * * * * Map Verification * * * * * *\n\n"); + do { + if (dos11ReadBlock(mount, mapblk, buf) == 0) + return; + + printf("* * * * * * Map Header Information * * * * * *\n"); + printf("Link = %6o\n", le16toh(buf[MAP_LINK])); + printf("Map Number = %2u.\n", le16toh(buf[MAP_MAP])); + printf("Words In Map = %3u.\n", le16toh(buf[MAP_WORDS])); + printf("Link To First Map = %6o\n\n", le16toh(buf[MAP_FIRST])); + + for (i = 0; i < le16toh(buf[MAP_WORDS]); i++) + printf(" %6o%c", + le16toh(buf[MAP_BMSTART + i]), + ((i + 1) % 8) == 0 ? '\n' : ' '); + printf("\n\n"); + + mapblk = le16toh(buf[MAP_LINK]); + } while (mapblk != 0); +} + +/*++ + * d o s 1 1 D i r + * + * Produce a full or brief directory listing. + * + * Inputs: + * + * mount - pointer to the mounted file system descriptor + * unit - device unit number (unused) + * fname - pointer to filename string + * + * Outputs: + * + * None + * + * Returns: + * + * None + * + --*/ +static void dos11Dir( + struct mountedFS *mount, + uint8_t UNUSED(unit), + char *fname +) +{ + struct DOS11data *data = &mount->dos11data; + uint8_t user = data->user; + uint8_t group = data->group; + struct dos11FileSpec spec; + unsigned int mfdblk, ufdblk, i, j; + uint16_t buf[512]; + uint8_t found = 0; + + if (dos11ParseFilespec(fname, &spec, user, group, DOS11_M_NONAME) == 0) { + fprintf(stderr, "dir: syntax error in file spec \"%s\"\n", fname); + return; + } + + if (dos11ReadBlock(mount, MFD1_BLOCK, NULL) == 0) + return; + + /* + * Search the MFD for matching UIC entries. + */ + mfdblk = le16toh(data->buf[MFD1_MFD2BLOCK]); + do { + if (dos11ReadBlock(mount, mfdblk, NULL) == 0) + return; + + for (i = MFD2_HEADER; i < EODIR(mount, MFD2_SIZE); i += MFD2_SIZE) { + uint16_t uic = le16toh(data->buf[i + MFD2_UFDUIC]); + uint16_t entrysz = le16toh(data->buf[i + MFD2_UFDSIZE]); + + if (uic == 0) + continue; + + if ((spec.flags & DOS11_WC_GROUP) == 0) + if (spec.group != ((uic >> 8) & 0377)) + continue; + + if ((spec.flags & DOS11_WC_USER) == 0) + if (spec.user != (uic & 0377)) + continue; + + found = 1; + + if ((ufdblk = le16toh(data->buf[i + MFD2_UFDSTART])) != 0) { + unsigned int header = 0; + + do { + if (dos11ReadBlock(mount, ufdblk, buf) == 0) + return; + + for (j = UFD_HEADER; j < EODIR(mount, entrysz); j += entrysz) { + if ((le16toh(buf[j + UFD_FILENAME]) == 0) && + (le16toh(buf[j + UFD_FILENAME + 1]) == 0) && + (le16toh(buf[j + UFD_EXTENSION]) == 0)) + continue; + + if ((spec.flags & DOS11_WC_NAME) == 0) + if ((le16toh(buf[j + UFD_FILENAME]) != spec.name[0]) || + (le16toh(buf[j + UFD_FILENAME + 1]) != spec.name[1])) + continue; + + if ((spec.flags & DOS11_WC_EXT) == 0) + if (le16toh(buf[j + UFD_EXTENSION]) != spec.ext) + continue; + + if (!header) { + printf("%s:[%u, %u]\n\n", + mount->name, (uic >> 8) & 0377, uic & 0377); + header = 1; + } + + dos11DisplayDir(&buf[j], SWISSET('f')); + } + ufdblk = le16toh(buf[UFD_LINK]); + } while (ufdblk != 0); + + if (header) + printf("\n"); + } + } + mfdblk = le16toh(data->buf[MFD2_LINK]); + } while (mfdblk != 0); + + if (!found && ((spec.flags & (DOS11_WC_GROUP | DOS11_WC_USER)) == 0)) + ERROR("dir: Directory [%o,%o] not found\n", spec.group, spec.user); +} + +/*++ + * d o s 1 1 O p e n F i l e R + * + * Open a DOS-11 file for reading. + * + * Inputs: + * + * mount - pointer to the mounted file system descriptor + * unit - device unit number (unused) + * fname - pointer to filename string + * + * Outputs: + * + * None + * + * Returns: + * + * Pointer to open file descriptor, NULL if open fails + * + --*/ +static void *dos11OpenFileR( + struct mountedFS *mount, + uint8_t UNUSED(unit), + char *fname +) +{ + struct DOS11data *data = &mount->dos11data; + struct dos11OpenFile *file; + struct dos11FileSpec spec; + uint8_t user = data->user; + uint8_t group = data->group; + + if (dos11ParseFilespec(fname, &spec, user, group, DOS11_M_NONE) == 0) { + fprintf(stderr, "Failed to parse filename \"%s\"\n", fname); + return NULL; + } + + if ((file = malloc(sizeof(struct dos11OpenFile))) != NULL) { + memset(file, 0, sizeof(struct dos11OpenFile)); + + if (dos11LookupFile(mount, &spec, file) != 0) { + /* + * Allocate local buffer space for the file. + */ + if ((file->buffer = malloc(mount->blocksz)) == NULL) { + free(file); + return NULL; + } + file->mode = M_RD; + } else { + free(file); + return NULL; + } + } + return file; +} + +/*++ + * d o s 1 1 O p e n F i l e W + * + * Open a DOS-11 file for writing. + * + * Inputs: + * + * mount - pointer to the mounted file system descriptor + * unit - device unit number (unused) + * fname - pointer to filename string + * size - estimated file size (in bytes) + * + * Outputs: + * + * None + * + * Returns: + * + * Pointer to open file descriptor, NULL if open fails + * + --*/ +static void *dos11OpenFileW( + struct mountedFS *mount, + uint8_t UNUSED(unit), + char *fname, + off_t size +) +{ + struct DOS11data *data = &mount->dos11data; + struct dos11OpenFile *file; + struct dos11FileSpec spec; + unsigned long contig = (size + mount->blocksz - 1) / mount->blocksz; + uint8_t user = data->user; + uint8_t group = data->group; + + if (SWISSET('c') && (contig == 0)) { + fprintf(stderr,"DOS-11 contiguous files must be at least 1 block\n"); + return NULL; + } + + if (dos11ParseFilespec(fname, &spec, user, group, DOS11_M_NONE) == 0) { + fprintf(stderr, "Failed to parse filename \"%s\"\n", fname); + return NULL; + } + + if ((file = malloc(sizeof(struct dos11OpenFile))) != NULL) { + memset(file, 0, sizeof(struct dos11OpenFile)); + + if (dos11LookupFile(mount, &spec, file) == 0) { + /* + * Allocate local bufferspace for the file. + */ + if ((file->buffer = malloc(mount->blocksz)) != NULL) { + if (dos11CreateFile(mount, &spec, file, contig) == 0) { + ERROR("Failed to create file \"%s\"\n", fname); + free(file->buffer); + free(file); + return NULL; + } else file->mode = M_WR; + } else { + ERROR("Buffer allocation failure for \"%s\"\n", fname); + free(file); + return NULL; + } + } else { + ERROR("File \"%s\" already exists\n", fname); + free(file); + return NULL; + } + } else fprintf(stderr, "Memory allocation failure\n"); + return file; +} + +/*++ + * d o s 1 1 F i l e S i z e + * + * Return an estimate of the size of an open file. This routine bases the + * size on the number of blocks allocated to the file. The size of "Linked" + * files will be over-reported by 2 bytes for each block allocated. + * + * Inputs: + * + * filep - pointer to open file descriptor + * + * Outputs: + * + * None + * + * Returns: + * + * Estimated size of the file + * + --*/ +static off_t dos11FileSize( + void *filep +) +{ + struct dos11OpenFile *file = filep; + struct mountedFS *mount = file->mount; + + return le16toh(file->length) * mount->blocksz; +} + +/*++ + * d o s 1 1 D e l e t e F i l e + * + * Delete a file which has been opened via dos11LookupFile(). + * + * Inputs: + * + * filep - pointer to open file descriptor + * fname - pointer to filename string + * + * Outputs: + * + * The mount point specific buffer will be modified. + * + * Returns: + * + * None + * + --*/ +static void dos11DeleteFile( + void *filep, + char *UNUSED(fname) +) +{ + struct dos11OpenFile *file = filep; + struct mountedFS *mount = file->mount; + struct DOS11data *data = &mount->dos11data; + uint16_t i, block; + + file->name[0] = 0; + file->name[1] = 0; + file->ext = 0; + + dos11UpdateFile(mount, file); + + /* + * The file has been removed from its UFD, release all disk blocks + * allocated to the file. + */ + block = le16toh(file->start); + + if ((le16toh(file->creation) & UFD_TYPE) != 0) { + /* + * Contiguous file + */ + for (i = 0; i < le16toh(file->length); i++) + if (bitmapReleaseBlock(mount, block + i) == 0) + return; + } else { + /* + * Linked file + */ + while (block != 0) { + if (dos11ReadBlock(mount, block, NULL) == 0) + return; + if (bitmapReleaseBlock(mount, block) == 0) + return; + + block = le16toh(data->buf[FILE_LINK]); + } + } + /* + * Make sure the bitmaps on disk are up to date. + */ + bitmapFlush(mount); + + dos11CloseFile(file); +} + +/*++ + * d o s 1 1 C l o s e F i l e + * + * Close an open DOS-11 file. + * + * Inputs: + * + * filep - pointer to open file descriptor + * + * Outputs: + * + * None + * + * Returns: + * + * None + * + --*/ +static void dos11CloseFile( + void *filep +) +{ + struct dos11OpenFile *file = filep; + struct mountedFS *mount = file->mount; + + if (file->mode == M_WR) { + uint16_t start = (le16toh(file->creation) & UFD_TYPE) == 0 ? 2 : 0; + + /* + * Flush any partial buffer, pending bitmap updates and update the + * directory entry. + */ + if (((le16toh(file->creation) & UFD_TYPE) == 0) || + (file->current == le16toh(file->last))) + file->nfb = htole16(file->nab); + else file->nfb = htole16(mount->blocksz); + + if (file->nab != start) + dos11WriteBlock(mount, file->current, file->buffer); + bitmapFlush(mount); + dos11UpdateFile(mount, file); + } + + if (file != NULL) { + if (file->buffer != NULL) + free(file->buffer); + free(file); + } +} + +/*++ + * d o s 1 1 R e a d F i l e + * + * Read data from a DOS-11 file to a supplied buffer. + * + * Inputs: + * + * filep - pointer to open file descriptor + * buf - pointer to buffer + * buflen - length of the supplied buffer + * + * Outputs: + * + * None + * + * Returns: + * + * # of bytes of data read, 0 means EOF or error + * + --*/ +static size_t dos11ReadFile( + void *filep, + void *buf, + size_t buflen +) +{ + struct dos11OpenFile *file = filep; + char *bufr = buf; + + if (SWISSET('a')) { + char ch; + size_t count = 0; + + /* + * Read a full or partial line from the open file. + */ + while ((buflen != 0) && (dos11ReadBytes(file, &ch, 1) == 1)) { + ch &= 0177; + bufr[count++] = ch; + buflen--; + if (ch == '\n') + break; + } + return count; + } + + return dos11ReadBytes(file, bufr, buflen); +} + +/*++ + * d o s 1 1 W r i t e F i l e + * + * Write data to a DOS-11 file from a supplied buffer. + * + * Inputs: + * + * filep - pointer to open file descriptor + * buf - pointer to buffer + * buflen - length of the supplied buffer + * + * Outputs: + * + * None + * + * Returns: + * + * # of bytes of data written, 0 means error + * + --*/ +size_t dos11WriteFile( + void *filep, + void *buf, + size_t buflen +) +{ + struct dos11OpenFile *file = filep; + char *bufw = buf; + + if (SWISSET('a')) { + size_t count = 0; + + /* + * Write a line, terminated by , to the open file. + */ + while (buflen != 0) { + char ch; + + ch = bufw[count++] & 0177; + buflen--; + if (dos11WriteBytes(file, &ch, 1) == 0) + break; + } + return count; + } + return dos11WriteBytes(file, bufw, buflen); +} + +/*++ + * d o s 1 1 F S + * + * Descriptor for accessing DOS-11 file systems. + * + --*/ +struct FSdef dos11FS = { + NULL, + "dos11", + "dos11 PDP-11 DOS/BATCH-11 file system (RF11, RK05 or RP03 disks)\n", + 0, + BLOCKSIZE_RK11 * 2, /* Default for RK05 */ + dos11Mount, + dos11Umount, + dos11Size, + dos11Newfs, + dos11Set, + dos11Info, + dos11Dir, + dos11OpenFileR, + dos11OpenFileW, + dos11FileSize, + dos11CloseFile, + dos11ReadFile, + dos11WriteFile, + dos11DeleteFile, + NULL, /* No tape support functions */ + NULL, + NULL, + NULL +}; diff --git a/converters/fsio/dos11.h b/converters/fsio/dos11.h new file mode 100644 index 0000000..79cdf02 --- /dev/null +++ b/converters/fsio/dos11.h @@ -0,0 +1,292 @@ +/* + * Copyright (C) 2018 John Forecast. All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY JOHN FORECAST "AS IS" AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#ifndef __DOS11_H__ +#define __DOS11_H__ + +/* + * General disk layout: + * + * Block + * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + * 0 | Reserved for Bootstrap | + * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + * 1 | MFD Block #1 | + * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + * 2 | UFD Block #1 | + * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + * ... | | + * | User linked files | + * | & | + * | other UFD blocks | + * | | + * |-- --| + * | User contiguous files | + * | | + * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + * X-n | MFD Block #2 | + * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + * X-n-1 | Bitmap Block #1 | + * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + * ... | | + * | | + * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + * X | Bitmap Block #n | + * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + * + * MFD Block 1: + * + * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + * | Block # of MFD Block 2 | + * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + * | Interleave factor | + * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + * | Bitmap start block # | + * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + * | Bitmap #1 block # | + * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + * | Bitmap #2 block # | + * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + * | | + * | ... | + * | | + * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + * | Bitmap #N block # | + * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + * | 0 | + * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + * | | + * + * MFD Block 2 - N: + * + * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + * | Link to next MFD block or 0 | + * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + * | Group code | User code | + * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + * | UFD start block # | + * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + * | # of words in UFD entry | + * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + * | 0 | + * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + * | Repeat above 4 words | + * | for each UFD | + * | | + * + * UFD Block: + * + * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + * | Link to next UFD block or 0 | + * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + * | File | + * +- -+ + * | name | + * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + * | Extension | + * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + * |Typ| Rsvd | Creation Date | + * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + * | Next free byte | + * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + * | Start block # | + * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + * | Length (in # of blocks) | + * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + * | Last block written | + * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + * |LCK| Usage count | Protection code | + * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + * + * Bitmap Block: + * + * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + * | Link to next bitmap block | + * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + * | Map # | + * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + * | # of words of map | + * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + * | Link to first bitmap block | + * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + * | Map for blocks 0 - 17 | + * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + * | Map for blocks 20 - 37 | + * | ... | + * | | + */ + +/* + * The MFD and UFD directories can be treated as an array of 16-bit values. + * The following offsets describe each structure. + */ +#define BOOT_BLOCK 0 /* Reserved for bootstrap */ +#define MFD1_BLOCK 1 /* MFD always at block 1*/ + +#define MFD1_MFD2BLOCK 0 +#define MFD1_INTERLEAVE 1 +#define MFD1_BMSTART 2 +#define MFD1_BM1 3 + +#define MFD2_LINK 0 +#define MFD2_HEADER 1 + +#define MFD2_UFDUIC 0 +#define MFD2_UFDSTART 1 +#define MFD2_UFDSIZE 2 +#define MFD2_UFDZERO 3 +#define MFD2_SIZE 4 + +#define UFD_LINK 0 +#define UFD_HEADER 1 + +#define UFD_FILENAME 0 +#define UFD_EXTENSION 2 +#define UFD_CREATION 3 +#define UFD_TYPE 0100000 +#define UFD_TYPELINKED 0000000 +#define UFD_TYPECONTIGUOUS 0100000 +#define UFD_DATE 0077777 +#define UFD_NEXTFREEBYTE 4 +#define UFD_FILESTART 5 +#define UFD_FILELENGTH 6 +#define UFD_LASTBLOCKWRITTEN 7 +#define UFD_LUP 8 +#define UFD_LOCK 0100000 +#define UFD_USAGE 0077400 +#define UFD_PROT 0000377 +#define UFD_LEN 9 /* 9 word in each entry */ + +#define MAP_LINK 0 +#define MAP_MAP 1 +#define MAP_WORDS 2 +#define MAP_FIRST 3 +#define MAP_BMSTART 4 +#define MAP_LEN 60 /* 60 words in each entry */ +#define MAP_BLOCKS (MAP_LEN * 16) + +#define FILE_LINK 0 + +/* + * Block sizes for each supported disk drive + */ +#define BLOCKSIZE_RC11 64 +#define BLOCKSIZE_RF11 64 +#define BLOCKSIZE_RK11 256 +#define BLOCKSIZE_RP03 512 + +/* + * Max # of UIC's (UFDs) for each supported disk drive + */ +#define UICCOUNT_RC11 15 +#define UICCOUNT_RF11 15 +#define UICCOUNT_RK11 63 +#define UICCOUNT_RP03 127 + +/* + * Compute end of directory block, given the size of each directory entry. + * This macro is good for both MFDs and UFDs. + */ +#define EODIR(m, sz) ((m->blocksz / 2) - (sz - 1)) + +/* + * The logical block size will depend on the type of the disks. Disks smaller + * than a RK05 (RS11/RS64) will use 64 word blocks while disks larger than + * a RK05 (RP03) will use 512 word blocks. The default will be for an 256 + * word blocks for a RK05. + */ +#define DISKSIZE_RK05 4800 + +/* + * Structure to describe a filename and associated UIC. Asterisks may be used + * as wild card characters for the 4 components of a filename; name, extension, + * group number and user number. + */ +struct dos11FileSpec { + uint8_t flags; /* Wild card indicators */ + uint16_t name[2]; /* File name */ + uint16_t ext; /* Extension */ + unsigned char user; /* User number */ + unsigned char group; /* Group number */ +}; +#define DOS11_WC_NAME 0001 /* Wild card name */ +#define DOS11_WC_EXT 0002 /* Wild card extension */ +#define DOS11_WC_GROUP 0004 /* Wild card group number */ +#define DOS11_WC_USER 0010 /* Wild card user number */ + +#define DOS11_M_NONE 0000 /* Wild cards not allowed */ +#define DOS11_M_ALLOW 0001 /* Wild cards allowed */ +#define DOS11_M_NONAME 0002 /* Wild cards allowed */ + /* If no filename + extension */ + /* present, default to *.* */ +/* + * Structure to define an open file. This is a DOS-11 UFD entry along with + * sufficient information to be able to write the directory entry back + * to disk. + */ +struct dos11OpenFile { + uint16_t name[2]; /* File name */ + uint16_t ext; /* Extension */ + uint16_t creation; /* Type + creation date */ + uint16_t nfb; /* Next free byte */ + uint16_t start; /* Start block # */ + uint16_t length; /* Length in blocks */ + uint16_t last; /* Last block written */ + uint16_t lup; /* Lock, usage + protection */ + /* End of directory entry */ + uint16_t ufdblk; /* UFD block # */ + uint16_t ufdoffset; /* Offset within UFD */ + /* Start of read/write info */ + enum openMode mode; /* Open mode (read/write) */ + struct mountedFS *mount; /* Mounted file system descriptor */ + char *buffer; /* Private buffer for file I/O */ + uint16_t current; /* Current block */ + uint16_t nab; /* Next available byte */ + uint16_t eob; /* End of buffer */ +}; + +/* + * DOS-11 specific data area. Some fields are sized for the worst case - + * RP03 disk pack with 65535 blocks of 1024 bytes each. + */ +struct DOS11data { + unsigned int blocks; /* # of blocks in file system */ + uint16_t bitmaps; /* # of bitmaps */ + uint16_t bmblk[128]; /* Bitmap block addresses */ + uint16_t bmindex; /* Current bitmap in buffer */ + uint16_t bmscan; /* Start bitmap scans here */ + uint8_t bmdirty; /* 0 => clean, 1 => dirty */ + uint16_t bmbuf[512]; /* Buffer for bitmap access */ + uint16_t buf[512]; /* Disk buffer */ + /* + * Settable parameters + */ + uint8_t group; /* Default group # */ + uint8_t user; /* Default user # */ +}; + +#endif diff --git a/converters/fsio/dosmt.c b/converters/fsio/dosmt.c new file mode 100644 index 0000000..03ac002 --- /dev/null +++ b/converters/fsio/dosmt.c @@ -0,0 +1,1321 @@ +/* + * Copyright (C) 2018 John Forecast. All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY JOHN FORECAST "AS IS" AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/* + * Support routines for handling DOS/BATCH-11 magtapes under fsio + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "fsio.h" + +/* + * Table of "set" commands + */ +static char *setCmds[] = { + "uic", + "prot", + NULL +}; +#define DOSMTSET_UIC 0 +#define DOSMTSET_PROT 1 + +extern int args; +extern char **words; + +/*++ + * d o s m t P a r s e F i l e s p e c + * + * Parse a character string representing a DOS-11 magtape file specification. + * + * Inputs: + * + * mount - pointer to a mounted file system descriptor + * ptr - pointer to the file specification string + * spec - pointer to the file specification block + * wildcard - wildcard processing options: + * 0 (DOSMT_M_NONE) - wildcards not allowed + * 1 (DOSMT_M_ALLOW) - wildcards allowed + * 2 (DOSMT_M_NONAME) - wildcards allowed + * if filename + ext not + * present default to *.*[*,*] + * + * Outputs: + * + * The file specification block will be filled with the file information. + * + * Returns: + * + * 1 if parse is successful, 0 otherwise + * + --*/ +#define P_DONE 0 +#define P_NAME 1 +#define P_EXT 2 +#define P_PROJ 3 +#define P_PROG 4 + +int dosmtParseFilespec( + struct mountedFS *mount, + char *ptr, + struct dosmtFileSpec *spec, + int wildcard +) +{ + struct DOSMTdata *data = &mount->dosmtdata; + char term[] = { '\0', '.', '[', ',', ']' }; + char flags[] = + { 0, DOSMT_WC_NAME, DOSMT_WC_EXT, DOSMT_WC_PROJ, DOSMT_WC_PROG }; + int state = P_NAME; + unsigned int uic, i, namemax = ((mount->flags & FS_DOSMTEXT) != 0) ? 9 : 6; + char filename[9], ext[3]; + + memset(spec, 0, sizeof(struct dosmtFileSpec)); + spec->proj = data->proj; + spec->prog = data->prog; + + memset(&filename, ' ', sizeof(filename)); + memset(&ext, ' ', sizeof(ext)); + + if (wildcard == DOSMT_M_NONAME) + spec->flags = DOSMT_WC_NAME | DOSMT_WC_EXT | DOSMT_WC_PROJ | DOSMT_WC_PROG; + + while (state != P_DONE) { + if (wildcard) { + if (*ptr == '*') { + spec->flags |= flags[state]; + ptr++; + if (*ptr == '\0') + state = P_DONE; + else if (*ptr == term[state]) { + if (state == P_PROG) + state = P_DONE; + else state++; + } else return 0; + ptr++; + continue; + } + } + + i = 0; + + switch (state) { + case P_NAME: + while ((*ptr != '\0') && + (*ptr != '.') && + (strchr(rad50, toupper(*ptr)) != NULL) && + (i < namemax)) { + spec->flags &= ~DOSMT_WC_NAME; + filename[i++] = toupper(*ptr++); + } + + switch (*ptr++) { + case '\0': + state = P_DONE; + break; + + case '.': + state = P_EXT; + break; + + case '[': + state = P_PROJ; + break; + + default: + return 0; + } + break; + + case P_EXT: + while ((*ptr != '\0') && + (strchr(rad50, toupper(*ptr)) != NULL) && + (i < sizeof(ext))) { + spec->flags &= ~DOSMT_WC_EXT; + ext[i++] = toupper(*ptr++); + } + + switch (*ptr++) { + case '\0': + state = P_DONE; + break; + + case '[': + state = P_PROJ; + break; + + default: + return 0; + } + break; + + case P_PROJ: + spec->flags &= ~(DOSMT_WC_PROJ | DOSMT_WC_PROG); + uic = 0; + while ((strchr("01234567", *ptr) != NULL) && (i < 3)) { + uic = (uic << 3) | (*ptr++ - '0'); + i++; + } + if ((uic == 0) || (uic > 0377)) + return 0; + spec->proj = uic & 0377; + + if (*ptr++ != ',') + return 0; + + state = P_PROG; + break; + + case P_PROG: + uic = 0; + while ((strchr("01234567", *ptr) != NULL) && (i < 3)) { + uic = (uic << 3) | (*ptr++ - '0'); + i++; + } + if ((uic == 0) || (uic > 0377)) + return 0; + spec->prog = uic & 0377; + + if (*ptr++ != ']') + return 0; + + state = P_DONE; + break; + } + } + + spec->name[0] = ascR50(&filename[0]); + spec->name[1] = ascR50(&filename[3]); + spec->name[2] = ascR50(&filename[6]); + spec->ext = ascR50(&ext[0]); + return 1; +} + +/*++ + * d o s m t L o o k u p F i l e + * + * Lookup up a specific file on the tape. If the "-n" switch is set, the + * tape will not be rewound before starting the lookup. + * + * Inputs: + * + * mount - pointer to a mounted file system descriptor + * spec - pointer to the file specification block + * file - pointer to open file descriptor to receive results + * + * Outputs: + * + * The mount point specific buffer area will be modified. + * + * Returns: + * + * 1 if file found, 0 otherwise + * + --*/ +int dosmtLookupFile( + struct mountedFS *mount, + struct dosmtFileSpec *spec, + struct dosmtOpenFile *file +) +{ + struct DOSMTdata *data = &mount->dosmtdata; + uint32_t status; + + if (!SWISSET('n')) + tapeRewind(mount->container); + + do { + switch (status = tapeReadRecord(mount->container, data->buf, DOSMTRCLNT)) { + case ST_FAIL: + goto error; + + case ST_EOM: + break; + + case ST_TM: + /* Second tape mark in a row - treat as end-of-media */ + status = ST_EOM; + break; + + default: + if ((status & ST_ERROR) == 0) { + if (status == sizeof(struct dosmthdr)) { + struct dosmthdr *hdr = (struct dosmthdr *)data->buf; + + /* + * Determine if this file matches the filespec. + */ + if ((spec->flags & DOSMT_WC_PROJ) == 0) + if (spec->proj != hdr->proj) + goto nomatch; + + if ((spec->flags & DOSMT_WC_PROG) == 0) + if (spec->prog != hdr->prog) + goto nomatch; + + if ((spec->flags & DOSMT_WC_NAME) == 0) { + if ((spec->name[0] != le16toh(hdr->fname[0])) || + (spec->name[1] != le16toh(hdr->fname[1]))) + goto nomatch; + + if ((mount->flags & FS_DOSMTEXT) != 0) + if (spec->name[2] != le16toh(hdr->fname3)) + goto nomatch; + } + + if ((spec->flags & DOSMT_WC_EXT) == 0) + if (spec->ext != le16toh(hdr->ext)) + goto nomatch; + + /* + * Found a matching file. + */ + file->mount = mount; + + return 1; + } else goto error; + } + + nomatch: + /* + * Skip to next file. + */ + do { + if ((status = tapeReadRecordLength(mount->container)) == ST_FAIL) + goto error; + } while ((status != ST_EOM) && (status != ST_TM)); + + if (status == ST_EOM) + tapeSetPosition(mount->container, data->eot); + } + } while (status != ST_EOM); + + error: + /* + * Make sure we leave the tape at a valid position. + */ + tapeSetPosition(mount->container, data->eot); + return 0; +} + +/*++ + * d o s m t R e a d B y t e s + * + * Read a sequence of bytes from an open file. + * + * Inputs: + * + * file - pointer to an open file descriptor + * buf - pointer to a buffer to receive the data + * len - # of bytes of data to read + * + * Outputs: + * + * The buffer will be overwritten by up to "len" bytes + * + * Returns: + * + * # of bytes read from the file (may be less than "len"), 0 if EOF + * + --*/ +static int dosmtReadBytes( + struct dosmtOpenFile *file, + char *buf, + int len +) +{ + struct mountedFS *mount = file->mount; + int count = 0; + uint32_t status; + off_t pos; + + while (len) { + if (file->nextb == DOSMTRCLNT) { + if (file->tm != 0) + return count; + + pos = tapeGetPosition(mount->container); + + switch (status = tapeReadRecord(mount->container, file->buf, DOSMTRCLNT)) { + case ST_FAIL: + return 0; + + case ST_TM: + case ST_EOM: + file->tm = 1; + return count; + + default: + if ((status & ST_ERROR) || (status != DOSMTRCLNT)) { + tapeSetPosition(mount->container, pos); + return count; + } + file->nextb = 0; + break; + } + } + *buf++ = file->buf[file->nextb++]; + len--; + count++; + } + return count; +} + +/*++ + * d o s m t W r i t e B y t e s + * + * Write a sequence of bytes to an open DOS-11 magtape file. + * + * Inputs: + * + * file - pointer to an open file descriptor + * buf - pointer to a buffer to receive the data + * len - # of bytes of data to read + * + * Outputs: + * + * None + * + * Returns: + * + * # of bytes written to the file (may be less than "len"), 0 if error + * + --*/ +static int dosmtWriteBytes( + struct dosmtOpenFile *file, + char *buf, + int len +) +{ + struct mountedFS *mount = file->mount; + int count = 0; + + if (file->error != 0) + return 0; + + while (len) { + file->buf[file->nextb++] = *buf++; + len--; + count++; + + if (file->nextb == DOSMTRCLNT) { + if (tapeWriteRecord(mount->container, file->buf, DOSMTRCLNT) == 0) { + file->error = 1; + return count; + } + memset(file->buf, 0, DOSMTRCLNT); + file->nextb = 0; + } + } + return count; +} + +/*++ + * d o s m t M o u n t + * + * Verify that the open container file is in valid .tap format and that it + * consists of a number of files each preceeded by a file header. Strict + * DOS-11 tapes will have a 12 byte file header but there are some tapes + * will use an extended, 14 byte header with an extra 3 characters in the + * filename. + * + * Inputs: + * + * mount - pointer to a mounted file system descriptor + * (not in the mounted file system list) + * + * Outputs: + * + * None + * + * Returns: + * + * 1 if a valid DOS-11 magtape, 0 otherwise + * + --*/ +static int dosmtMount( + struct mountedFS *mount +) +{ + struct DOSMTdata *data = &mount->dosmtdata; + + if (tapeVerify(mount->container, &data->eot)) { + unsigned char buf[DOSMTRCLNT]; + uint32_t status; + + /* + * Scan each file making sure it is preceeded by a dos11 header (either + * version is valid). + */ + do { + /* + * If we are at end-of-tape, everything is OK + */ + if (tapeGetPosition(mount->container) == data->eot) + break; + + switch (status = tapeReadRecord(mount->container, buf, sizeof(buf))) { + case ST_FAIL: + return 0; + + case ST_EOM: + break; + + case ST_TM: + /* Second tape mark in a row - treat as end of media */ + status = ST_EOM; + break; + + default: + /* + * If the header has an error, unable to use this tape. + */ + if ((status & ST_ERROR) != 0) + return 0; + + status &= ST_LENGTH; + + if (status != sizeof(struct dosmthdr)) + return 0; + + /* + * Scan forward to the end of this file. + */ + do { + switch (status = tapeReadRecordLength(mount->container)) { + case ST_FAIL: + return 0; + + case ST_EOM: + case ST_TM: + break; + + default: + if ((status & ST_ERROR) == 0) + if ((status & ST_LENGTH) != DOSMTRCLNT) + return 0; + } + } while ((status != ST_EOM) && (status != ST_TM)); + } + } while (status != ST_EOM); + + data->proj = 01; + data->prog = 01; + data->prot = 0233; + + if (SWISSET('x')) + mount->flags |= FS_DOSMTEXT; + + /* + * Position the tape at the beginning + */ + tapeRewind(mount->container); + + return 1; + } else fprintf(stderr, "mount: Invalid tape format\n"); + return 0; +} + +/*++ + * d o s m t U m o u n t + * + * Unmount the DOS-11 magtape file system, releasing any storage allocated. + * + * Inputs: + * + * mount - pointer to the mounted file system descriptor + * + * Outputs: + * + * None + * + * Returns: + * + * None + * + --*/ +static void dosmtUmount( + struct mountedFS *UNUSED(mount) +) +{ +} + +/*++ + * d o s m t S e t + * + * Set mount point/filesystem specific values. + * + * Inputs: + * + * mount - pointer to the mounted file system descriptor + * unit - device unit number (unused) + * present - device unit number present (unused) + * + * Outputs: + * + * None + * + * Returns: + * + * None + * + --*/ +static void dosmtSet( + struct mountedFS *mount, + uint8_t UNUSED(unit), + uint8_t UNUSED(present) +) +{ + struct DOSMTdata *data = &mount->dosmtdata; + int idx = 0; + uint8_t prog, proj, prot; + + while (setCmds[idx] != NULL) { + if (strcmp(words[1], setCmds[idx]) == 0) { + switch (idx) { + case DOSMTSET_UIC: + if (args == 3) { + if (sscanf(words[2], "[%hho,%hho]", &proj, &prog) == 2) { + data->proj = proj; + data->prog = prog; + } else fprintf(stderr, + "dosmt: UIC syntax error \"%s\"\n", words[2]); + } else fprintf(stderr, "dosmt: Invalid syntax for \"set uic\"\n"); + return; + + case DOSMTSET_PROT: + if (args == 3) { + if (sscanf(words[2], "<%hho>", &prot) == 1) { + data->prot = prot; + } else fprintf(stderr, + "dosmt: Protection syntax error \"%s\"\n", words[2]); + } else fprintf(stderr, "dosmt: Invalid syntax for \"set prot\"\n"); + return; + } + } + idx++; + } + fprintf(stderr, "dosmt: Unknown set command \"%s\"\n", words[1]); +} + +/*++ + * d o s m t I n f o + * + * Display information about the current position of the tape. + * + * Inputs: + * + * mount - pointer to the mounted file system descriptor + * unit - device unit number (unused) + * present - device unit number present (unused) + * + * Outputs: + * + * None + * + * Returns: + * + * None + * + --*/ +static void dosmtInfo( + struct mountedFS *mount, + uint8_t UNUSED(unit), + uint8_t UNUSED(present) +) +{ + struct DOSMTdata *data = &mount->dosmtdata; + off_t pos = tapeGetPosition(mount->container); + int eot = 0, bot = pos == 0; + char temp[32], filename[14]; + struct dosmthdr *hdr = (struct dosmthdr *)data->buf; + struct stat stat; + + fstat(fileno(mount->container), &stat); + + /* + * If we are positioned at the end-of-file, there is a tape mark or + * end-of-media marker missing. Treat it as though the missing marker + * is present. + */ + if (pos != stat.st_size) { + uint32_t bc; + + switch (bc = tapeReadRecord(mount->container, data->buf, DOSMTRCLNT)) { + case ST_FAIL: + goto error; + + case ST_TM: + case ST_EOM: + eot = 1; + break; + + default: + if ((bc & ST_ERROR) == 0) { + if (bc == sizeof(struct dosmthdr)) { + + memset(filename, ' ', sizeof(filename)); + filename[13]='\0'; + + r50Asc(le16toh(hdr->fname[0]), &filename[0]); + r50Asc(le16toh(hdr->fname[1]), &filename[3]); + if ((mount->flags & FS_DOSMTEXT) != 0) + r50Asc(le16toh(hdr->fname3), &filename[6]); + filename[9] = '.'; + r50Asc(le16toh(hdr->ext), &filename[10]); + break; + } + } + strcpy(filename, "Unknown"); + break; + } + + /* + * Restore the tape position + */ + if (tapeSetPosition(mount->container, pos) != 0) + goto error; + + } else eot = 1; + + if ((bot == 0) && (eot == 0)) { + unsigned long long position = pos; + + sprintf(temp, "Offset %llu", position); + } + + printf("%s:\n", mount->name); + printf(" Position: %s\n", + bot ? "Beginning of tape" : + (eot ? "End of tape" : temp)); + if (!eot) + printf(" Next file: %s[%3o,%3o]\n", + filename, hdr->proj, hdr->prog); + return; + + error: + fprintf(stderr, "info: container file I/O error\n"); + tapeRewind(mount->container); +} + +/*++ + * d o s m t D i r + * + * Produce a full or brief directory listing. After listing the tape will + * be positioned at beginning-of-tape. If the '-n' switch is specified, + * the directory will be listed from the current tape position. + * + * Inputs: + * + * mount - pointer to the mounted file system descriptor + * unit - device unit number (unused) + * fname - pointer to filename string + * + * Outputs: + * + * None + * + * Returns: + * + * None + * + --*/ +static void dosmtDir( + struct mountedFS *mount, + uint8_t UNUSED(unit), + char *fname +) +{ + struct DOSMTdata *data = &mount->dosmtdata; + struct dosmtFileSpec spec; + uint32_t status; + + if (dosmtParseFilespec(mount, fname, &spec, DOSMT_M_NONAME) == 0) { + fprintf(stderr, "dir: syntax error in file spec \"%s\"\n", fname); + return; + } + + /* + * Position to beginning-of-tape unless the '-n' switch is specified. + */ + if (!SWISSET('n')) + tapeRewind(mount->container); + + do { + switch (status = tapeReadRecord(mount->container, data->buf, DOSMTRCLNT)) { + case ST_FAIL: + return; + + case ST_EOM: + break; + + case ST_TM: + /* Second tape mark in a row - treat as end-of-media */ + status = ST_EOM; + break; + + default: + if ((status & ST_ERROR) == 0) { + if (status == sizeof(struct dosmthdr)) { + char filename[14], sdate[12]; + struct dosmthdr *hdr = (struct dosmthdr *)data->buf; + uint16_t blocks = 0; + uint32_t length; + + /* + * Compute the number of blocks in this file. + */ + do { + switch (length = tapeReadRecordLength(mount->container)) { + case ST_FAIL: + return; + + case ST_EOM: + case ST_TM: + break; + + default: + if ((length & ST_ERROR) == 0) + blocks++; + break; + } + } while ((length != ST_EOM) && (length != ST_TM)); + + /* + * Determine if this file matches the filespec. + */ + if ((spec.flags & DOSMT_WC_PROJ) == 0) + if (spec.proj != hdr->proj) + continue; + + if ((spec.flags & DOSMT_WC_PROG) == 0) + if (spec.prog != hdr->prog) + continue; + + if ((spec.flags & DOSMT_WC_NAME) == 0) { + if ((spec.name[0] != le16toh(hdr->fname[0])) || + (spec.name[1] != le16toh(hdr->fname[1]))) + continue; + + if ((mount->flags & FS_DOSMTEXT) != 0) + if (spec.name[2] != le16toh(hdr->fname3)) + continue; + } + + if ((spec.flags & DOSMT_WC_EXT) == 0) + if (spec.ext != le16toh(hdr->ext)) + continue; + + memset(filename, ' ', sizeof(filename)); + filename[13] = '\0'; + + r50Asc(le16toh(hdr->fname[0]), &filename[0]); + r50Asc(le16toh(hdr->fname[1]), &filename[3]); + if ((mount->flags & FS_DOSMTEXT) != 0) + r50Asc(le16toh(hdr->fname3), &filename[6]); + filename[9] = '.'; + r50Asc(le16toh(hdr->ext), &filename[10]); + + if (SWISSET('f')) { + dos11Date(le16toh(hdr->date), sdate); + + printf("%s %5d. %s <%03o> [%3o,%3o]\n", + filename, blocks, sdate, + le16toh(hdr->prot), hdr->proj, hdr->prog); + } else printf("%s [%3o,%3o]\n", filename, hdr->proj, hdr->prog); + } else fprintf(stderr, " *** Unexpected record size\n"); + } else fprintf(stderr, " *** Directory entry contains error\n"); + } + } while (status != ST_EOM); + + tapeRewind(mount->container); +} + +/*++ + * d o s m t O p e n F i l e R + * + * Open a DOS-11 magtape file for reading. + * + * Inputs: + * + * mount - pointer to the mounted file system descriptor + * unit - device unit number (unused) + * fname - pointer to filename string + * + * Outputs: + * + * None + * + * Returns: + * + * Pointer to open file descriptor, NULL if open fails + * + --*/ +static void *dosmtOpenFileR( + struct mountedFS *mount, + uint8_t UNUSED(unit), + char *fname +) +{ + struct dosmtOpenFile *file; + struct dosmtFileSpec spec; + + if (dosmtParseFilespec(mount, fname, &spec, DOSMT_M_NONAME) == 0) { + fprintf(stderr, "Failed to parse filename \"%s\"\n", fname); + return NULL; + } + + if ((file = malloc(sizeof(struct dosmtOpenFile))) != NULL) { + if (dosmtLookupFile(mount, &spec, file) != 0) { + file->mode = M_RD; + file->nextb = DOSMTRCLNT; + file->tm = 0; + file->error = 0; + } else { + free(file); + return NULL; + } + } + return file; +} + +/*++ + * d o s m t O p e n F i l e W + * + * Open a DOS-11 magtape file for writing. + * + * Inputs: + * + * mount - pointer to the mounted file system descriptor + * unit - device unit number (unused) + * fname - pointer to filename string + * size - estimated file size (in bytes) + * + * Outputs: + * + * None + * + * Returns: + * + * Pointer to open file descriptor, NULL if open fails + * + --*/ +static void *dosmtOpenFileW( + struct mountedFS *mount, + uint8_t UNUSED(unit), + char *fname, + off_t UNUSED(size) +) +{ + struct dosmtOpenFile *file; + struct dosmtFileSpec spec; + struct dosmthdr hdr; + struct tm tm; + time_t now = time(NULL); + uint16_t today; + + if (dosmtParseFilespec(mount, fname, &spec, DOSMT_M_NONE) == 0) { + fprintf(stderr, "Failed to parse filename \"%s\"\n", fname); + return NULL; + } + + if ((file = malloc(sizeof(struct dosmtOpenFile))) != NULL) { + /* + * Compute a suitable year for file creation date. This year will have + * the same calendar as the current year but will be in the 20th century + * so that DOS/BATCH-11 will be able to interpret it correctly. + */ + localtime_r(&now, &tm); + tm.tm_year -= 28; + + today = ((tm.tm_year - 70) * 1000) + tm.tm_yday + 1; + + hdr.fname[0] = htole16(spec.name[0]); + hdr.fname[1] = htole16(spec.name[1]); + if ((mount->flags & FS_DOSMTEXT) != 0) + hdr.fname3 = htole16(spec.name[2]); + else hdr.fname3 = 0; + hdr.ext = htole16(spec.ext); + hdr.prog = spec.prog; + hdr.proj = spec.proj; + hdr.prot = htole16(mount->dosmtdata.prot); + hdr.date = htole16(today); + + if (tapeWriteRecord(mount->container, &hdr, sizeof(hdr)) == 0) { + free(file); + return NULL; + } + + file->mode = M_WR; + file->mount = mount; + file->nextb = 0; + file->tm = 0; + file->error = 0; + } + return file; +} + +/*++ + * d o s m t F i l e S i z e + * + * Return an estimate of the size of an open DOS-11 magtape file. + * + * Inputs: + * + * filep - pointer to open file descriptor + * + * Outputs: + * + * None + * + * Returns: + * + * Estimated size of the file + * + --*/ +static off_t dosmtFileSize( + void *filep +) +{ + struct dosmtOpenFile *file = filep; + struct mountedFS *mount = file->mount; + off_t pos = tapeGetPosition(mount->container); + off_t size = 0; + uint32_t length; + + /* + * Compute the length of this file. The estimate may larger than the + * actual size of the file size don't know the actual EOF position + * within the last block of the file. + */ + do { + switch (length = tapeReadRecordLength(mount->container)) { + case ST_FAIL: + size = 0; + length = ST_EOM; + /* FALLTHROUGH */ + + case ST_EOM: + case ST_TM: + break; + + default: + size += DOSMTRCLNT; + break; + } + } while((length != ST_EOM) && (length != ST_TM)); + + tapeSetPosition(mount->container, pos); + return size; +} + +/*++ + * d o s m t C l o s e F i l e + * + * Close an open DOS-11 magtape file. + * + * Inputs: + * + * filep - pointer to open file descriptor + * + * Outputs: + * + * None + * + * Returns: + * + * None + * + --*/ +static void dosmtCloseFile( + void *filep +) +{ + struct dosmtOpenFile *file = filep; + struct mountedFS *mount = file->mount; + struct DOSMTdata *data = &mount->dosmtdata; + uint32_t status; + + if (file->mode == M_RD) { + if (file->tm == 0) { + /* + * Tape mark not seen, skip to end of current file. + */ + do { + if ((status = tapeReadRecordLength(mount->container)) == ST_FAIL) { + fprintf(stderr, + "Error positioning \"%s\", rewinding\n", mount->name); + tapeRewind(mount->container); + status = ST_TM; + } + } while ((status != ST_EOM) && (status != ST_TM)); + + if (status == ST_EOM) + tapeSkipRecordR(mount->container); + } + } else { + if (file->nextb != 0) { + /* + * The are some pending output byte(s), flush a final record to the tape. + */ + if (tapeWriteRecord(mount->container, file->buf, DOSMTRCLNT) == 0) + file->error = 1; + } + if (tapeWriteTM(mount->container) == 0) + file->error = 1; + + data->eot = tapeGetPosition(mount->container); + + if (tapeWriteEOM(mount->container, 1) == 0) + file->error = 1; + + if (file->error != 0) { + ERROR("Panic: Error writing on \"%s\"\n", mount->name); + exit(3); + } + } + + free(file); +} + +/*++ + * d o s m t R e a d F i l e + * + * Read data from a DOS-11 magtape to a supplied buffer. + * + * Inputs: + * + * filep - pointer to open file descriptor + * buf - pointer to buffer + * buflen - length of the supplied buffer + * + * Outputs: + * + * None + * + * Returns: + * + * # of bytes of data read, 0 means EOF or error + * + --*/ +static size_t dosmtReadFile( + void *filep, + void *buf, + size_t buflen +) +{ + struct dosmtOpenFile *file = filep; + char *bufr = buf; + + return dosmtReadBytes(file, bufr, buflen); +} + +/*++ + * d o s m t W r i t e F i l e + * + * Write data to a DOS-11 magtape file from a supplied buffer. + * + * Inputs: + * + * filep - pointer to open file descriptor + * buf - pointer to buffer + * buflen - length of the supplied buffer + * + * Outputs: + * + * None + * + * Returns: + * + * # of bytes of data written, 0 means error + * + --*/ +size_t dosmtWriteFile( + void *filep, + void *buf, + size_t buflen +) +{ + struct dosmtOpenFile *file = filep; + char *bufw = buf; + + return dosmtWriteBytes(file, bufw, buflen); +} + +/*++ + * d o s m t R e w i n d + * + * Rewind the tape. + * + * Inputs: + * + * mount - pointer to the mounted file system descriptor + * + * Outputs: + * + * None + * + * Returns: + * + * None + * + --*/ +static void dosmtRewind( + struct mountedFS *mount +) +{ + tapeRewind(mount->container); +} + +/*++ + * d o s m t E O M + * + * Position the tape just before the final tape mark or end of media marker. + * A subsequent write operation will append to the tape. Note that we have + * special case an empty container file containg or + * where we need to position to the beginning of the container rather than + * after the first . + * + * Inputs: + * + * mount - pointer to the mounted file system descriptor + * + * Outputs: + * + * None + * + * Returns: + * + * None + * + --*/ +static void dosmtEOM( + struct mountedFS *mount +) +{ + struct DOSMTdata *data = &mount->dosmtdata; + + if (tapeEOM(mount->container, &data->eot) == 0) + fprintf(stderr, "eom: Failed to position device\n"); +} + +/*++ + * d o s m t S k i p F + * + * Skip forward over a specified number of files. If end-of-media is reached, + * the skip operation will terminate early. + * + * Inputs: + * + * mount - pointer to the mounted file system descriptor + * count - # of files to skip + * + * Outputs: + * + * None + * + * Returns: + * + * None + * + --*/ +static void dosmtSkipF( + struct mountedFS *mount, + unsigned long count +) +{ + if (tapeSkipForward(mount->container, count) == 0) + fprintf(stderr, "skipf: Failed to position device\n"); +} + +/*++ + * d o s m t S k i p R + * + * Skip backwards over a specified number of files. If end-of-media is + * reached, the skip operation will terminate early. + * + * Inputs: + * + * mount - pointer to the mounted file system descriptor + * count - # of files to skip + * + * Outputs: + * + * None + * + * Returns: + * + * None + * + --*/ +static void dosmtSkipR( + struct mountedFS *mount, + unsigned long count +) +{ + if (tapeSkipReverse(mount->container, count) == 0) + fprintf(stderr, "skipf: Failed to position device\n"); +} + +/*++ + * d o s m t F S + * + * Descriptor for access DOS/BATCH-11 magtapes. + * + --*/ +struct FSdef dosmtFS = { + NULL, + "dosmt", + "dosmt PDP-11 DOS/BATCH-11 magtape access\n", + FS_TAPE | FS_EMPTYFILE | FS_1OPENFILE, + 0, + dosmtMount, + dosmtUmount, + NULL, + NULL, + dosmtSet, + dosmtInfo, + dosmtDir, + dosmtOpenFileR, + dosmtOpenFileW, + dosmtFileSize, + dosmtCloseFile, + dosmtReadFile, + dosmtWriteFile, + NULL, + dosmtRewind, + dosmtEOM, + dosmtSkipF, + dosmtSkipR +}; diff --git a/converters/fsio/dosmt.h b/converters/fsio/dosmt.h new file mode 100644 index 0000000..6ece45b --- /dev/null +++ b/converters/fsio/dosmt.h @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2018 John Forecast. All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY JOHN FORECAST "AS IS" AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#ifndef __DOSMT_H__ +#define __DOSMT_H__ +#include "tape.h" + +/* + * DOS-11 magtapes contain data records of 512 bytes each. + */ +#define DOSMTRCLNT 512 + +/* + * DOS/BATCH-11 magtape file header. + */ +struct dosmthdr { + uint16_t fname[2]; /* first 6 chars of filename (RAD50) */ + uint16_t ext; /* 3 char file extension (RAD50) */ + uint8_t prog; /* programmer # (octal) */ + uint8_t proj; /* project # (octal) */ + uint16_t prot; /* protection code (octal) */ + uint16_t date; /* (year-1970) * 1000 + day of year */ + uint16_t fname3; /* optional, char 7 - 9 of name */ +}; + +/* + * Structure to describe a filename and associated UIC. Asterisks may be used + * as wild card characters for the 4 components of a filename; name, extension, + * group number and user number. + */ +struct dosmtFileSpec { + uint8_t flags; /* Wild card indicators */ + uint16_t name[3]; /* File name */ + uint16_t ext; /* Extension */ + unsigned char proj; /* Project number */ + unsigned char prog; /* Programmer number */ +}; +#define DOSMT_WC_NAME 0001 /* Wild card name */ +#define DOSMT_WC_EXT 0002 /* Wild card extension */ +#define DOSMT_WC_PROJ 0004 /* Wild card project number */ +#define DOSMT_WC_PROG 0010 /* Wild card programmer number */ + +#define DOSMT_M_NONE 0000 /* Wild cards not allowed */ +#define DOSMT_M_ALLOW 0001 /* Wild cards allowed */ +#define DOSMT_M_NONAME 0002 /* Wild cards allowed */ + /* If no filename + extension */ + /* present, default to *.*[*,*] */ + +/* + * Structure to define an open file. + */ +struct dosmtOpenFile { + enum openMode mode; /* Open mode (read/write) */ + struct mountedFS *mount; /* Mounted file system descriptor */ + char buf[DOSMTRCLNT];/* Private buffer for file I/O */ + uint16_t nextb; /* Next byte to use */ + uint8_t tm; /* Tape mark has been read */ + uint8_t error; /* Error has been detected */ +}; + +/* + * DOS-11 magtape specific data area. + */ +struct DOSMTdata { + uint8_t buf[DOSMTRCLNT]; + off_t eot; /* Logical end-of-tape */ + + /* + * Settable parameters + */ + uint8_t proj; /* project # */ + uint8_t prog; /* programmer # */ + uint8_t prot; /* protection code */ +#define FS_DOSMTEXT 0x0100 /* Write extended file headers */ +}; + +#endif diff --git a/converters/fsio/fsio-dos11.1 b/converters/fsio/fsio-dos11.1 new file mode 100644 index 0000000..b4665d1 --- /dev/null +++ b/converters/fsio/fsio-dos11.1 @@ -0,0 +1,51 @@ +.TH FSIO-DOS11 1 "Jun 21,2019" "FFS I/O - DOS-11" +.SH NAME +fsio-dos11 \- Foreign File System I/O - DOS-11 +.br +.SH DESCRIPTION +\fBfsio\fP allows access to DOS-11 file systems using the file system type +"\fIdos11\fP" +.br +.SH WILDCARD CHARACTERS +The wildcard character \fI'*'\fP may be used to match filename, extension, +group number and user number. Wildcard characters are only valid with the +\fIdir\fP command. +.br +.SH NEWFS OPERATION +\fInewfs\fP creates a blank RK05 image (2.5MB, 4800 blocks) with no UFD +entries. +.SH SET OPERATION +The following \fIset\fP commands are supported: +.br +.TP +.B "\fIuic\fP [group,user]" +Sets the default UIC for file access (was [1,1] after mount). +.br +.RS +.RS +.B "\fIgroup\fP \- Group number (octal 1 - 377)" +.br +.B "\fIuser\fP \- User number (octal 1 - 377)" +.br +.RE +.RE +.TP +.B "\fIufd\fP [group,user]" +Creates an empty UFD and sets default UIC for file access. +.br +.RS +.RS +.B "\fIgroup\fP \- Group number (octal 1 - 377)" +.br +.B "\fIuser\fP \- User number (octal 1 - 377)" +.br +.RE +.RE +.SH SEE ALSO +.BR fsio (1), +.BR fsio-rt11 (1) +.BR fsio-dosmt (1) +.BR fsio-os8 (1) +.SH AUTHOR +John Forecast, +.br diff --git a/converters/fsio/fsio-dosmt.1 b/converters/fsio/fsio-dosmt.1 new file mode 100644 index 0000000..cf263e3 --- /dev/null +++ b/converters/fsio/fsio-dosmt.1 @@ -0,0 +1,111 @@ +.TH FSIO-DOSMT 1 "Jun 25,2019" "FFS I/O - DOS-11 magtape" +.SH NAME +fsio-dosmt \- Foreign File System I/O - DOS-11 magtape +.br +.SH DESCRIPTION +\fBfsio\fP allows access to DOS-11 magtapes using the file system type +"\fIdosmt\fP" +.br +.SH WILDCARD CHARACTERS +The wildcard character \fI'*'\fP may be used to match filename, extension, +group number and user number. Wildcard characters may be used with the +\fIdir\fP command to limit the number of files being displayed. They may also +be used with commands to read files; \fIdump\fP, \fItype\fP and \fIcopy\fP +(only the source file for \fIcopy\fP), in which case the next file which +matches the wildcard character(s) will be used for input. As a consequence +of this, the empty filename may be used to select the next file on the tape, +so: +.RS + +fsio> type mt: +.RE + +will type the next file on the tape. +.br +.SH MOUNT OPERATION +DOS/BATCH-11 defined magtape headers to be 14 bytes long with 2 bytes unused. +Some tapes use these unused bytes to add an additional 3 characters to the +filename (e.g. ABCDEFGHI.EXT rather than ABCDEF.EXT). By default, fsio will +ignore these unused bytes. If the \fI-x\fP switch is used on the \fImount\fP +command, fsio will make use of the extra 3 characters on file lookup, +directory listing and file creation. +.SH NEWFS OPERATION +\fInewfs\fP creates an empty (zero length) file. +.SH SET OPERATION +The following \fIset\fP commands are supported: +.br +.TP +.B "\fIuic\fP [proj,prog]" +Sets the default UIC for file access (was [1,1] after mount). +.br +.RS +.RS +.B "\fIproj\fP \- Project number (octal 1 - 377)" +.br +.B "\fIprog\fP \- Programmer number (octal 1 - 377)" +.br +.RE +.RE +.TP +.B "\fIprot\fP " +Set the default protection code when writing new files to magtape. +.br +.RS +.RS +.B "\fIprot\fP \- Protection code (octal 0 - 377)" +.br +.RE +.RE +.SH INFO OPERATION +\fIinfo\fP displays where the tape is currently positioned. If the tape is +not positioned at the end and it is not an empty tape, the name of the next +file after the current position will be displayed. +.SH NOTES +DOS-11 magtapes differ from disk-based file systems in a number of ways: +.br +.RS +1. Filenames are not unique on a magtape. +.br +2. The tape has to be positioned before writing a new file to + avoid over-writing an existing file. +.br +.RE + +Assuming a magtape is mounted on mt: and has 5 files on it: +.br + +The sequence: +.br +.RS + +fsio> rewind mt: +.br +fsio> copy -a a.b mt:c.d +.br +.RE + +will result in the tape containing a single file (c.d), while the sequence: +.br +.RS + +fsio> eom mt: +.br +fsio> copy -a a.b mt:c.d +.br +.RE + +will result in the tape containing 6 files (the original 5 followed by c.d). +.br + +By using skipf/skipr commands it is possible to position the tape just past +the end of any specific file (see info command for more details on how to +determine the current tape position). +.br +.SH SEE ALSO +.BR fsio (1) +.BR fsio-dos11 (1) +.BR fsio-rt11 (1) +.BR fsio-os8 (1) +.SH AUTHOR +John Forecast, +.br diff --git a/converters/fsio/fsio-os8.1 b/converters/fsio/fsio-os8.1 new file mode 100644 index 0000000..724886b --- /dev/null +++ b/converters/fsio/fsio-os8.1 @@ -0,0 +1,80 @@ +.TH FSIO-OS8 1 "Sep 218,2019" "FFS I/O - OS/8" +.SH NAME +fsio-os8 \- Foreign File System I/O - OS/8 +.br +.SH DESCRIPTION +\fBfsio\fP allows access to OS/8 file systems using the file system type +"\fIos8\fP" +.br +.SH OS/8 PHYSICAL DISKS +OS/8 uses a file system block size of 256 words and OS/8 devices are limited +to 4095 blocks. Blocks 0 through 6 are reserved so the largest possible file +is 4088 blocks long (1046528 words). Larger devices, for example the RK05, +place multiple file systems on each physical device. + +.br +OS/8 does not write any type of signature on the device and each device type +has it's own partitioning scheme so the \fImount\fP command must use the +"\fI-f type\fP switch so that \fBfsio\fP can determine the file system +layout. \fBfsio\fP uses a set of heuristics to verify the integrity of +the OS/8 file system(s) but it is quite possible for a random disk to pass +these tests and later crash \fBfsio\fP. +.SH MOUNT OPERATION +\fImount\fP requires the "\fI-f type\fP" switch so that it can determine the +type of the underlying disk (See NEWFS OPERATION below for details). +.SH NEWFS OPERATION +\fInewfs\fP creates an RK05 disk image with 2 file systems. If the +"\fI-f type\fP" switch is present a different container file will be created +depending on the type of the device specified: +.br +.RS +.TP +\fIrk05\fP \- RK05 image (2 file systems, 3248 blocks each) +.br +.TP +\fIrx01\fP \- RX01 image (single file system, 494 blocks) +.br +.TP +\fIrx02\fP \- RX02 image (single file system, 988 blocks) +.br +.RE + +Note that in order to use RX02 media the OS/8 system must have the device +extensions kit installed. +.br + +The "\fI-e extra\fP" switch, where "extra" is a number in the range 0 - 63, +may be used to create file systems with "extra" additional information words +available to each directory entry. By default, \fInewfs\fP will create file +systems with 1 additional information word which will be used to hold the +file creation date. If you do not want any additional information words +use "\fI-e 0\fP". +.br +.SH SET OPERATION +The following \fIset\fP commands are supported: +.br +.TP +.B "\fIyear\fP n" +.br + +All subsequent OS/8 file creations will include a creation date (if the file +system was created with at least 1 additional information word). The date used +will be the month and day of the month for today with the year "n" (where "n" +is in the range 0 - 7). +.br +.TP +.B "\fIyear\fP none" +.br + +All subsequent OS/8 file creations will include a zero creation date (if the +file system was created with at least 1 additional information word). This is +the default state after mount. +.br +.SH SEE ALSO +.BR fsio (1) +.BR fsio-dos11 (1) +.BR fsio-dosmt (1) +.BR fsio-rt11 (1) +.SH AUTHOR +John Forecast, +.br diff --git a/converters/fsio/fsio-rt11.1 b/converters/fsio/fsio-rt11.1 new file mode 100644 index 0000000..2dce4a0 --- /dev/null +++ b/converters/fsio/fsio-rt11.1 @@ -0,0 +1,89 @@ +.TH FSIO-RT11 1 "Jun 25,2019" "FFS I/O - RT-11" +.SH NAME +fsio-rt11 \- Foreign File System I/O - RT-11 +.br +.SH DESCRIPTION +\fBfsio\fP allows access to RT-11 file systems using the file system type +"\fIrt11\fP" +.br +.SH RT-11 VERSIONS +There were 5 major versions of RT-11 released. Versions 1 and 2 did not use +the home block (block 1) to indicate that the on-disk file structure was RT-11 +format. The remaining 3 versions wrote a signature on the home block +including the file system version in use. In order to access a version 1 or +version 2 container file, the \fI"-f"\fP switch must be used on the +\fI"mount"\fP command to force \fBfsio\fP +to bypass the home block checks. While \fBfsio\fP will use a set of +heuristics to verify the integrity of the RT-11 file structure it is possible +that an invalid file structure will satisfy the checks and cause \fBfsio\fP to +fail. Do not use the \fI"-f"\fP switch other than to mount version 1 or +version 2 file systems. + +.br +The VMS exchange utility may be used to initialize an RT-11 volume but writes +it's own unique signature in the home block. \fBfsio\fP is able to correctly +handle this signature. +.br +.SH RT-11 PHYSICAL DISKS +RT-11 uses a file system block size of 512 bytes and most disk type either +have a block size of 512 bytes or use 2 contiguous blocks or 256 bytes each. +Floppy diskettes, RX01 and RX02 drives, use small sectors size and for +performance reason, adjacent logical sectors are interleaved within the same +track. When mounting a floppy diskette, the \fI"-f type"\fP must be used so +that \fIfsio\fP can determine the interleaving used. In this case "type" +should be either "rx01" or "rx02". +.SH LOGICAL DISKS +\fBfsio\fP can be used to access an RT-11 logical disk if the file has been +copied to the host file system. +.SH WILDCARD CHARACTERS +The wildcard character \fI'%'\fP may be used to match a single character in a +filename or type. The wildcard character \fI'*'\fP may be used to match +zero or more characters in a filename or type. Wildcard characters are only +valid with the \fIdir\fP command. +.br +.SH NEWFS OPERATION +\fInewfs\fP creates a blank MSCP image (32MB, 65535 blocks) with a single +partition which can be accessed via the DU device (rq in SIMH). If the +\fI"-t type"\fP switch is present, a smaller container file will be created +depending on the type of device specified: +.br +.RS +.TP +\fIrk05\fP \- RK05 image (2.5MB, 4800 blocks) +.br +.TP +\fIrl01\fP \- RL01 image (5MB, 10240 blocks) +.br +.TP +\fIrl02\fP \- RL02 image (10MB, 20480 blocks) +.br +.TP +\fIrx01\fP \- RX01 floopy image (256KB, 2002 sectors of 128 bytes) +.br +.TP +\fIrx02\fP \- RX02 floopy image (512KB, 2002 sectors of 256 bytes) +.br +.RE + +The "\fI-e extra\fP" switch, where "extra" is a number in the range 0 - 63, +may be used to create file systems with "extra" additional bytes available to +each directory entry. \fIfsio\fP will silently round up the value to make it +an even number required by RT-11. By default, \fInewfs\fP will create file +systems with no additional bytes for each directory entry. +.SH COPY OPERATION +When copying in ASCII mode, \fBfsio\fP will normally write ^Z (octal 032) to +indicate the end-of-file. Some utilities, such as FLX on RSX-11, cannot +handle ^Z in the middle of a formatted ASCII file. The \fI"-p"\fP switch +may be used on a copy operation to skip writing the ^Z and pad the file with +NULLs up to the next block boundary if this occurs. +.br +.SH SET OPERATION +No \fIset\fP commands are currently supported. +.SH SEE ALSO +.BR fsio (1) +.BR fsio-dos11 (1) +.BR fsio-dosmt (1) +.BR fsio-os8 (1) +.SH AUTHOR +John Forecast, +.br diff --git a/converters/fsio/fsio.1 b/converters/fsio/fsio.1 new file mode 100644 index 0000000..fb664d1 --- /dev/null +++ b/converters/fsio/fsio.1 @@ -0,0 +1,343 @@ +.TH FSIO 1 "Sep 17,2019" "Foreign File System I/O" +.SH NAME +fsio \- Foreign File System I/O +.SH SYNOPSIS +.B fsio +[ +.B \-qv +] +[ +.I cmdfile +] +.br +.SH DESCRIPTION +\fBfsio\fP is a utility for manipulating foreign file systems within container +files used by various emulators such as +.B SIMH. + +If cmdfile is given, commands will be read and executed from the command file +otherwise it will prompt the user for commands with \fBfsio> \fP +.br +.TP +\fB-q\fP - Be quiet, do not output unsolicited text during processing +.TP +\fB-v\fP - Echo each command as it is read from a command file +.br +.TP +Each command occupies one line and has a common format: +.br +.RS +.TP +verb [switches] args ... +.RE +.TP +The following verbs are supported: + +.br +.B "\fImount\fP \- make a container file available to fsio" +.br +.B "\fIumount\fP \- remove knowledge of a container file from fsio" +.br +.B "\fInewfs\fP \- create and new container and empty file system" +.br +.B "\fIset\fP \- set parameters on a mounted file system" +.br +.B "\fIinfo\fP \- display information about the container file system" +.br +.B "\fIdir\fP \- list a directory" +.br +.B "\fIdump\fP \- dump a file in hex or octal" +.br +.B "\fIcopy\fP \- copy a single file" +.br +.B "\fItype\fP \- type a file on the terminal" +.br +.B "\fIdelete\fP \- delete a file" +.br +.B "\fIstatus\fP \- display currently mounted file systems" +.br +.B "\fIdo\fP \- echo and execute commands from a file" +.br +.B "\fIhelp\fP \- display help on using fsio" +.br +.B "\fIexit\fP \- terminate fsio (quit is an alias for exit)" +.br +.TP +The following commands are only accepted by file systems which are on magtape devices: + +.br +.B "\fIrewind\fP \- position the tape to the start of the data stream" +.br +.B "\fIeom\fP \- position the tape to the end of the data stream" +.br +.B "\fIskipf\fP \- position the tape by skipping forward over files" +.br +.B "\fIskipr\fP \- position the tape by skipping backward over files" +.br +.SH COMMANDS +.TP +.B "\fImount\fP [-dfrx] [-t type] dev[:] file type" +Make the container file available to fsio. +.br +.RS +.RS +.B "\fI\-d\fP \- generate debug output on stdout" +.br +.B " Use environment variable \fIFSioDebugLog\fP to" +.br +.B " override stdout" +.br +.B " Only available if built with DEBUG enabled" +.br +.B "\fI\-f\fP \- bypass home block validation (RT-11 only)" +.br +.B "\fI\-r\fP \- mount file system read-only" +.br +.B "\fI\-t type\fP \- specify optional disk type" +.br +.B "\fI\-x\fP \- dosmt will use extended filenames when writing" +.br +.B "\fIdev[:]\fP \- user supplied name for the mount" +.br +.B "\fIfile\fP \- name of the container file" +.br +.B "\fItype\fP \- type of container file system" +.br +.RE +.RE +.TP +.B "\fIumount\fP dev[:]" +Remove knowledge of the container file from fsio. +.br +.RS +.RS +.B "\fIdev[:]\fP \- name supplied on a previous mount" +.RE +.RE +.TP +.B "\fInewfs\fP [-t type] [-e count] file type" +Create an new container with an empty file system. +.br +.RS +.RS +.B "\fI\-t type\fP \- use alternate, file-system dependent size" +.br +.B "\fI\-e n\fP \- Specify extra space for directory entries (RT11, OS/8)" +.br +.B "\fIfile\fP \- name of the container file" +.br +.B "\fItype\fP \- type of container file system" +.br +.RE +.RE +.TP +.B "\fIset\fP dev: args ..." +Set parameters on a mounted file system. +.br +.RS +.RS +.B "\fIdev:\fP \- name supplied on a previous mount" +.br +.B "\fIargs ...\fP\- arguments are passed on to the file system" +.br +.RE +.RE +.TP +.B "\fIinfo\fP dev:" +Display information about the file system(s) within the container file. +.br +.RS +.RS +.B "\fIdev:\fP \- name supplied on a previous mount" +.RE +.RE +.TP +.B "\fIdir\fP [-fn] dev:dirspec" +List the contents of a specific directory. +.br +.RS +.RS +.B "\fI\-f\fP \- display a full (vs. brief) directory" +.br +.B "\fI\-n\fP \- don't rewind tape before listing directory" +.br +.B "\fIdev:\fP \- name supplied on a previous mount" +.br +.B "\fIdirspec\fP \- filespec to display, may include wildcards" +.br +.RE +.RE +.TP +.B "\fIdump\fP [-bcdnwx] dev:filespec" +Dump the contents of the file in octal, hex or characters. +.br +.RS +.RS +.B "\fI\-b\fP \- dump byte (8-bits) at a time" +.br +.B "\fI\-c\fP \- dump in character format" +.br +.B "\fI\-d\fP \- dump double-word (32-bits) at a time" +.br +.B "\fI\-w\fP \- dump word (16-bits) at a time" +.br +.B "\fI\-x\fP \- dump in hex format (default is octal)" +.br +.B "\fI\-n\fP \- don't rewind magtape before looking for file" +.br +.B "\fIdev:\fP \- name supplied on a previous mount" +.br +.B "\fIdirspec\fP \- filespec to dump" +.br +.RE +.RE +.TP +.B "\fIcopy\fP [-anpc blks] dev1:srcfile dev2:dstfile" +Copy a file. +.br +.RS +.RS +.B "\fI\-a\fP \- copy in ASCII mode (translates line endings)" +.br +.B "\fI\-p\fP \- pad the file with NULLs" +.br +.B "\fI\-n\fP \- don't rewind magtape before looking for input file" +.br +.B "\fI\-c blks\fP \- make contiguous file of specified size" +.br +.B "\fIdev1:\fP \- name supplied on a previous mount" +.br +.B "\fIsrcfile\fP \- source file to copy" +.br +.B "\fIdev2:\fP \- name supplied on a previous mount" +.br +.B "\fIdstfile\fP \- destination file to copy" +.br +.RE +.RE +.TP +.B "\fItype\fP [-n] dev:filespec" +Types the contents of the file on stdout. +.br +.RS +.RS +.B "\fI\-n\fP \- don't rewind magtape before looking for file" +.br +.B "\fIdev:\fP \- name supplied on a previous mount" +.br +.B "\fIfilespec\fP\- filespec to type" +.br +.RE +.RE +.TP +.B "\fIdelete\fP dev:filespec" +Deletes the specified file. +.br +.RS +.RS +.B "\fIdev:\fP \- name supplied on a previous mount" +.br +.B "\fIfilespec\fP\- filespec to delete" +.br +.RE +.RE +.TP +.B "\fIstatus\fP" +Displays the currently mounted file system(s). +.br +.TP +.B "\fIdo\fP [-q] cmdFile" +Echo and execute commands from a file. +.br +.RS +.RS +.B "\fI\-q\fP \- don't echo commands as they are read" +.br +.B "\fIcmdFile\fP \- file containing fsio commands" +.br +.RE +.RE +.TP +.B "\fIhelp\fP" +Displays help text on stdout. +.br +.TP +.B "\fIexit\fP" +Causes fsio to exit (the quit command has the same effect). +.br +.TP +.B "\fIrewind\fP dev:" +Positions the device to the start of the tape. +.br +.RS +.RS +.B "\fIdev:\fP \- name supplied on a previous mount" +.br +.RE +.RE +.TP +.B "\fIeom\fP dev:" +Positions the device to the end of the tape. +.br +.RS +.RS +.B "\fIdev:\fP \- name supplied on a previous mount" +.br +.RE +.RE +.TP +.B "\fIskipf\fP dev: n" +Positions the device by skipping forward over files. +.br +.RS +.RS +.B "\fIdev:\fP \- name supplied on a previous mount" +.br +.B "\fIn\fP \- # of files to skip (must be > 0)" +.br +.RE +.RE +.TP +.B "\fIskipr\fP dev: n" +Positions the device by skipping backward over files. +.br +.RS +.RS +.B "\fIdev:\fP \- name supplied on a previous mount" +.br +.B "\fIn\fP \- # of files to skip (must be > 0)" +.br +.RE +.RE +.SH NOTES +If the "\fIdev:\fP" prefix is not present on a file specification, a file in +the host file system is used. It is also possible to use the "\fIlocal:\fP" +prefix to reference local files. + +.br +The -c switch on the copy command is used to determine the number of +contiguous blocks allocated to the destination file before starting the copy. +This function depends on the value of blks: + +.br +.B "\fI0\fP \- Use size of source file, if 0 then default to 1 block" +.br +.B "\fI!=0\fP \- Use larger of blks and size of the source file" +.br +.SH SUPPORTED FILESYSTEMS +.B "\fIdos11\fP \- DOS/BATCH-11 on RF11, RK05 or RP03" +.br +.B "\fIrt11\fP \- RT-11 including large drives with multiple partitions" +.br +.B "\fIdosmt\fP \- container file in DOS-11 magtape format" +.br +.B "\fIos8\fP \- OS/8 on RX01, RX01 or RK05" +.br +.SH SEE ALSO +.BR fsio-dos11 (1), +.BR fsio-rt11 (1) +.BR fsio-dosmt (1) +.BR fsio-os8 (1) +.SH AUTHOR +John Forecast, +.br diff --git a/converters/fsio/fsio.c b/converters/fsio/fsio.c new file mode 100644 index 0000000..57017a4 --- /dev/null +++ b/converters/fsio/fsio.c @@ -0,0 +1,2187 @@ +/* + * Copyright (C) 2018 John Forecast. All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY JOHN FORECAST "AS IS" AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/* + * Utility for manipulating files with file system container files as used + * by various emulators such as SIMH. + */ + +/* + * Foreign file system interface (9/18/2019) + * + * Each foreign file system is described by a struct FSdef which is linked + * into a master list of supported file systems in FSioInit(). Each entry + * in struct FSdef is described here: + * + * struct FSdef *next; + * + * Pointer to the next supported file system. Set up by FSioInit(). + * + * char *fstype; + * + * Pointer to the file system name. This is used by the "mount" command + * to locate a specific file system. + * + * char *descr; + * + * One line description of the file system. Output by the "help" command. + * + * uint16_t flags; + * + * Flags to control operation of the top-level code: + * + * FS_UNITVALID + * + * Some file systems (e.g. RT11) allow a container file to + * contain multiple file systems. Setting this bit allows the + * device name parser to include a unit number (8 bit, octal + * value) when referencing a file/filesystem. For example: + * + * mount dk rt11sys.dsk rt11 + * dir dk1: + * + * displays the directory of the second partition within the + * container file. + * + * FS_TAPE + * + * The file system operates on SIMH .tap format magtape + * container files. + * + * size_t blocksz; + * + * The size of a data block for this particular file system. It is + * possible for a file system to have different block sizes for + * different disks (see dos11 for an example). + * + * int (*mount)(struct mountedFS *mount); + * + * This routine is called when processing a "mount" command. The container + * file is open, and this routine should verify that it contains a valid + * file system. This routine may print out information about the file + * system. Return 1 if the file system is valid, 0 if the file system is + * invalid and -1 if the file system is invalid and an erroir message + * has already been printed. + * + * void (*umount)(struct mountedFS *mount); + * + * This routine is called when processing an "unmount" command. This + * routine is responsible for cleaning up any memory allocations, open + * files etc (the framework will close the container file after calling + * this routine). + * + * size_t (*size)(void) + * + * Return the number of byte used for the container file. The command + * line switch (-t type) may be used to override the default size. + * + * int (*newfs)(struct mountedFS *mount, size_t size); + * + * Create an empty file system in the container file provided by the + * mountedFS structure. The size of the container file is provided + * in the "size" parameter. The file system is never completely mounted + * by this command. This routine returns 1 if the file system was + * successfully created, 0 otherwise. + * + * void (*set)(struct mountedFS *mount, uint8_t unit, uint8_t present); + * + * This routine is called when processing a "set" command. The arguments + * for the "set" command are passed on to the file system and the + * command syntax is handled completely by the file system code. + * + * void (*info)(struct mountedFS *mount, uint8_t unit, uint8_t present); + * + * This routine is called when processing an "info" command. It is most + * useful for the file system plug-in developer and should output + * information about the file system layout including information which + * is not normally available to users. On file systems with + * FS_UNITVALID set (see above), "present" is non-zero if a unit number + * was included on the command line. For example, RT-11 uses "present" + * to decide whether to display information about a single file system + * ("present" 0) or all file systems in the containder ("present" 1). + * + * void (*dir)(struct mountedFS *mount, uint8_t unit, char *fname); + * + * This routine is called to process a "dir" command. It should output + * information in a format native to the file system. If the "-f" + * switch is present (SWISSET('f')), more information may be output. + * "fname" is a filename string which may be present to filter the + * output. Wild carding may be used but the plug-in is responsible + * for parsing and handling wild card characters. + * + * void *(*openFileR)(struct mountedFS *mount, uint8_t unit, char *fname); + * + * Open a file on the specified filesystem for reading. The filename + * may not include wild card characters. This routine returns an + * opaque pointer as a file handle which will be passed to the + * read/write/close routines. + * + * void *(*openFileW)(struct mountedFS *mount, uint8_t unit, + * char *fname, off_t size); + * + * Open a file on the specified filesystem for writing, similar to + * openFileR() above. "size" is an estimate of the amount of space + * needed for the file (in bytes) which may be used to pre-allocate + * disk space. A value of 0 indicates that the system is unable to + * determine the amount of space required (e.g. input from /dev/tty). + * + * off_t (*fileSize)(void *filep); + * + * Returns the size, in bytes, of a file currently open for reading. + * If it is not possible to determine the actual file size, return 0. + * In all other cases, the returned value may over-estimate, but not + * under-estimate the size of the file. For example, linked files + * on DOS-11 will overestimate the size by 2 bytes/block. + * + * void (*closeFile)(void *filep); + * + * Close a file which is currently open for reading/writing. + * + * size_t (*readFile)(void *filep, void *buf, size_t buflen); + * + * Read a maximum of "buflen" bytes from the file into the buffer. + * If ASCII mode is selected by the "-a" switch (SWISSET('a')), the + * read request should terminate at record (line) boundaries and the + * buffer should be terminated with a character pair. Other + * file system dependent processing may occur in ASCII mode, e.g. + * DOS-11 masks characters down to 7-bits while RT-11 looks for ^Z + * characters indicating EOF. This routine returns the number of bytes + * read from the file, which may be less than the buffer size. If the + * file is positioned at EOF when the read request is issued or an + * error occurs it should return 0. + * + * size_t (*writeFile)(void *filep, void *buf, size_t buflen); + * + * Writes a maximum of "buflen" bytes from the buffer to the file. + * If ASCII mode is selected by the "-a" switch (SWISSET('a')), + * if the last 2 characters of the buffer are they should be + * replaced by the native line ending character(s) for the file + * system. This routine returns the number of bytes written to the + * file, 0 if an error occurs. + * + * void (*deleteFile)(void *filep, char *fname) + * + * Delete a file which is currently open for reading. This routine + * should perform a closeFile() operation to free up resources. "fname" + * is the filename used when opening the file, it may be used in those + * cases where it is not possible to delete an open file (e.g. Unix). + * + * void (*rewind)(struct mountedFS *mount) + * + * This function is only valid for magtape container files. Rewind the + * container file so that it is postiioned at beginning-of-tape. + * + * void (*eom)(struct mountedFS *mount) + * + * This function is only valid for magtape container files. Position the + * container file at the logical end-of-media so that subsequent write + * operations will append data to the tape. + * + * void (*skipforw)(struct mountedFS *mount, unsigned long count) + * + * This function is only valid for magtape container files. Skip formward + * over the specified number of files or until end-of-media is reached. + * + * void (*skiprev)(struct mountedFS *mount, unsigned long count) + * + * This function is only valid for magtape container files. Skip backwards + * over the specified number of files or until beginning-of-tape is + * reached. + * + * + * Mounting a file system creates a struct mountedFS which is provided to + * most routines. Each entry is described here: + * + * struct mountedFS *next; + * + * Pointer to the next mounted file system or NULL. + * + * char name[MAX_DEVLEN+1]; + * + * The name provided by the user on the "mount" command. MAX_DEVLEN + * is defined as 16. + * + * struct FSdef *filesys; + * + * The file system used by this mount. + * + * size_t blocksz; + * + * Block size in use for this file system. + * + * uint16_t flags; + * + * Flags to control operation of the file system code: + * + * FS_READONLY + * + * The file system is mounted read-only and no changes will + * be allowed. + * + * FS_DEBUG + * + * Enables debug output. Such debug code may not be included + * in the default build. + * + * FILE *container; + * + * The open file handle for performing I/O on file system(s). + * + * union {} FSdata; + * + * Private region for use by the file system code. + */ +#include +#include +#include +#include +#include +#include + +#include "fsio.h" + +/* + * By default, fsio will use the GNU readline library. If you do not have + * it available or do not wish to use it, comment out the following line. + * You may also need to comment out the LIBS= line in Makefile. + */ +#define USE_READLINE + +#ifdef USE_READLINE +#include +#include +#endif + +int verbose = 0, quiet = 0; + +#define MAX_CMDLEN 512 + +static void doMount(void); +static void doUmount(void); +static void doNewfs(void); +static void doSet(void); +static void doInfo(void); +static void doDir(void); +static void doDump(void); +static void doCopy(void); +static void doType(void); +static void doDelete(void); +static void doStatus(void); +static void doDo(void); +static void doHelp(void); +static void doExit(void); +static void doRewind(void); +static void doEOM(void); +static void doSkipForw(void); +static void doSkipRev(void); + +typedef void (*cmd_t)(void); + +struct command { + char *name; /* Command name string */ + char *switches; /* Command switches (getopt format) */ + int minargs; /* Required argument count */ + int maxargs; /* Maximum argument count */ + int flags; /* Command flags */ + cmd_t func; /* Command execution function */ +} cmdTable[] = { +#ifdef DEBUG + { "mount", OPTIONS("dfrt:x"), 3, 3, 0, doMount }, +#else + { "mount", OPTIONS("frt:x"), 3, 3, 0, doMount }, +#endif + { "umount", NULL, 1, 1, 0, doUmount }, + { "newfs", OPTIONS("e:t:"), 2, 2, 0, doNewfs }, + { "set", NULL, 2, MAX_CMDLEN, 0, doSet }, + { "info", NULL, 1, 1, 0, doInfo }, + { "dir", OPTIONS("fn"), 1, 1, 0, doDir }, + { "dump", OPTIONS("bcdnwx"), 1, 1, 0, doDump }, + { "copy", OPTIONS("ac:np"), 2, 2, 0, doCopy }, + { "type", OPTIONS("n"), 1, 1, 0, doType }, + { "delete", NULL, 1, 1, 0, doDelete }, + { "status", NULL, 0, 0, 0, doStatus }, + { "do", OPTIONS("q"), 1, 1, 0, doDo }, + { "help", NULL, 0, 0, 0, doHelp }, + { "exit", NULL, 0, 0, 0, doExit }, + { "quit", NULL, 0, 0, 0, doExit }, + { "rewind", NULL, 1, 1, 0, doRewind }, + { "eom", NULL, 1, 1, 0, doEOM }, + { "skipf", NULL, 2, 2, 0, doSkipForw }, + { "skipr", NULL, 2, 2, 0, doSkipRev }, + { NULL, NULL, 0, 0, 0, doExit } +}; + +/* + * Switches and values + */ +uint32_t swPresent; +char *swValue[26]; + +/* + * Command argument strings + */ +int args; +char **words, *wds[MAX_CMDLEN]; + +#ifdef DEBUG +FILE *DEBUGout = NULL; +#endif + +struct mountedFS *mounts; + +struct FSdef *fileSystems = NULL; + +/* + * Tables for use with bitmap allocators + */ +uint16_t bits[16] = { + 0000001, 0000002, 0000004, 0000010, 0000020, 0000040, 0000100, 0000200, + 0000400, 0001000, 0002000, 0004000, 0010000, 0020000, 0040000, 0100000 +}; + +uint16_t lowbits[16] = { + 0000001, 0000003, 0000007, 0000017, 0000037, 0000077, 0000177, 0000377, + 0000777, 0001777, 0003777, 0007777, 0017777, 0037777, 0077777, 0177777 +}; + +uint16_t highbits[16] = { + 0100000, 0140000, 0160000, 0170000, 0174000, 0176000, 0177000, 0177400, + 0177600, 0177700, 0177740, 0177760, 0177770, 0177774, 0177776, 0177777 +}; + +/* + * Table of # of zeroes in each byte value. + */ +uint8_t zeroes[256] = { + 8, 7, 7, 6, 7, 6, 6, 5, 7, 6, 6, 5, 6, 5, 5, 4, + 7, 6, 6, 5, 6, 5, 5, 4, 6, 5, 5, 4, 5, 4, 4, 3, + 7, 6, 6, 5, 6, 5, 5, 4, 6, 5, 5, 4, 5, 4, 4, 3, + 6, 5, 5, 4, 5, 4, 4, 3, 5, 4, 4, 3, 4, 3, 3, 2, + 7, 6, 6, 5, 6, 5, 5, 4, 6, 5, 5, 4, 5, 4, 4, 3, + 6, 5, 5, 4, 5, 4, 4, 3, 5, 4, 4, 3, 4, 3, 3, 2, + 6, 5, 5, 4, 5, 4, 4, 3, 5, 4, 4, 3, 4, 3, 3, 2, + 5, 4, 4, 3, 4, 3, 3, 2, 4, 3, 3, 2, 3, 2, 2, 1, + 7, 6, 6, 5, 6, 5, 5, 4, 6, 5, 5, 4, 5, 4, 4, 3, + 6, 5, 5, 4, 5, 4, 4, 3, 5, 4, 4, 3, 4, 3, 3, 2, + 6, 5, 5, 4, 5, 4, 4, 3, 5, 4, 4, 3, 4, 3, 3, 2, + 5, 4, 4, 3, 4, 3, 3, 2, 4, 3, 3, 2, 3, 2, 2, 1, + 6, 5, 5, 4, 5, 4, 4, 3, 5, 4, 4, 3, 4, 3, 3, 2, + 5, 4, 4, 3, 4, 3, 3, 2, 4, 3, 3, 2, 3, 2, 2, 1, + 5, 4, 4, 3, 4, 3, 3, 2, 4, 3, 3, 2, 3, 2, 2, 1, + 4, 3, 3, 2, 3, 2, 2, 1, 3, 2, 2, 1, 2, 1, 1, 0, +}; + +void FSioCommands(FILE *); +static void FSioExecute(char *); + +extern struct mountedFS localMount; + +extern struct FSdef localFS; +extern struct FSdef dos11FS; +extern struct FSdef rt11FS; +extern struct FSdef dosmtFS; +extern struct FSdef os8FS; + +#define MAX(a, b) (((a) > (b)) ? (a) : (b)) +#define MIN(a, b) (((a) < (b)) ? (a) : (b)) + +/*++ + * F S i o I n i t + * + * Perform once-only initialization. + * + * Inputs: + * + * None + * + * Outputs: + * + * None + * + * Returns: + * + * None + * + --*/ +static void FSioInit(void) +{ + mounts = NULL; + + /* + * Initialize the the local access device + */ + strcpy(localMount.name, "local"); + localMount.filesys = &localFS; + localMount.blocksz = 0; /*** TODO ***/ + localMount.container = NULL; + + /* + * Add the local file access device. + */ + localMount.next = mounts; + mounts = &localMount; + + /* + * Add supported file systems + */ + localFS.next = fileSystems; + fileSystems = &localFS; + + dos11FS.next = fileSystems; + fileSystems = &dos11FS; + + rt11FS.next = fileSystems; + fileSystems = &rt11FS; + + dosmtFS.next = fileSystems; + fileSystems = &dosmtFS; + + os8FS.next = fileSystems; + fileSystems = &os8FS; +} + +/*++ + * U s a g e + * + * Display a usage message and exit. + * + * Inputs: + * + * None + * + * Outputs: + * + * None + * + * Returns: + * + * Never returns + * + --*/ +void Usage(void) +{ + fprintf(stderr, "Usage: fsio [cmdFile]\n"); + exit(1); +} + +/*++ + * m a i n + * + * Start routine for the fsio application. + * + * Inputs: + * + * argc - # of supplied arguments + * argv - array of argument strings + * + * Outputs: + * + * None + * + * Returns: + * + * Exit status + * + --*/ +int main( + int argc, + char **argv +) +{ + FILE *commands = stdin; + int ch; + + FSioInit(); + + /* + * Process command line switches + */ + while ((ch = getopt(argc, argv, "qv")) != -1) { + switch (ch) { + case 'q': + quiet = 1; + break; + + case 'v': + verbose = 1; + break; + + default: + Usage(); + } + } + + argc -= optind; + argv += optind; + +#if !defined(__linux__) + optreset = 1; +#endif + optind = 1; + + if (argc <= 1) { + if (argc == 1) { + commands = fopen(argv[0], "r"); + if (commands == NULL) { + fprintf(stderr, "Failed to open \"%s\": %s\n", + argv[0], strerror(errno)); + exit(2); + } + } + FSioCommands(commands); + } else Usage(); + + return 0; +} + +/*++ + * c h e c k D e v + * + * Check the validity of a device name. An error message will be generated + * if it is invalid. + * + * Inputs: + * + * dev - pointer to device name + * + * Outputs: + * + * None + * + * Returns: + * + * 1 if device name valid, 0 otherwise + * + --*/ +static int checkDev( + char *dev +) +{ + unsigned int i; + + /* + * Remove any trailing ':' characters + */ + while ((strlen(dev) != 0) && (dev[strlen(dev) - 1] == ':')) + dev[strlen(dev) - 1] = '\0'; + + for (i = 0; i < strlen(dev); i++) + if (!isalpha(dev[i])) { + fprintf(stderr, + "%s: Device name contains non-alpha character\n", wds[0]); + return 0; + } + return 1; +} + +/*++ + * f i n d M o u n t + * + * Given a full device + file specification, find the mount point from + * the device name, parse out the optional unit number and return the + * remaining file specification. If no device + file specification is + * present, assume the file specification is for the local file system. + * + * Inputs: + * + * cmd - current command name + * dev - pointer to device name + file specification + * unit - return optional unit number here + * present - return unit present indicator here (0 or 1) + * fname - return remaining file specification here + * + * Outputs: + * + * None + * + * Returns: + * + * Pointer to the mounted file system structure, NULL if not found, unit + * number invalid or other parse error + * + --*/ +static struct mountedFS *findMount( + char *cmd, + char *dev, + uint8_t *unit, + uint8_t *present, + char **fname +) +{ + struct mountedFS *fs = &localMount; + unsigned long unitno = 0; + uint8_t unitpresent = 0; + unsigned int i; + char *endptr, *ptr; + char origdev[32]; + + if ((ptr = strchr(dev, ':')) != NULL) { + *ptr++ = '\0'; + + strncpy(origdev, dev, sizeof(origdev)); + + /* + * Determine validity of the device name and whether a unit number is + * present. + */ + for (i = 0; i < strlen(dev); i++) + if (!isalpha(dev[i])) { + if (isdigit(dev[i])) { + unitno = strtoul(&dev[i], &endptr, 8); + if (*endptr != '\0') { + fprintf(stderr, + "%s: Device \"%s\" unit number invalid\n", cmd, origdev); + return NULL; + } + + if (unitno > 0377) { + fprintf(stderr, + "%s: Device \"%s\" unit number too large\n", cmd, origdev); + return NULL; + } + dev[i] ='\0'; + unitpresent = 1; + break; + } + fprintf(stderr, + "%s: Device \"%s\" contains non-alpha character\n", + cmd, origdev); + return NULL; + } + + /* + * Search the mounted file system list. + */ + for (fs = mounts; fs != NULL; fs = fs->next) + if (strcmp(dev, fs->name) == 0) { + if ((unitpresent == 0) || + ((fs->filesys->flags & FS_UNITVALID) != 0)) + break; + fprintf(stderr, + "%s: \"%s\" does not support unit numbers\n", cmd, fs->name); + return NULL; + } + + if (fs == NULL) + fprintf(stderr, "%s: Device \"%s\" not mounted\n", cmd, origdev); + } else { + if (getenv("FSioForceLocal") != NULL) { + fprintf(stderr, "Local file system access requires use of \"local:\"\n"); + return NULL; + } + ptr = dev; + } + + /* + * No device present - assume local file system usage + */ + *unit = unitno & 0377; + *present = unitpresent; + *fname = ptr; + return fs; +} + +/*++ + * l o o k u p D e v + * + * Search the mounted device table for a specified name. + * + * Inputs: + * + * dev - pointer to the device name + * + * Outputs: + * + * None + * + * Returns: + * + * Pointer to the mounted file system structure, NULL if not found or + * unit number invalid + * + --*/ +static struct mountedFS *lookupDev( + char *dev +) +{ + struct mountedFS *fs; + + for (fs = mounts; fs != NULL; fs = fs->next) + if (strcmp(dev, fs->name) == 0) + return fs; + + return NULL; +} + +/*++ + * l o o k u p F S + * + * Search the list of supported file systems for a specific type. + * + * Inputs: + * + * fs - pointer to the file system type + * + * Outputs: + * + * None + * + * Returns: + * + * Pointer to the file system definition, NULL if not found + * + --*/ +static struct FSdef *lookupFS( + char *fs +) +{ + struct FSdef *filesys; + + for (filesys = fileSystems; filesys != NULL; filesys = filesys->next) + if (strcmp(fs, filesys->fstype) == 0) + return filesys; + + return NULL; +} + +/*++ + * d o M o u n t + * + * Mount command processing routine. + * + * Inputs: + * + * None + * + * Outputs: + * + * None + * + * Returns: + * + * None + * + --*/ +static void doMount(void) +{ + struct FSdef *filesys; + FILE *container; + int status; + char *mode = SWISSET('r') ? "r" : "r+"; + + if (checkDev(words[0]) != 0) { + if (lookupDev(words[0]) == NULL) { + if ((filesys = lookupFS(words[2])) != NULL) { + if (filesys->mount == NULL) { + fprintf(stderr, + "mount: \"%s\" is not a mountable filesystem\n", words[2]); + return; + } + + if ((container = fopen(words[1], mode)) != NULL) { + struct mountedFS *mount; + + if ((mount = malloc(sizeof(struct mountedFS))) != NULL) { + memset(mount, 0, sizeof(struct mountedFS)); + + strcpy(mount->name, words[0]); + mount->filesys = filesys; + mount->blocksz = filesys->blocksz; + mount->flags = SWISSET('r') ? FS_READONLY : 0; +#ifdef DEBUG + if (SWISSET('d')) { + mount->flags |= FS_DEBUG; + + if (DEBUGout == NULL) { + char *dbg = getenv("FSioDebugLog"); + + if (dbg != NULL) + DEBUGout = fopen(dbg, "a"); + } + + if (DEBUGout == NULL) + DEBUGout = stdout; + } +#endif + mount->container = container; + mount->skip = 0; + + /* + * Verify that the container holds a valid file system + */ + if ((status = (*filesys->mount)(mount)) > 0) { + mount->next = mounts; + mounts = mount; + return; + } + free(mount); + if (status == 0) + fprintf(stderr, + "mount: \"%s\" does not contain a valid file system\n", + words[1]); + } else fprintf(stderr, "mount: out of memory\n"); + fclose(container); + } else fprintf(stderr, "mount: failed to open \"%s\"\n", words[1]); + } else fprintf(stderr, "mount: \"%s\" is not a supported file system type\n", words[2]); + } else fprintf(stderr, "mount: \"%s\" already mounted\n", words[0]); + } +} + +/*++ + * d o U m o u n t + * + * Umount command processing routine. + * + * Inputs: + * + * None + * + * Outputs: + * + * None + * + * Returns: + * + * None + * + --*/ +static void doUmount(void) +{ + struct mountedFS *mount, **ptr = &mounts; + + if (checkDev(words[0]) != 0) { + if ((mount = lookupDev(words[0])) != NULL) { + if (mount->filesys->umount == NULL) { + fprintf(stderr, "umount: \"%s\" may not be unmounted\n", words[0]); + return; + } + + for (ptr = &mounts; *ptr != NULL; ptr = &((*ptr)->next)) + if (*ptr == mount) { + *ptr = mount->next; + (*mount->filesys->umount)(mount); + fclose(mount->container); + free(mount); + return; + } + fprintf(stderr, "Mounted file system list corrupted\n"); + exit(10); + } else fprintf(stderr, "umount: \"%s\" is not mounted\n", words[0]); + } +} + +/*++ + * d o N e w f s + * + * Create a new container file and build an empty file system on it. + * + * Inputs: + * + * None + * + * Outputs: + * + * None + * + * Returns: + * + * None + * + --*/ +static void doNewfs(void) +{ + struct FSdef *filesys; + struct mountedFS mount; + char ch = 0; + int status; + size_t size = 0; + + if ((filesys = lookupFS(words[1])) != NULL) { + if ((filesys->newfs == NULL) && ((filesys->flags & FS_EMPTYFILE) == 0)) { + fprintf(stderr, + "newfs: \"%s\" does not support the newfs command\n", words[1]); + return; + } + + if (access(words[0], F_OK) == 0) { + fprintf(stderr, "newfs: \"%s\" already exists\n", words[0]); + return; + } + + memset(&mount, 0, sizeof(mount)); + + /* + * Get the required container file size. + */ + if (filesys->size != NULL) + size = (*filesys->size)(); + + if ((mount.container = fopen(words[0], "w+")) != NULL) { + off_t offset = size - 1; + + mount.skip = 0; + + /* + * If an empty file is valid, we are all done + */ + if ((filesys->flags & FS_EMPTYFILE) != 0) + return; + + if ((fseeko(mount.container, offset + mount.skip, SEEK_SET) == 0) && + (fwrite(&ch, 1, 1, mount.container) == 1)) { + mount.filesys = filesys; + mount.blocksz = filesys->blocksz; + + status = (*filesys->newfs)(&mount, size); + + fclose(mount.container); + if (status == 0) + unlink(words[0]); + } else { + fprintf(stderr, "newfs: failed to extend \"%s\"\n", words[0]); + fclose(mount.container); + unlink(words[0]); + } + } else fprintf(stderr, "newfs: failed to create \"%s\"\n", words[0]); + } else fprintf(stderr, "newfs: \"%s\" is not a supported file system type\n", words[1]); +} + +/*++ + * d o S e t + * + * Set command processing routine. + * + * Inputs: + * + * None + * + * Outputs: + * + * None + * + * Returns: + * + * None + * + --*/ +static void doSet(void) +{ + struct mountedFS *mount; + char *fname; + uint8_t unit, present; + + if ((mount = findMount("set", words[0], &unit, &present, &fname)) != NULL) { + if (*fname == '\0') { + if (mount->filesys->set != NULL) + (*mount->filesys->set)(mount, unit, present); + else fprintf(stderr, "set: \"%s\" does not support \"set\" command\n", + mount->filesys->fstype); + } else fprintf(stderr, "set: Does not expect a file name\n"); + } +} + +/*++ + * d o I n f o + * + * Info command processing routine. + * + * Inputs: + * + * None + * + * Outputs: + * + * None + * + * Returns: + * + * None + * + --*/ +static void doInfo(void) +{ + struct mountedFS *mount; + char *fname; + uint8_t unit, present; + + if ((mount = findMount("info", words[0], &unit, &present, &fname)) != NULL) { + if (*fname == '\0') + (*mount->filesys->info)(mount, unit, present); + else fprintf(stderr, "info: Does not expect a file name\n"); + } +} + +/*++ + * d o D i r + * + * Dir command processing routine. + * + * Inputs: + * + * None + * + * Outputs: + * + * None + * + * Returns: + * + * None + * + --*/ +static void doDir(void) +{ + struct mountedFS *mount; + char *fname; + uint8_t unit, present; + + if ((mount = findMount("dir", words[0], &unit, &present, &fname)) != NULL) { + (*mount->filesys->dir)(mount, unit, fname); + return; + } +} + +/*++ + * d o D u m p + * + * Dump command processing routine. + * + * Inputs: + * + * None + * + * Outputs: + * + * None + * + * Returns: + * + * None + * + --*/ +static void doDump(void) +{ + struct mountedFS *mount; + char *fname; + void *file; + uint8_t unit, present; + + if ((mount = findMount("dump", words[0], &unit, &present, &fname)) != NULL) { + if ((file = (*mount->filesys->openFileR)(mount, unit, fname)) != NULL) { + unsigned int offset = 0, datasz = 2; + uint8_t data1; + uint16_t data2; + uint32_t data4; + char output[128]; + + if (SWISSET('b') || SWISSET('c')) + datasz = 1; + else if (SWISSET('d')) + datasz = 4; + else if (SWISSET('w')) + datasz = 2; + + output[0] = '\0'; + + switch (datasz) { + case 1: + while ((*mount->filesys->readFile)(file, &data1, 1) != 0) { + if (SWISSET('b')) + sprintf(&output[strlen(output)], + SWISSET('x') ? " 0x%02X" : " 0%03o", data1); + else sprintf(&output[strlen(output)], " %s ", Ascii[data1 & 0177]); + + if ((offset & 07) == 07) { + if (mount->blocksz != 0) + printf("%s%011o %s\n", + ((offset & ~07) % mount->blocksz) == 0 ? "\n" : "", + offset & ~07, output); + else printf("%011o %s\n", offset & ~07, output); + output[0] = '\0'; + } + offset++; + } + + if (output[0] != '\0') + printf("%011o %s\n", offset & ~07, output); + break; + + case 2: + while ((*mount->filesys->readFile)(file, &data2, 2) != 0) { + sprintf(&output[strlen(output)], + SWISSET('x') ? " 0x%04X" : " %07o", data2); + + if ((offset & 017) == 016) { + if (mount->blocksz != 0) + printf("%s%011o %s\n", + ((offset & ~017) % mount->blocksz) == 0 ? "\n" : "", + offset & ~017, output); + else printf("%011o %s\n", offset & ~017, output); + output[0] = '\0'; + } + offset += 2; + data2 = 0; + } + + if (output[0] != '\0') + printf("%011o %s\n", offset & ~07, output); + break; + + case 4: + while ((*mount->filesys->readFile)(file, &data4, 4) != 0) { + sprintf(&output[strlen(output)], + SWISSET('x') ? " 0x%08X" : " %011o", data2); + + if ((offset & 017) == 014) { + if (mount->blocksz != 0) + printf("%s%011o %s\n", + ((offset & ~017) % mount->blocksz) == 0 ? "\n" : "", + offset & ~017, output); + else printf("%011o %s\n", offset & ~017, output); + output[0] = '\0'; + } + offset += 4; + data2 = 0; + } + + if (output[0] != '\0') + printf("%011o %s\n", offset & ~07, output); + break; + } + /* + * Close the file + */ + (*mount->filesys->closeFile)(file); + } else fprintf(stderr, "dump: \"%s\" no such file\n", fname); + } +} + +/*++ + * d o C o p y + * + * Copy command processing routine. + * + * Inputs: + * + * None + * + * Outputs: + * + * None + * + * Returns: + * + * None + * + --*/ +#define BFRSIZ 512 + +static void doCopy(void) +{ + struct mountedFS *mountSrc, *mountDest; + struct FSdef *fsSrc, *fsDest; + uint8_t unitSrc, unitDest, presentSrc, presentDest; + char *fnameSrc, *fnameDest; + void *fileSrc, *fileDest; + char *endptr; + + mountSrc = findMount("copy", words[0], &unitSrc, &presentSrc, &fnameSrc); + mountDest = findMount("copy", words[1], &unitDest, &presentDest, &fnameDest); + + if ((mountSrc != NULL) && (mountDest != NULL)) { + fsSrc = mountSrc->filesys; + fsDest = mountDest->filesys; + + if (mountSrc == mountDest) { + if ((fsDest->flags & FS_1OPENFILE) != 0) { + fprintf(stderr, + "copy: \"%s\" does not allow simultaneous read/write access\n", + mountDest->name); + return; + } + } + + if ((mountDest->flags & FS_READONLY) != 0) { + fprintf(stderr, "copy: \"%s\" is mounted read-only\n", mountDest->name); + return; + } + fsSrc = mountSrc->filesys; + fsDest = mountDest->filesys; + + if ((fileSrc = (*fsSrc->openFileR)(mountSrc, unitSrc, fnameSrc)) != NULL) { + off_t size = (*fsSrc->fileSize)(fileSrc); + unsigned long contig; + + if (SWISSET('c')) { + contig = strtoul(SWGETVAL('c'), &endptr, 10); + if (*endptr != '\0') { + fprintf(stderr, "copy: Invalid character in '-c' argument\n"); + (*fsSrc->closeFile)(fileSrc); + return; + } + if (contig != 0) + size = MAX(size, (long)(contig * mountDest->blocksz)); + } + + if ((fileDest = (*fsDest->openFileW)(mountDest, unitDest, fnameDest, size)) != NULL) { + char buf[BFRSIZ]; + size_t len; + + while ((len = (*fsSrc->readFile)(fileSrc, buf, BFRSIZ)) != 0) { + if ((*fsDest->writeFile)(fileDest, buf, len) == 0) { + fprintf(stderr, "copy: Error writing \"%s\"\n", fnameDest); + break; + } + } + (*fsDest->closeFile)(fileDest); + (*fsSrc->closeFile)(fileSrc); + } else { + fprintf(stderr, + "copy: failed to open \"%s\" for write\n", fnameDest); + (*fsSrc->closeFile)(fileSrc); + } + } else fprintf(stderr, + "copy: failed to open \"%s\" for read\n", fnameSrc); + } +} + +/*++ + * d o T y p e + * + * Type command processing routine. + * + * Inputs: + * + * None + * + * Outputs: + * + * None + * + * Returns: + * + * None + * + --*/ +static void doType(void) +{ + struct mountedFS *mount; + char *fname; + void *file; + uint8_t unit, present; + + /* + * Force ASCII mode + */ + SWSET('a'); + + if ((mount = findMount("type", words[0], &unit, &present, &fname)) != NULL) { + if ((file = (*mount->filesys->openFileR)(mount, unit, fname)) != NULL) { + char buf[BFRSIZ]; + size_t len, i; + + while ((len = (*mount->filesys->readFile)(file, buf, BFRSIZ)) != 0) { + for (i = 0; i < len; i++) + if (buf[i] != '\0') + putchar(buf[i]); + } + + /* + * Close the file + */ + (*mount->filesys->closeFile)(file); + } else fprintf(stderr, "type: \"%s\" no such file\n", fname); + } +} + +/*++ + * d o D e l e t e + * + * Delete command processing routine. + * + * Inputs: + * + * None + * + * Outputs: + * + * None + * + * Returns: + * + * None + * + --*/ +static void doDelete(void) +{ + struct mountedFS *mount; + char *fname; + void *file; + uint8_t unit, present; + + if ((mount = findMount("delete", words[0], &unit, &present, &fname)) != NULL) { + if (mount->filesys->deleteFile != NULL) { + if ((mount->flags & FS_READONLY) == 0) { + if ((file = (*mount->filesys->openFileR)(mount, unit, fname)) != NULL) + (*mount->filesys->deleteFile)(file, fname); + else fprintf(stderr, "delete: \"%s\" no such file\n", fname); + } else fprintf(stderr, "delete: \"%s\" is mounted read-only\n", mount->name); + } else fprintf(stderr, "delete: Function not supported\n"); + } +} + +/*++ + * d o S t a t u s + * + * Display the current status of all mounted file systems. + * + * Inputs: + * + * None + * + * Outputs: + * + * None + * + * Returns: + * + * None + * + --*/ +static void doStatus(void) +{ + if (mounts != NULL) { + struct mountedFS *mount; + + for (mount = mounts; mount != NULL; mount = mount->next) + printf("%-20s%s\n", mount->name, mount->filesys->fstype); + } +} + +/*++ + * d o D o + * + * Execute commands from a file. + * + * Inputs: + * + * None + * + * Outputs: + * + * None + * + * Returns: + * + * None + * + --*/ +static void doDo(void) +{ + uint8_t noecho = SWISSET('q'); + FILE *cmdFile; + char buf[MAX_CMDLEN]; + + if ((cmdFile = fopen(words[0], "r")) != NULL) { + for (;;) { + int len; + + if (fgets(buf, sizeof(buf), cmdFile) == NULL) { + fclose(cmdFile); + return; + } + + if (!noecho) + printf("fsio> %s", buf); + + /* + * Remove any trailing newline + */ + len = strlen(buf); + if (buf[len - 1] == '\n') + buf[len - 1] = '\0'; + + /* + * Parse the command line and execute any command found + */ + FSioExecute(buf); + } + } else fprintf(stderr, "do: failed to open \"%s\"\n", words[0]); +} + +/*++ + * d o H e l p + * + * Help command processing routine. + * + * Inputs: + * + * None + * + * Outputs: + * + * None + * + * Returns: + * + * None + * + --*/ +static void doHelp(void) +{ + struct FSdef *filesys; + + printf( + "fsio allows manipulating files within file system container files\n" + "supported by various emulators such as SIMH.\n\n" + "fsio is executed by the command:\n\n" + " fsio [cmdfile]\n\n" + "If cmdfile is present, fsio will read commands from the command file\n" + "otherwise it will prompt for input:\n\n" + " fsio>\n\n" + "A common command format is used:\n\n" + " verb [switches] arg1 arg2 ...\n\n" + "The following commands are supported:\n\n" + " mount [-r] [-t type] dev[:] container fstype\n\n" + "The file system container file is made available to fsio (via the dev\n" + "specifier). fstype specifies the type of the container file system.\n" + "If -r is specified, the file system will be read-only.\n" + "In some cases (e.g. OS/8) fsio is unable to determine the type of the\n" + "underlying disk so it must be specified using \"-t type\"\n\n" + " umount dev[:]\n\n" + "Remove all knowledge of the container file from fsio.\n\n" + " newfs [-t type] container fstype\n\n" + "Create a new container file with an empty file system. The \"-t type\"\n" + "switch may be used to control the size of the container file, this\n" + "switch is file-system dependent.\n\n" + " set dev: args ...\n\n" + "Set parameter(s) on a mounted file system.\n\n" + " info dev:\n\n" + "Display some internal information about a mounted file system.\n\n" + " dir [-fn] dev:filespec\n\n" + "List the contents of the specified directory/file(s). Wildcards\n" + "may be specified in the format expected by the specified file system.\n" + "By default a \"brief\" format will be displayed, if -f is specified,\n" + "a \"full\" format directory will be displayed. If -n is specified on\n" + "a magtape-based filesystem, the tape will not be rewound before\n" + "starting the listing.\n\n" + " dump [-bcdnwx] dev:filespec\n\n" + "Dump the contents of the specified file (no wildcards allowed) in some\n" + "human-readable format. The switches control the format; -b bytes, -c\n" + "characters (ASCII), -w 16-bit words and -d 32-bit double words.\n" + "By default, bytes, words and double words are dumped in octal, use -x\n" + "for hex format. If -n is specified on a magtape-based filesystem\n" + "the tape will not be rewound before looking for the specified file.\n\n" + " copy [-anc blocks] dev1:src dev2:dest\n\n" + "Copy a file between file systems. If -a is specified the copy will be\n" + "performed in ASCII mode which will translate end-of-line characters.\n" + "If \"-c blocks\" is specified and the destination file system supports\n" + "contiguous files, the specified # of contiguous file system blocks will\n" + "be allocated before starting the transfer. If -n is specified on a\n" + "magtape-based filesystem the tape will not be rewound before looking\n" + "for the source file.\n\n" + " type [-n] dev:src\n\n" + "Type the contents of the file on the terminal. This is equivalent to\n" + "the command:\n\n" + "\tcopy -a dev:src /dev/tty\n\n" + "If -n is specified on a magtape-based filesystem the tape will not be\n" + "rewound before looking for the file\n\n" + " delete dev:file\n\n" + "Delete the specified file from the container file system.\n\n" + " status\n\n" + "Display a list of the currently mounted file systems.\n\n" + " do [-q] cmdFile\n\n" + "Echo and execute commands from a file. If -q is present suppress the echo.\n\n" + " help\n\n" + "Display this help text.\n\n" + " exit\n" + " quit\n\n" + "Terminate execution.\n\n" + "The following commands are only accepted by magtape-based filesystems:\n\n" + " rewind dev:\n\n" + "Position the magtape device to the start of the tape.\n\n" + " eom dev:\n\n" + "Position the magtape past the end of the last file already on the tape.\n\n" + " skipf dev: n\n\n" + "Skip forward over n files (n > 0).\n\n" + " skipr dev: n\n\n" + "Skip backwards over n files (n > 0).\n\n" + "The special device name \"local:\" may be used to specify files in the\n" + "local host file system.\n\n" + "The following container file systems are supported:\n\n"); + + for (filesys = fileSystems; filesys != NULL; filesys = filesys->next) + printf("%s", filesys->descr); +} + +/*++ + * d o E x i t + * + * Exit (and Quit) command processing routine. + * + * Inputs: + * + * None + * + * Outputs: + * + * None + * + * Returns: + * + * None + * + --*/ +static void doExit(void) +{ + struct mountedFS *mount; + + for (mount = mounts; mount != NULL; mount = mount->next) + if (mount != &localMount) + fclose(mount->container); + +#ifdef DEBUG + if ((DEBUGout != NULL) && (DEBUGout != stdout)) + fclose(DEBUGout); +#endif + + exit(0); +} + +/*++ + * d o R e w i n d + * + * Rewind a tape container file, positioning it a=t beginning-of-tape. + * + * Inputs: + * + * None + * + * Outputs: + * + * None + * + * Returns: + * + * None + * + --*/ +static void doRewind(void) +{ + struct mountedFS *mount; + char *fname; + uint8_t unit, present; + + if ((mount = findMount("rewind", words[0], &unit, &present, &fname)) != NULL) { + if ((mount->filesys->flags & FS_TAPE) == 0) { + fprintf(stderr, "rewind: Command only valid on magtapes\n"); + return; + } + + if (*fname == '\0') + (*mount->filesys->rewind)(mount); + else fprintf(stderr, "rewind: Does not expect a file name\n"); + } +} + +/*++ + * d o E O M + * + * Position the tape at the end-of-media. + * + * Inputs: + * + * None + * + * Outputs: + * + * None + * + * Returns: + * + * None + * + --*/ +static void doEOM(void) +{ + struct mountedFS *mount; + char *fname; + uint8_t unit, present; + + if ((mount = findMount("eom", words[0], &unit, &present, &fname)) != NULL) { + if ((mount->filesys->flags & FS_TAPE) == 0) { + fprintf(stderr, "eom: Command only valid on magtapes\n"); + return; + } + + if (*fname == '\0') + (*mount->filesys->eom)(mount); + else fprintf(stderr, "eom: Does not expect a file name\n"); + } +} + +/*++ + * d o S k i p F o r w + * + * Skip forward over the specified number of files or until end-of-media is + * reached. + * + * Inputs: + * + * None + * + * Outputs: + * + * None + * + * Returns: + * + * None + * + --*/ +static void doSkipForw(void) +{ + struct mountedFS *mount; + char *fname; + uint8_t unit, present; + + if ((mount = findMount("skipf", words[0], &unit, &present, &fname)) != NULL) { + if ((mount->filesys->flags & FS_TAPE) == 0) { + fprintf(stderr, "skipf: Command only valid on magtapes\n"); + return; + } + + if (*fname == '\0') { + char *endptr; + unsigned long count = strtoul(words[1], &endptr, 10); + + if (*endptr != '\0') { + fprintf(stderr, "skipf: Invalid character in count\n"); + return; + } + + if (count != 0) + (*mount->filesys->skipforw)(mount, count); + } else fprintf(stderr, "skipf: Does not expect a file name\n"); + } +} + +/*++ + * d o S k i p R e v + * + * Skip backwards over the specified number of files or beginning-of-tape is + * reached. + * + * Inputs: + * + * None + * + * Outputs: + * + * None + * + * Returns: + * + * None + * + --*/ +static void doSkipRev(void) +{ + struct mountedFS *mount; + char *fname; + uint8_t unit, present; + + if ((mount = findMount("skipf", words[0], &unit, &present, &fname)) != NULL) { + if ((mount->filesys->flags & FS_TAPE) == 0) { + fprintf(stderr, "skipr: Command only valid on magtapes\n"); + return; + } + + if (*fname == '\0') { + char *endptr; + unsigned long count = strtoul(words[1], &endptr, 10); + + if (*endptr != '\0') { + fprintf(stderr, "skipr: Invalid character in count\n"); + return; + } + + if (count != 0) + (*mount->filesys->skiprev)(mount, count); + } else fprintf(stderr, "skipr: Does not expect a file name\n"); + } +} + +/*++ + * F S i o E x e c u t e + * + * Parse a command line and execute any command found + * + * Inputs: + * + * in - pointer to the command line (zero terminated) + * + * Outputs: + * + * None + * + * Returns: + * + * None + * + --*/ +static void FSioExecute( + char *in +) +{ + char quote; + + words = wds; + args = 0; + + /* + * Split the command line into individual words + */ + while (*in != '\0') { + switch (*in) { + case ' ': + case '\t': + in++; + continue; + + case '\'': + case '\"': + quote = *in++; + words[args++] = in; + while (*in != quote) { + if (*in == '\0') { + fprintf(stderr, "Missing terminating quote - %c%s\n", + quote, words[args - 1]); + return; + } + in++; + } + *in++ = '\0'; + break; + + case '#': + if (args == 0) + return; + /* FALLTHROUGH */ + + default: + words[args++] = in++; + while ((*in != ' ') && (*in != '\t') && (*in != '\0')) + in++; + + if (*in != '\0') + *in++ = '\0'; + break; + } + } + + if (args != 0) { + struct command *cmds = cmdTable; + int i, len, idx = -1; + + len = strlen(words[0]); + + for (cmds = cmdTable, i = 0; cmds->name != NULL; cmds++, i++) { + if (strncmp(cmds->name, words[0], len) == 0) { + if (idx != -1) { + idx = -1; + break; + } + idx = i; + } + } + + if (idx != -1) { + char *switches = cmdTable[idx].switches; + + swPresent = 0; + for (i = 0; i < 26; i++) + swValue[i] = NULL; + + /* + * Parse any switches associated with the command + */ + if (switches != NULL) { + int ch; + char *ptr; + + while ((ch = getopt(args, words, switches)) != -1) { + if ((ch == '?') || ((ptr = strchr(switches, ch)) == NULL)) { +#if !defined(__linux__) + optreset = 1; +#endif + optind = 1; + return; + } + SWSET(ch); + + if (ptr[1] == ':') + SWSETVAL(ch, optarg); + } + args -= optind; + words += optind; + + /* + * Reset getopt() for subsequent uses. + */ +#if !defined(__linux__) + optreset = 1; +#endif + optind = 1; + } else args--, words++; + + if ((args < cmdTable[idx].minargs) || (args > cmdTable[idx].maxargs)) { + fprintf(stderr, "%s: Too %s arguments\n", + wds[0], args < cmdTable[idx].minargs ? "few" : "many"); + return; + } + + /* + * Execute the command. + */ + (*cmdTable[idx].func)(); + } else fprintf(stderr, "Unknown command \"%s\"\n", words[0]); + } +} + +/*++ + * F S i o C o m m a n d s + * + * Read and process commands from the specified input stream. + * + * Inputs: + * + * commands - commands are read from this stream + * + * Outputs: + * + * None + * + * Returns: + * + * None + * + --*/ +void FSioCommands( + FILE *commands +) +{ +#ifdef USE_READLINE + char *buf, bufr[MAX_CMDLEN]; + + for (;;) { + if (commands == stdin) { + if ((buf = readline("fsio> ")) == NULL) + return; + + /* + * Don't add empty lines to history. + */ + if (*buf) + add_history(buf); + } else { + int len; + + if (fgets(bufr, sizeof(bufr), commands) == NULL) + return; + + buf = bufr; + + if (verbose) + printf("fsio> %s", bufr); + + /* + * Remove any trailing newline + */ + len = strlen(buf); + if (buf[len - 1] == '\n') + buf[len - 1] = '\0'; + } + + /* + * Parse the command line and execute any command found + */ + FSioExecute(buf); + + if (commands == stdin) + free(buf); + } +#else + char buf[MAX_CMDLEN]; + + for (;;) { + int len; + + if (isatty(fileno(commands))) + fputs("fsio> ", stdout); + + if (fgets(buf, sizeof(buf), commands) == NULL) + return; + + if (verbose && !isatty(fileno(commands))) + printf("fsio> %s", buf); + + /* + * Remove any trailing newline + */ + len = strlen(buf); + if (buf[len - 1] == '\n') + buf[len - 1] = '\0'; + + /* + * Parse the command line and execute any command found + */ + FSioExecute(buf); + } +#endif +} + +/*++ + * F S i o R e a d B l o b + * + * Read an arbitrary sized blob of binary data from the container file. The + * caller is responsible for making sure that the buffer has sufficient + * space for the blob. + * + * Inputs: + * + * mount - pointer to a mounted file system descriptor + * offset - offset in the container file to start the read + * size - size of the data to read (in bytes) + * buf - pointer to the buffer to receive the data + * + * Outputs: + * + * The buffer will be overwrittn by the contents of the blob from the + * container file system + * + * Returns: + * + * 1 if read was successful, 0 otherwise + * + --*/ +int FSioReadBlob( + struct mountedFS *mount, + off_t offset, + unsigned int size, + void *buf +) +{ + if (fseeko(mount->container, offset, SEEK_SET) == 0) + return fread(buf, size, 1, mount->container); + + return 0; +} + +/*++ + * F S i o W r i t e B l o b + * + * Write an arbitrary sized blob of binary to from the container file. + * + * Inputs: + * + * mount - pointer to a mounted file system descriptor + * offset - offset in the container file to start the write + * size - size of the data to write (in bytes) + * buf - pointer to the buffer with the data + * + * Outputs: + * + * None + * + * Returns: + * + * 1 if write was successful, 0 otherwise + * + --*/ +int FSioWriteBlob( + struct mountedFS *mount, + off_t offset, + unsigned int size, + void *buf +) +{ + if (fseeko(mount->container, offset, SEEK_SET) == 0) + return fwrite(buf, size, 1, mount->container); + + return 0; +} + +/*++ + * F S i o R e a d B l o c k + * + * Read a specified block from the container file. The caller is responsible + * for making sure that the buffer has sufficient space for the block. + * + * Inputs: + * + * mount - pointer to a mounted file system descriptor + * block - logical block # in the range 0 - N + * buf - pointer to the buffer to receive the data + * + * Outputs: + * + * The buffer will be overwritten by the contents of the block from + * the container file system + * + * Returns: + * + * 1 if read was successful, 0 otherwise + * + --*/ +int FSioReadBlock( + struct mountedFS *mount, + unsigned int block, + void *buf +) +{ + off_t offset = block * mount->blocksz; + + if (fseeko(mount->container, offset + mount->skip, SEEK_SET) == 0) + return fread(buf, mount->blocksz, 1, mount->container); + + return 0; +} + +/*++ + * F S i o W r i t e B l o c k + * + * Write a specified block to the container file. + * + * Inputs: + * + * mount - pointer to a mounted file system descriptor + * block - logical block # in the range 0 - N + * buf - pointer to the buffer containing the data + * + * Outputs: + * + * None + * + * Returns: + * + * 1 if write was successful, 0 otherwise + * + --*/ +int FSioWriteBlock( + struct mountedFS *mount, + unsigned int block, + void *buf +) +{ + off_t offset = block * mount->blocksz; + + if (fseeko(mount->container, offset + mount->skip, SEEK_SET) == 0) + return fwrite(buf, mount->blocksz, 1, mount->container); + + return 0; +} + +/*++ + * F S i o R e a d S e c t o r + * + * Read a sector of a specified size from the container. The caller is + * responsible for making sure that the buffer has sufficient space for + * the sector. This routine is used when file system blocks are constructed + * from a number of sectors which are interleaved on the physical disk (e.g. + * RX02 floppies). + * + * Inputs: + * + * mount - pointer to a mounted file system descriptor + * sector - logical sector # in the range 0 - N + * size - size of each sector (in bytes) + * buf - pointer to the buffer to receive the data + * + * Outputs: + * + * The buffer will be overwritten by the contents of the sector from + * the container file system + * + * Returns: + * + * 1 if read was successful, 0 otherwise + * + --*/ +int FSioReadSector( + struct mountedFS *mount, + unsigned int sector, + unsigned int size, + void *buf +) +{ + off_t offset = sector * size; + + if (fseeko(mount->container, offset + mount->skip, SEEK_SET) == 0) + return fread(buf, size, 1, mount->container); + + return 0; +} + +/*++ + * F S i o W r i t e S e c t o r + * + * Write a sector of a specified size to the container. This routine is used + * when file system blocks are constructed from a number of sectors which are + * interleaved on the physical disk (e.g. RX02 floppies). + * + * Inputs: + * + * mount - pointer to a mounted file system descriptor + * sector - logical sector # in the range 0 - N + * size - size of each sector (in bytes) + * buf - pointer to the buffer with the data + * + * Outputs: + * + * None + * + * Returns: + * + * 1 if write was successful, 0 otherwise + * + --*/ +int FSioWriteSector( + struct mountedFS *mount, + unsigned int sector, + unsigned int size, + void *buf +) +{ + off_t offset = sector * size; + + if (fseeko(mount->container, offset + mount->skip, SEEK_SET) == 0) + return fwrite(buf, size, 1, mount->container); + + return 0; +} diff --git a/converters/fsio/fsio.h b/converters/fsio/fsio.h new file mode 100644 index 0000000..c9f1d60 --- /dev/null +++ b/converters/fsio/fsio.h @@ -0,0 +1,174 @@ +/* + * Copyright (C) 2018 John Forecast. All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY JOHN FORECAST "AS IS" AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef __FSIO_H__ +#define __FSIO_H__ +#include +#include +#include "declib.h" + +/* + * Mode for open files + */ +enum openMode { M_RD, M_WR }; + +#include "dos11.h" +#include "rt11.h" +#include "dosmt.h" +#include "os8.h" + +/* + * All of the supported file systems are natively little endian so we only + * need a subset of the endian support macros/routines. + */ +#if defined(__APPLE__) +#include + +#define htole16(x) OSSwapHostToLittleInt16(x) +#define le16toh(x) OSSwapLittleToHostInt16(x) + +#define htole32(x) OSSwapHostToLittleInt32(x) +#define le32toh(x) OSSwapLittleToHostInt32(x) +#elif defined(__linux__) +#include +#elif defined(__NetBSD__) +#include + +#define le16toh(x) letoh16(x) + +#define le32toh(x) letoh32(x) +#elif defined(__FreeBSD__) || defined(__OpenBSD__) +#include +#endif + +#ifdef __GNUC__ +#define UNUSED(x) UNUSED_ ## x __attribute__((__unused__)) +#else +#define UNUSED(x) UNUSED_ ## x +#endif + +#define MAX_DEVLEN 16 + +#if defined(__linux__) +#define OPTIONS(s) "+" s +#else +#define OPTIONS(s) s +#endif + +extern uint32_t swPresent; +extern char *swValue[]; + +#define SWISSET(c) ((swPresent & (1 << (c - 'a'))) != 0) +#define SWSET(c) swPresent |= (1 << (c - 'a')) +#define SWGETVAL(c) (swValue[c - 'a']) +#define SWSETVAL(c, v) swValue[c - 'a'] = v + +#ifdef DEBUG +extern FILE *DEBUGout; +#define ERROR(...) { \ + fprintf(stderr, __VA_ARGS__); \ + if ((DEBUGout != NULL) && (DEBUGout != stdout)) \ + fprintf(DEBUGout, __VA_ARGS__); \ + } +#else +#define ERROR(...) fprintf(stderr, __VA_ARGS__) +#endif + +struct mountedFS; + +/* + * File system definition + */ +struct FSdef { + struct FSdef *next; /* Pointer to next file system */ + char *fstype; /* File system type name */ + char *descr; /* File system description */ + uint16_t flags; /* Flags */ + size_t blocksz; /* Default block size */ + int (*mount)(struct mountedFS *); + void (*umount)(struct mountedFS *); + size_t (*size)(void); + int (*newfs)(struct mountedFS *, size_t); + void (*set)(struct mountedFS *, uint8_t, uint8_t); + void (*info)(struct mountedFS *, uint8_t, uint8_t); + void (*dir)(struct mountedFS *, uint8_t, char *); + void *(*openFileR)(struct mountedFS *, uint8_t, char *); + void *(*openFileW)(struct mountedFS *, uint8_t, char *, off_t); + off_t (*fileSize)(void *); + void (*closeFile)(void *); + size_t (*readFile)(void *, void *, size_t); + size_t (*writeFile)(void *, void *, size_t); + void (*deleteFile)(void *, char *); + /* + * The following functions are only supported by magtape file systems. + */ + void (*rewind)(struct mountedFS *); + void (*eom)(struct mountedFS *); + void (*skipforw)(struct mountedFS *, unsigned long); + void (*skiprev)(struct mountedFS *, unsigned long); +}; +#define FS_UNITVALID 0x0001 /* Unit # valid in device name */ +#define FS_TAPE 0x0002 /* File system for magtapes */ +#define FS_EMPTYFILE 0x0004 /* Empty file is OK */ +#define FS_1OPENFILE 0x0008 /* Only support a single open file */ + +/* + * Mounted file system descriptor + */ +struct mountedFS { + struct mountedFS *next; /* Pointer to next mounted file sys */ + char name[MAX_DEVLEN + 1]; + struct FSdef *filesys; /* File system */ + size_t blocksz; /* Active block size */ + uint16_t flags; +#define FS_READONLY 0x0001 /* Mounted read-only */ +#define FS_DEBUG 0x0002 /* Debug output */ + /* Bits after 0x0080 reserved for */ + /* file system use */ + FILE *container; /* Container file access */ + off_t skip; /* Data to skip in container file */ + union { + struct DOS11data _dos11; + struct RT11data _rt11; + struct DOSMTdata _dosmt; + struct OS8data _os8; + } FSdata; +#define dos11data FSdata._dos11 +#define rt11data FSdata._rt11 +#define dosmtdata FSdata._dosmt +#define os8data FSdata._os8 +}; + +extern int FSioReadBlob(struct mountedFS *, off_t, unsigned int, void *); +extern int FSioWriteBlob(struct mountedFS *, off_t, unsigned int, void *); +extern int FSioReadBlock(struct mountedFS *, unsigned int, void *); +extern int FSioWriteBlock(struct mountedFS *, unsigned int, void *); +extern int FSioReadSector(struct mountedFS *, unsigned int, unsigned int, void *); +extern int FSioWriteSector(struct mountedFS *, unsigned int, unsigned int, void *); + +#endif diff --git a/converters/fsio/fsio.txt b/converters/fsio/fsio.txt new file mode 100644 index 0000000..f924c96 --- /dev/null +++ b/converters/fsio/fsio.txt @@ -0,0 +1,332 @@ +fsio is a utility for manipulating files within file system container files +supported by by various emulators such as SIMH. fsio is designed for Unix and +Unix-like operating systems. + +fsio is executed by the command: + + fsio [-qv] [cmdfile] + +If cmdfile is present, fsio will read commands from the command file and +echoing each command to stdout if -v . present. If the -q switch is present, +no unsolicited output will be generated by fsio. If the command file is not +present fsio will prompt for input: + + fsio> + +Commands to fsio have the following general syntax: + + verb [switches] args... + +The following verbs are supported: + +mount - makes a container file available to fsio and specifies the file + system it contains. + +umount - removes knowledge of a container file from fsio + +newfs - create a new container with an empty file system + +set - set file system parameters + +info - display information about the file system + +dir - list a directory + +dump - Dump the contents of a file in a human readable format + +copy - copies a single file + +type - types the contents of a file on the terminal + +delete - deletes a file + +status - displays the currently mounted file systems + +do - execute commands from a file + +help - display help on using fsio + +exit - terminate fsio + +The following commands are only accepted by file systems which are on magtape +devices: + +rewind - position the tape to the start of the data stream + +eom - position the tape to the end of the data stream + +skipf - position the tape by skipping forward over a number of files + +skipr - position the tape by skipping backwards over a number of files + +fsio will accept shorter versions of each verb as long as they are unique +within the command set, so c, co and cop are all synonyms for copy. + +The generic form of the "dev" argument is "DDnn:" where the ":" is optional +in some commands. DD is a sequence of upper and lower case letters and nn is +an octal number in the range 0 - 377. The length of "DDnn" must not exceed 16 +characters. In most cases the number field should not be present and will be +flagged as an error if it is. Some container file formats, e.g. RT-11, allow +multiple file systems within a single container and the number is used to +specify which file system to use. For these types of containers the following +rules apply: + + - For "mount" and "umount" commands always use "DD:" + + - For file access commands (e.g. "dir", "type" etc) use "DDnn:" ("DD:" + is treated the same as "DD0:") + + - The "info" command use the presence/absence of the number to control + what information is display. If "DD:" is used, it will display + information abount all file systems within the container. If "DDnn:" + is used, it will only display information about the specific file + system. + + +Full command syntax: + + 1. mount + + mount [-dfr] [-t type] dev[:] container type + + Make the specified container file available to fsio for I/O. + + -d Generate debug output if fsio is built with the DEBUG symbol + defined + -f Force the mount to happen even if we are unable to completely + validate the container file format + -r If present, the file system is only available for read access + + -t type Specify the type of the container file. This is only required + for OS/8 file systems where type should be one of "rx01", + "rx02" or "rk05". + + dev[:] User supplied name to be used for accessing files within the + container file. Files within the container are named by using + the syntax dev:filespec where filespec uses the native syntax + of the container file system. Files within the host file + system can be named directly without any prefix. If such a + file includes ':' in its filename, the reserved prefix local: + may be used to provide access. If you want to disallow default + access to the local filesystem, creating an environment + variable with the name "FSioForceLocal" (the value does not + matter) will require use of the "local:" prefix. + + container The name of the file containing the file system + + type The type of the file system in the container + + 2. umount + + umount dev[:] + + Removes all knowledge of the container file from fsio + + dev[:] The name of a previously mounted file system + + 3. newfs + + newfs [-t type] container type + + Create a new container file with an empty file system. The container + will be a fixed size (depending on file system type) and may not exist + prior to issuing this command. This command does not support all features + of container files and will build a fixed size container known to work + on the target O/S: + + dos11 RK05 image (2.5MB, 4800 blocks) + rt11 MSCP image (32MB, 65535 blocks) + dosmt An empty file suitable for use with any magtape controller + os8 An OS/8 file system + + -t type Use a different size for the container file. The size used + will be file system dependent. + + For rt11, the following disk types are valid: + + rl02 RL02 image (10MB, 20480 blocks) + rx20 RX20 image (512KB, 1024 blocks) + + For os8, the following disk types are valid: + + rk05 RK05 image (3248 blocks) + rx01 RX01 image (2002 sectors of 128 bytes each) + rx02 RX02 imgae (2002 sectors of 256 bytes each) + + container The name of the file to create + + type The type of the file system to create in the container + + 4. set + + set dev: args ... + + Set parameters on a mounted file system. The arguments are file system + dependent. + + dev: The name of a previously mounted file system + + 5. info + + info dev: + + Display internal information about the file system within the container + file + + dev: The name of a previously mounted file system + + 6. dir + + dir [-fn] dev:dirspec + + List the contents of a specific directory. + + -f If present, display full directory information. + + -n If present, don't rewind the magtape before listing + + dev: The name of a previosly mounted file system + dirspec Specification of the directory to list using the dev: syntax. + + 7. dump + + dump [-bcdnwx] dev:src + + Dump the contents of a specified file in some human readable format (e.g + hex, octal etc). If no switches are present, the output will be in octal + word format. If multiple size switches are set, the first in alphabetical + order will take precedence. + + -b Output byte at a time + -d Output double word (4 bytes) at a type + -c Output ASCII characters + -w Output word (2 bytes) at a time + + -x Output the data in hex + + -n Don't rewind the magtape before looking for the input file + + dev: The name of a previosly mounted file system + src The name of the source file (e.g. dp:input.dat) + + 8. copy + + copy [-anpc blocks] dev1:src dev2:dest + + Copy a file. The copy may be between file systems or within a single + filesystem including the host filesystem. + + -a Copy in ASCII mode. This performs any necessary translation + of end-of-line characters. + + -n Don't rewind the magtape before looking for the input file + + -p Pad the file with NULLs. This is target file system + dependent. + + -c blocks If the file system for the destination file supports + contiguous files, "blocks" is the number of file system + contiguous files to be allocated before starting to write + the file. This function depends on the value of "blocks": + + 0 - Use the size of the source file for the allocation, + if that is 0 (e.g. input from a keyboard) use 1. + + != 0 - Use the larger of "blocks" and the size of the + source file. + + dev1: The name of a previosly mounted file system + src The name of the source file (e.g. dk:source.file) + + dev2: The name of a previously mounted file system + dest The name of the desination file (e.g. dm:dest.file) + + Note that wildcard naming is not supported and the source and destination + file names must be fully specified. + + 9. type + + type [-n] dev:src + + Copies the specified file to the terminal. This is equivalent to + + copy -a dev:src /dev/tty + + -n Don't rewind the magtape before looking for the input file + + dev: The name of a previously mounted file system + src The name of the source file (e.g. dp:input.txt) + +10. delete + + delete dev:file + + Deletes the specified file from the file system. + + dev: The name of a previously mounted file system + file The name of the file to be deleted + +11. status + + status + + Displays the currently mounted file systems. + +12. do + + do [-q] cmdFile + + Executes commands from the specified file + + -q By default, command lines are echoed. Use -q to not echo. + +13. help + + help + + Display help text on the terminal. + +14. exit + + exit + + Terminates the fsio application. + +15. rewind + + rewind dev: + + Positions a magtape device to the start of the tape. + + dev: The name of a previously mounted file system + +16. eom + + eom dev: + + Positions a magtape device past the end of the last file already on the + tape. A subsequent copy to the device will append a new file to the tape. + + dev: The name of a previously mounted file system + +17. skipf + + skipf dev: n + + Changes the current position of the magtape device by skipping forwards + over a number of files. If end of tape is reached before the file count is + complete, the skip operation will terminate early. + + dev: The name of a previously mounted file system + n Number of files to skip (must be > 0) + +18. skipr + + skipr dev: n + + Changes the current position of the magtape device by skipping backwards + over a number of files. If beginning of tape is reached before the file + count is complete, the skip operation will terminate early. + + dev: The name of a previously mounted file system + n Number of files to skip (must be > 0) diff --git a/converters/fsio/fsioSimh.txt b/converters/fsio/fsioSimh.txt new file mode 100644 index 0000000..cc76d33 --- /dev/null +++ b/converters/fsio/fsioSimh.txt @@ -0,0 +1,317 @@ +How to use fsio to transfer files in/out of SIMH emulators +---------------------------------------------------------- + +The examples below assume a basic knowlegde of fsio (see the man pages - fsio.1 +fsio-dos11.1 and fsio-rt11.1). The following examples assume that we want to +move an ASCII formatted file "file.txt" into the target operating system. If +fsio understands the target file system (DOS-11 and RT-11 right now), we can +copy the file directly into the SIMH container file used for booting the +operating system. For other target file systems we need to transfer into a +file system that the target operating system understands, even if it is not +the native file system for that operation system. In the DEC world, this is +typically RT-11. + +Note: all of these examples assume that we are running on a Linux or Unix-like +operating system (in my case Raspbian on a Raspberry Pi). + +Native File System Support +-------------------------- + +DOS-11 +------ + +For DOS-11, fsio understands the native disk format so we can transfer +directly into the bootable SIMH disk ("system.dsk" in the examples). + + pi@host:~ $ fsio + fsio> mount dk: system.dsk dos11 + dk: successfully mounted + Total blocks: 4800, Free blocks: 3591, Interleave: 1 + fsio> copy -a file.txt dk:file.txt + fsio> quit + pi@host:~ $ + +This will copy file.txt into the default UFD ([1,1]) on the target file system. +The copy will be in ASCII mode meaning that line endings will be translated +into the character pair which DOS/BATCH-11 requires. If you wish to +transfer the file into another (existing) UFD (e.g. [200,200]) you can use +either of the following 2 options: + + fsio> copy -a file.txt dk:file.txt[200,200] + +or + fsio> set dk: uic [200,200] + fsio> copy -a file.txt dk:file.txt + +If the destination UFD does not already exist on the target file system use +the following to create the UFD: + + fsio> set dk: ufd [200,200] + fsio> copy -a file.txt dk:file.txt + +RT-11 +----- + +For RT-11, fsio understands the native disk format so we can transfer +directly into the bootable SIMH disk ("rt11sys.dsk" in the examples). Note +that RT-11 disk images can hold multiple partitions, each having a maximum +size of 32MB. Partitions are named by appending an octal number to the device +name - xx0: will be the first partition, xx1: the second etc. fsio will only +allow access to partitions which have been initialized with an RT-11 file +system. The example below shows a disk with 3 partitions but only the first +and last have been initialized. + + pi@host:~ $ fsio + fsio> mount dk: rt11sys.dsk rt11 + dk: successfully mounted (2 partitions) + + dk0: + Total blocks: 65536, Free blocks: 24279 + Directory segments: 31 (Highest 4) + Extra bytes/directory entry: 0 + dk2: + Total blocks: 65536, Free blocks: 65422 + Directory segments: 31 (Highest 1) + Extra bytes/directory entry: 0 + fsio> copy -a file.txt dk:file.txt + fsio> quit + pi@host:~ $ + +This will copy file.txt into the target file system. The copy will be in +ASCII mode meaning that line endings will be translated into the character +pair which RT-11 requires. In addition, unless the file ends +exactly on a block (512 bytes) boundary, a ^Z (octal 32) will be appended +to the file indicating end-of-file. + + +Foreign File System Support +--------------------------- + +For other target operating systems we will use an RT-11 format RL02 disk as an +intermediary for performing the transfer: + + pi@host:~ $ fsio + fsio> newfs -a xfer.dsk rt11 + fsio> mount dk: xfer.dsk rt11 + fsio> copy -a file.txt dk:file.txt + fsio> quit + pi@host:~ $ + +This will create a maximum sized, single partition RT-11 file system in a +container file called "xfer.dsk" which may be copied to the target system +running SIMH. + +Once on the target system, the commands to copy the file into the emulated +operating system will depend on that operating system and, possible, its age. + +RSX-11M/RSX-11M+ +---------------- + +Use the FLX utility to copy the file over to the Files-11 file system: + + >set /uic=[1,2] + >^E + Simulation stopped, PC: 017460 (BR 17426) + sim> att rq2 xfer.dsk + sim> c + + >all du2: + >mou du2:/for + >ins $flx + >flx [200,200]/fa=du2:file.txt/rt + >pip [200,200]file.txt/fu + + + Directory DU0:[200,200] + 2019-01-10 18:10 + + FILE.TXT;1 (6400,15) 1./1. 2019-01-10 18:09 + [1,2] [RWED,RWED,RWED,R] + + Total of 1./1. blocks in 1. file + + >dmo du2: + 18:10:28 *** DU2: -- Dismount complete + DMO -- TT0: dismounted from DU2: *** Final dismount initiated *** + >dea du2: + >^E + Simulation stopped, PC: 017460 (BR 17426) + sim> det rq2 + sim> c + + > + +Note that RSX FLX cannot handle formatted ASCII input files with an embedded +^Z character indicating end-of-file. Use the "-p" switch during the copy to +pad the file with NULLs. + +IAS +--- + +The following example assumes that you are logged into a non-console +terminal. You will still need to break in on the cosole with ^E and attach +the RT-11 transfer disk as in all the other examples. + + PDS> ALLOC DL0: + PDS> MOU/FOR DL0: + MOUNT-**Volume Information** + Device =DL0 + Class =Foreign + UIC =[201,201] + Access =[RWED,RWED,RWED,RWED] + Charac =[FOR,ATCH,DCF] + PDS> DIR DL0:/RT + + + DIRECTORY DL0: + 31-JAN-99 + + FILE .TXT 1. 12-87 + < UNUSED > 20411. + + 20411. FREE BLOCKS + + TOTAL OF 1. BLOCKS IN 1. FILES + + PDS> COPY DL:FILE.TXT/RT *.* + PDS> DIR + + + Directory DU0:[201,201] + 31-JAN-99 15:56 + + FILE.TXT;1 1. 31-JAN-99 15:56 + + Total of 1./1. blocks in 1. file + + PDS> DISM DL0: + DMO -- DL00: ** DISMOUNT COMPLETE ** + +Note that in order to use the RL02 drive from timesharing, the following +changes will need to be made to the IAS startup scripts of the PiDP-11 +distribution: + +1. [1,1]STARTUP.CMD + + Add the following lines at the start of the file: + + INS [11,1]DL + LOA DL + +2. [1,1]IASSTART.CMD + + Add the following lines after the one containing "DU0/S": + + DL0 + DL1 + +After making these changes, reboot the system and users will be able to access +the 2 RL02 drives. + + +RSTS/E +------ + +For this example I have used the "-a" on the fsio "newfs" command to create an +empty RL02 disk image. Use the "-p" switch on the fsio "copy" command to pad +the file with NULLs, otherwise the ^Z will be copied into the RSTS/E file. Use +the FIT utility to copy the file over to the RSTS/E file system: + + $ ^E + Simulation stopped, PC: 065640 (BR 65630) + sim> att rl0 xfer.dsk + sim> c + + $ run auxlib$:fit + FIT V10.1-A RSTS V10.1-L RSTS/E V10.1 + FIT>sy:=dl0:file.txt/rt11 + FIT>^Z + $ dir file.txt/fu + + Name .Typ Size Prot Access Date Time Clu RTS Pos Op/rr SY:[1,2] + FILE .TXT 1 < 60> 12-Jan-19 12-Jan-87 04:23 PM 8 RT11 214 0/0 + + Total of 1 block in 1 file in SY:[1,2] + + $ ^E + Simulation stopped, PC: 065640 (BR 65630) + sim> det rl0 + sim> c + + $ + +VMS +--- + +Use the exchange utility to copy the file over to the VMS file system: + + $^E + Simulation stopped, PC: 80B90B8D (BBC #3,26C(R3),80B90BE1) + sim> att rq1 xfer.dsk + sim> c + + $ mou/for dua1: + %MOUNT-I-MOUNTED, mounted on _ROUTER$DUA1: + $ exchange copy dua1:file.txt/volume=rt11/record=stream file.txt + %EXCHANGE-S-MOUNTED, the RT-11 volume _ROUTER$DUA1: has been mounted + $ dism dua1: + $^E + Simulation stopped, PC: 80B90936 (ASHL #1,R3,R0) + sim> det rq1 + sim> c + + $ dir file.txt + + Directory SYS$SYSROOT:[SYSMGR] + + FILE.TXT;1 + + Total of 1 file. + $ + +This can be especially useful for getting the Hobbyist licences into VMS +before you have any network running. + +TOPS-10 +------- +The RTFLX utility is available on TOPS-10 reading/writing files on an RX20 +drive (combination of RX02 drive and controller) attached to a DECsystem-2020 +(KS10). In order to access the RX20, a new system generation must be +performed. There is a help file on Tops-10 for RTFLX but no other +documentation. + + . + Simulation stopped, PC: 000001 (SOJG 6,1) + sim> att ry0 xfer.dsk + RY: buffering file in memory + sim> c + + .assign rxa0: rt: + RXA000 assigned + .r rtflx + + + RTFLX>dir + + FILE TXT 1 11-Jun-87 + + Total of 1 block in 1 file + 973 blocks free on RT: + + RTFLX>copy rt:file.txt + FILE.TXT=RT:FILE.TXT + + RTFLX>exit + + .dir *.txt + + + FILE TXT 1 <057> 11-Jun-87 DSKB: [1,2] + + .deassign rxa0: + . + +If you use the RTFLX "ZERO" command to initialize an RT-11 file system it +will create an RT-11 V2 format file system and you will need to use the "-f" +switch to force fsio to see the file system. + diff --git a/converters/fsio/local.c b/converters/fsio/local.c new file mode 100644 index 0000000..46809d7 --- /dev/null +++ b/converters/fsio/local.c @@ -0,0 +1,377 @@ +/* + * Copyright (C) 2018 John Forecast. All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY JOHN FORECAST "AS IS" AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/* + * Support routines for local file access under fsio. + */ +#include +#include +#include +#include +#include +#include + +#include "fsio.h" + +/*++ + * l o c a l I n f o + * + * Display information about the local file system. This is not supported + * by the "local" device. + * + * Inputs: + * + * mount - pointer to the mounted file system descriptor + * unit - device unit number (unused) + * present - device unit number present (unused) + * + * Outputs: + * + * None + * + * Returns: + * + * None + * + --*/ +static void localInfo( + struct mountedFS *UNUSED(mount), + uint8_t UNUSED(unit), + uint8_t UNUSED(present) +) +{ + fprintf(stderr, "info: The \"local:\" device does not support this command\n"); +} + +/*++ + * l o c a l D i r + * + * Produce a full or brief directory listing. + * + * Inputs: + * + * mount - pointer to the mounted file system descriptor + * unit - device unit number + * fname - pointer to filename string + * + * Outputs: + * + * None + * + * Returns: + * + * None + * + --*/ +static void localDir( + struct mountedFS *UNUSED(mount), + uint8_t UNUSED(unit), + char *fname +) +{ + char cmd[64]; + + sprintf(cmd, "/bin/ls %s%s\n", + SWISSET('f') ? "-l " : "", fname); + system(cmd); +} + +/*++ + * l o c a l O p e n F i l e R + * + * Open a local file for reading. + * + * Inputs: + * + * mount - pointer to the mounted file system descriptor + * unit - device unit number + * fname - pointer to filename string + * + * Outputs: + * + * None + * + * Returns: + * + * Pointer to open file descriptor, NULL if open fails + * + --*/ +static void *localOpenFileR( + struct mountedFS *UNUSED(mount), + uint8_t UNUSED(unit), + char *fname +) +{ + return fopen(fname, "r"); +} + +/*++ + * l o c a l O p e n F i l e W + * + * Open a local file for writing. + * + * Inputs: + * + * mount - pointer to the mounted file system descriptor + * unit - device unit number + * fname - pointer to filename string + * size - estimated file size (in bytes) + * + * Outputs: + * + * None + * + * Returns: + * + * Pointer to open file descriptor, NULL if open fails + * + --*/ +static void *localOpenFileW( + struct mountedFS *UNUSED(mount), + uint8_t UNUSED(unit), + char *fname, + off_t UNUSED(size) +) +{ + return fopen(fname, "w"); +} + +/*++ + * l o c a l F i l e S i z e + * + * Return the size of a currently open file. If the file is open in ASCII + * mode we do not know how many lines are present in the file and so cannot + * calculate how much additional space will be needed for a possible + * to translation. In this case we return 0. + * + * Inputs: + * + * filep - pointer to open file descriptor + * + * Outputs: + * + * None + * + * Returns: + * + * Current size of the file, 0 on error + * + --*/ +static off_t localFileSize( + void *filep +) +{ + FILE *file = filep; + struct stat stat; + + if (!SWISSET('a')) + if (fstat(fileno(file), &stat) == 0) + return stat.st_size; + + return 0; +} + +/*++ + * l o c a l D e l e t e F i l e + * + * Delete a local file. + * + * Inputs: + * + * file - pointer to open file descriptor + * fname - pointer to filename string + * + * Outputs: + * + * None + * + * Returns: + * + * None + * + --*/ +static void localDeleteFile( + void *file, + char *fname +) +{ + fclose(file); + + if (unlink(fname) != 0) + fprintf(stderr, "delete: failed to delete \"%s\"\n", fname); +} + +/*++ + * l o c a l C l o s e F i l e + * + * Close an open local file. + * + * Inputs: + * + * file - pointer to open file descriptor + * + * Outputs: + * + * None + * + * Returns: + * + * None + * + --*/ +static void localCloseFile( + void *file +) +{ + fclose(file); +} + +/*++ + * l o c a l R e a d F i l e + * + * Read from a local file into a supplied buffer. If ASCII mode is active, + * each read will return at most 1 line of data and any terminating will + * be translated into . + * + * Inputs: + * + * file - pointer to open file descriptor + * buf - pointer to buffer + * buflen - length of the supplied buffer + * + * Outputs: + * + * None + * + * Returns: + * + * # of bytes of data read, 0 means EOF or error + * + --*/ +static size_t localReadFile( + void *file, + void *buf, + size_t buflen +) +{ + if (SWISSET('a')) { + char *bufr = buf; + int readlen; + + if (fgets(bufr, buflen - 1, file) == NULL) + return 0; + + /* + * Translate terminating \n into \r\n unless it is already there + */ + if ((readlen = strlen(bufr)) != 0) { + if (bufr[readlen - 1] == '\n') { + if ((readlen == 1) || (bufr[readlen - 2] != '\r')) + strcpy(&bufr[readlen - 1], "\r\n"); + } + } + return strlen(bufr); + } + return fread(buf, sizeof(char), buflen, file); +} + +/*++ + * l o c a l W r i t e F i l e + * + * Write to a local file from a supplied buffer. If ASCII mode is active, + * if the buffer is terminated with translate it into . + * + * Inputs: + * + * file - pointer to open file descriptor + * buf - pointer to buffer + * buflen - length of the supplied buffer + * + * Outputs: + * + * None + * + * Returns: + * + * # of bytes of data written, 0 means error + * + --*/ +static size_t localWriteFile( + void *file, + void *buf, + size_t buflen +) +{ + if (SWISSET('a')) { + char *bufw = buf; + + if (buflen >= 2) + if ((bufw[buflen - 2] == '\r') && (bufw[buflen - 1] == '\n')) { + bufw[buflen - 2] = '\n'; + buflen--; + } + } + return fwrite(buf, sizeof(char), buflen, file); +} + +/*++ + * l o c a l F S + * + * Descriptor for accessing local files. Note that none of command routines + * are present since this device never appears to be mounted. + --*/ +struct FSdef localFS = { + NULL, + "local", + "local Local file access\n", + 0, + 0, + NULL, + NULL, + NULL, + NULL, + NULL, + localInfo, + localDir, + localOpenFileR, + localOpenFileW, + localFileSize, + localCloseFile, + localReadFile, + localWriteFile, + localDeleteFile, + NULL, /* No tape support functions */ + NULL, + NULL, + NULL +}; + +/* + * Statically allocated mounted file system for local file access + */ +struct mountedFS localMount; diff --git a/converters/fsio/os8.c b/converters/fsio/os8.c new file mode 100644 index 0000000..8e705b7 --- /dev/null +++ b/converters/fsio/os8.c @@ -0,0 +1,3014 @@ +/* + * Copyright (C) 2019 John Forecast. All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY JOHN FORECAST "AS IS" AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/* + * Support routines for handling OS/8 file systems under fsio + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "fsio.h" + +static int rk05BlockPresent(struct mountedFS *, uint8_t, unsigned int); +static int rk05ReadBlock(struct mountedFS *, uint8_t, unsigned int, void *); +static int rk05WriteBlock(struct mountedFS *, uint8_t, unsigned int, void *); +static int rx01BlockPresent(struct mountedFS *, uint8_t, unsigned int); +static int rx01ReadBlock(struct mountedFS *, uint8_t, unsigned int, void *); +static int rx01WriteBlock(struct mountedFS *, uint8_t, unsigned int, void *); +static int rx02BlockPresent(struct mountedFS *, uint8_t, unsigned int); +static int rx02ReadBlock(struct mountedFS *, uint8_t, unsigned int, void *); +static int rx02WriteBlock(struct mountedFS *, uint8_t, unsigned int, void *); + +/* + * Floppy interleave tables: + * + * RX01: + * Each OS/8 logical block maps into 4 sectors (interleave 2) + * + * RX02: + * Each OS/8 logical block maps into 2 sectors (interleave 3) + */ +#define RX01INTLV(t, s) ((t * OS8_RX0xNSECT) + s - 1) +#define RX02INTLV(t, s) ((t * OS8_RX0xNSECT) + s - 1) + +static uint16_t rx01interleave[] = { + RX01INTLV(0, 1), RX01INTLV(0, 3), RX01INTLV(0, 5), RX01INTLV(0, 7), + RX01INTLV(0, 9), RX01INTLV(0, 11), RX01INTLV(0, 13), RX01INTLV(0, 15), + RX01INTLV(0, 17), RX01INTLV(0, 19), RX01INTLV(0, 21), RX01INTLV(0, 23), + RX01INTLV(0, 25), RX01INTLV(0, 2), RX01INTLV(0, 4), RX01INTLV(0, 6), + RX01INTLV(0, 8), RX01INTLV(0, 10), RX01INTLV(0, 12), RX01INTLV(0, 14), + RX01INTLV(0, 16), RX01INTLV(0, 18), RX01INTLV(0, 20), RX01INTLV(0, 22), + RX01INTLV(0, 24), RX01INTLV(0, 26), RX01INTLV(1, 1), RX01INTLV(1, 3), + RX01INTLV(1, 5), RX01INTLV(1, 7), RX01INTLV(1, 9), RX01INTLV(1, 11), + RX01INTLV(1, 13), RX01INTLV(1, 15), RX01INTLV(1, 17), RX01INTLV(1, 19), + RX01INTLV(1, 21), RX01INTLV(1, 23), RX01INTLV(1, 25), RX01INTLV(1, 2), + RX01INTLV(1, 4), RX01INTLV(1, 6), RX01INTLV(1, 8), RX01INTLV(1, 10), + RX01INTLV(1, 12), RX01INTLV(1, 14), RX01INTLV(1, 16), RX01INTLV(1, 18), + RX01INTLV(1, 20), RX01INTLV(1, 22), RX01INTLV(1, 24), RX01INTLV(1, 26) +}; +#define RX01REPEAT ((sizeof(rx01interleave)/sizeof(rx01interleave[0])) / 4) + +static uint16_t rx02interleave[] = { + RX02INTLV(0, 1), RX02INTLV(0, 4), + RX02INTLV(0, 7), RX02INTLV(0, 10), + RX02INTLV(0, 13), RX02INTLV(0, 16), + RX02INTLV(0, 19), RX02INTLV(0, 22), + RX02INTLV(0, 25), RX02INTLV(0, 2), + RX02INTLV(0, 5), RX02INTLV(0, 8), + RX02INTLV(0, 11), RX02INTLV(0, 14), + RX02INTLV(0, 17), RX02INTLV(0, 20), + RX02INTLV(0, 23), RX02INTLV(0, 26), + RX02INTLV(0, 3), RX02INTLV(0, 6), + RX02INTLV(0, 9), RX02INTLV(0, 12), + RX02INTLV(0, 15), RX02INTLV(0, 18), + RX02INTLV(0, 21), RX02INTLV(0, 24) +}; +#define RX02REPEAT ((sizeof(rx02interleave)/sizeof(rx02interleave[0])) / 2) + +/* + * Six-bit code => character mapping table + */ +static char sixbit[64] = { + ' ', 'A', 'B', 'C', 'D', 'E', 'F', 'G', + 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', + 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', + 'X', 'Y', 'Z', '[', '\\', ']', '^', '_', + ' ', '!', '"', '#', '$', '%', '&', '\'', + '(', ')', '*', '+', ',', '-', '.', '/', + '0', '1', '2', '3', '4', '5', '6', '7', + '8','9', ':', ';', '<', '=', '>', '?' +}; + +/* + * Month of the year - allow 4-bit index field + */ +static char *os8Month[] = { + "???", "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", + "Aug", "Sep", "Oct", "Nov", "Dec", "???", "???", "???" +}; + +/* + * Table of "set" commands + */ +static char *setCmds[] = { + "year", + NULL +}; +#define OS8SET_YEAR 0 + +static void os8CloseFile(void *); + +extern int args; +extern char **words; +extern int quiet; + +/*++ + * o s 8 D e v i c e s + * + * List of OS/8 disk device types supported by this program. + * + --*/ +struct OS8device OS8Devices[] = { + { "rk05", 2, 2 * OS8_RK05FS_BLKS, + 0, + { OS8_RK05FS_BLKS, OS8_RK05FS_BLKS, 0, 0, 0, 0, 0,0 }, + rk05BlockPresent, rk05ReadBlock, rk05WriteBlock }, + + { "rx01", 1, (OS8_RX0xSZ * OS8_RX01SS_W) / OS8_BLOCKSIZE, + OS8_RX0xNSECT * OS8_RX01SS, + { ((OS8_RX0xSZ - OS8_RX0xNSECT) * OS8_RX01SS_W) / OS8_BLOCKSIZE, + 0, 0, 0, 0, 0, 0, 0 }, + rx01BlockPresent, rx01ReadBlock, rx01WriteBlock }, + + { "rx02", 1, (OS8_RX0xSZ * OS8_RX02SS_W) / OS8_BLOCKSIZE, + OS8_RX0xNSECT * OS8_RX02SS, + { ((OS8_RX0xSZ - OS8_RX0xNSECT) * OS8_RX02SS_W) / OS8_BLOCKSIZE, + 0, 0, 0, 0, 0, 0, 0 }, + rx02BlockPresent, rx02ReadBlock, rx02WriteBlock }, + + { NULL, 0, 0, + 0, + { 0, 0, 0, 0, 0, 0, 0, 0 }, + NULL, NULL, NULL } +}; + +/*++ + * o s 8 W o r d + * + * Extract an unsigned 12-bit value. + * + * Inputs: + * + * value - 16-bit incoming value + * + * Outputs: + * + * None + * + * Returns: + * + * 16-bit value with the high 4-bit zeroed + * + --*/ +static uint16_t os8Word( + uint16_t value +) +{ + return value & 0xFFF; +} + +/*++ + * o s 8 S X T + * + * Extract a 12-bit value extending the size bit. + * + * Inputs: + * + * value - 16-bit incoming value + * + * Outputs: + * + * None + * + * Returns: + * + * Original value with sign extended to 16-bits + * + --*/ +static uint16_t os8SXT( + uint16_t value +) +{ + return (value & 0xFFF) | (((value & 0x800) != 0) ? 0xF000 : 0); +} + +/*++ + * o s 8 N e g + * + * Negate a 12-bit value to a 16-bit value leading to a result in the range + * 0 - 4095. + * + * Inputs: + * + * value - 16-bit incoming value + * + * Outputs: + * + * None + * + * Returns: + * + * Negated value + * + --*/ +static uint16_t os8Neg( + uint16_t value +) +{ + uint16_t value16 = os8SXT(value); + + return (uint16_t)((0 - value16) & 0xFFF); +} + +/*++ + * o s 8 V a l u e + * + * Truncate a 16-bit value to 12-bits. + * + * Inputs: + * + * value - 16-bit incoming value + * + * Outputs: + * + * None + * + * Returns: + * + * The 12-bit truncated value + * + --*/ +static uint16_t os8Value( + uint16_t value +) +{ + return value & 0xFFF; +} + +/*++ + * o s 8 D a t e + * + * Construct a user readable date string given an OS/8 date value. The caller + * must supply a suitably sized buffer to contain the date string. If the + * supplied date value is zero, the date string will be all spaces. + * + * Inputs: + * + * date - OS/8 date value + * buf - pointer to buffer to receive the file name + * (Must be at least 12 bytes) + * + * Outputs: + * + * The buffer will be overwritten with the date string. + * + * Returns: + * + * Pointer to the NULL terminated file name string + * + --*/ +static char *os8Date( + uint16_t date, + char *buf +) +{ + if (date != 0) { + sprintf(buf, "%2d-%s-%4d", + (date >> 3) & 0x1F, + os8Month[(date >> 8) & 0xF], + 1970 + (date & 0x7)); + } else strcpy(buf, " "); + + return buf; +} + +/*++ + * o s 8 F i l e N a m e + * + * Construct a user readable filename string from a directory entry in the + * mount point specific buffer. The caller must supply a suitably sized + * buffer to contain the file name string. + * + * Inputs: + * + * mount - pointer to a mounted file system descriptor + * off - offset of the directory entry + * buf - pointer to buffer to receive the file name + * (Must be at least 10 bytes) + * + * Outputs: + * + * The buffer will be overwritten with the file name + * + * Returns: + * + * Pointer to the NULL terminated file name string + * + --*/ +static char *os8FileName( + struct mountedFS *mount, + uint16_t off, + char *buf +) +{ + struct OS8data *data = &mount->os8data; + uint16_t fname1 = os8Word(le16toh(data->buf[off + OS8_DI_FNAME1])); + uint16_t fname2 = os8Word(le16toh(data->buf[off + OS8_DI_FNAME2])); + uint16_t fname3 = os8Word(le16toh(data->buf[off + OS8_DI_FNAME3])); + uint16_t ext = os8Word(le16toh(data->buf[off + OS8_DI_EXT])); + + buf[0] = sixbit[(fname1 >> 6) & 0x3F]; + buf[1] = sixbit[fname1 & 0x3F]; + buf[2] = sixbit[(fname2 >> 6) & 0x3F]; + buf[3] = sixbit[fname2 & 0x3F]; + buf[4] = sixbit[(fname3 >> 6) & 0x3F]; + buf[5] = sixbit[fname3 & 0x3F]; + buf[6] = '.'; + buf[7] = sixbit[(ext >> 6) & 0x3F]; + buf[8] = sixbit[ext & 0x3F]; + buf[9] = '\0'; + + return buf; +} + +/*++ + * o s 8 U n i t V a l i d + * + * Determine if a provided unit # is valid. + * + * Inputs: + * + * data - pointer to OS/8 specific data area + * unit - file system unit number + * + * Outputs: + * + * None + * + * Returns: + * + * 1 if unit # is valid, 0 otherwise + * + --*/ +static int os8UnitValid( + struct OS8data *data, + uint8_t unit +) +{ + struct OS8device *device; + + if ((device = data->device) != NULL) { + if (unit < device->filesys) + return (data->valid & (1 << unit)) != 0 ? 1 : 0; + } + return 0; +} + +/*++ + * r k 0 5 B l o c k P r e s e n t + * + * Check if the specified block is present in the container file. + * + * Inputs: + * + * mount - pointer to a mounted file system descriptor + * unit - file system unit number + * block - logical block # in the range 0 - N + * + * Outputs: + * + * None + * + * Returns: + * + * 1 if block is present in the container file, 0 otherwise + * + --*/ +static int rk05BlockPresent( + struct mountedFS *mount, + uint8_t unit, + unsigned int block +) +{ + struct OS8data *data = &mount->os8data; + unsigned int base = unit == 0 ? 0 : OS8_RK05FS_BLKS; + + return data->blocks > (base + block); +} + +/*++ + * r k 0 5 R e a d B l o c k + * + * Read a block from an OS/8 file system on an RK05 disk. + * + * Inputs: + * + * mount - pointer to a mounted file system descriptor + * unit - file system unit number + * block - logical block # in the range 0 - N + * buf - buffer to receive data + * + * Outputs: + * + * The block will be read into the specified buffer + * + * Returns: + * + * 1 if successful, 0 otherwise + * + --*/ +static int rk05ReadBlock( + struct mountedFS *mount, + uint8_t unit, + unsigned int block, + void *buf +) +{ + int status = 0; + unsigned int base = unit == 0 ? 0 : OS8_RK05FS_BLKS; + + if (block >= OS8_RK05FS_BLKS) { + ERROR("Attempt to read block (%u) outside file system \"%s%o:\"\n", + block, mount->name, unit); + return 0; + } + + status = FSioReadBlock(mount, base + block, buf); + + if (status == 0) + ERROR("I/O error on \"%s%o:\"\n", mount->name, unit); + + return status; +} + +/*++ + * r k 0 5 W r i t e B l o c k + * + * Write a block to an OS/8 file system on an RK05 disk. + * + * Inputs: + * + * mount - pointer to a mounted file system descriptor + * unit - file system unit number + * block - logical block # in the range 0 - N + * buf - buffer containing the data + * + * Outputs: + * + * None + * + * Returns: + * + * 1 if successful, 0 otherwise + * + --*/ +static int rk05WriteBlock( + struct mountedFS *mount, + uint8_t unit, + unsigned int block, + void *buf +) +{ + int status = 0; + unsigned int base = unit == 0 ? 0 : OS8_RK05FS_BLKS; + + if (block >= OS8_RK05FS_BLKS) { + ERROR("Attempt to write block (%u) outside file system \"%s%o:\"\n", + block, mount->name, unit); + return 0; + } + + status = FSioWriteBlock(mount, base + block, buf); + + if (status == 0) + ERROR("I/O error on \"%s%o:\"\n", mount->name, unit); + + return status; +} + +/*++ + * r x 0 1 B l o c k P r e s e n t + * + * Check if the specified block is present in the container file. + * + * Inputs: + * + * mount - pointer to a mounted file system descriptor + * unit - file system unit number + * block - logical block # in the range 0 - N + * + * Outputs: + * + * None + * + * Returns: + * + * 1 if block is present in the container file, 0 otherwise + * + --*/ +static int rx01BlockPresent( + struct mountedFS *mount, + uint8_t UNUSED(unit), + unsigned int block +) +{ + struct OS8data *data = &mount->os8data; + + return data->blocks > block; +} + +/*++ + * r x 0 1 R e a d B l o c k + * + * Read a block from an OS/8 file system on an RX01 disk. + * + * Inputs: + * + * mount - pointer to a mounted file system descriptor + * unit - file system unit number + * block - logical block # in the range 0 - N + * buf - buffer to receive data + * + * Outputs: + * + * The block will be read into the specified buffer + * + * Returns: + * + * 1 if successful, 0 otherwise + * + --*/ +static int rx01ReadBlock( + struct mountedFS *mount, + uint8_t unit, + unsigned int block, + void *buf +) +{ + unsigned int base = (block / RX01REPEAT) * RX01REPEAT; + unsigned int offset = block % RX01REPEAT; + uint16_t *buffer = buf; + int i, j, k, status; + +#ifdef DEBUG + if ((mount->flags & FS_DEBUG) != 0) + fprintf(DEBUGout, " >>%s%o: rx01ReadBlock, base %u, offset %u\n", + mount->name, unit, base, offset); +#endif + + /* + * Convert to sector number + */ + base *= OS8_BLOCKSIZE / OS8_RX01SS_W; + + /* + * Read 4 sectors into the buffer + */ + for (i = 0; i < 4; i++) { + unsigned int sector = base + rx01interleave[(offset * 4) + i]; + unsigned char temp[OS8_RX01SS]; + +#ifdef DEBUG + if ((mount->flags & FS_DEBUG) != 0) + fprintf(DEBUGout, " >> %s%o: (os8) Reading %u, track %u sector %u\n", + mount->name, unit, sector, + base + ((rx01interleave[(offset * 4) + i] + 1) / OS8_RX01SS), + (rx01interleave[(offset * 4) + i] + 1) % OS8_RX01SS); +#endif + + if ((status = FSioReadSector(mount, sector, OS8_RX01SS, temp)) == 0) { + ERROR("I/O error on \"%s%o:\"\n", mount->name, unit); + return 0; + } + + /* + * Unpack 64 words from the temporary buffer + */ + for (j = 0, k = 0; j < OS8_RX01SS_W; j += 2, k += 3) { + buffer[j] = ((temp[k] << 4) | ((temp[k + 1] >> 4) & 0xF)) & 0xFFF; + buffer[j + 1] = (((temp[k + 1] << 8) & 0xF00) | temp[k + 2]) & 0xFFF; + } + buffer += OS8_RX01SS_W; + } + return 1; +} + +/*++ + * r x 0 1 W r i t e B l o c k + * + * Write a block to an OS/8 file system on an RX01 disk. + * + * Inputs: + * + * mount - pointer to a mounted file system descriptor + * unit - file system unit number + * block - logical block # in the range 0 - N + * buf - buffer containing the data + * + * Outputs: + * + * None + * + * Returns: + * + * 1 if successful, 0 otherwise + * + --*/ +static int rx01WriteBlock( + struct mountedFS *mount, + uint8_t unit, + unsigned int block, + void *buf +) +{ + unsigned int base = (block / RX01REPEAT) * RX01REPEAT; + unsigned int offset = block % RX01REPEAT; + uint16_t *buffer = buf; + int i, j, k, status; + +#ifdef DEBUG + if ((mount->flags & FS_DEBUG) != 0) + fprintf(DEBUGout, " >>%s%o: rx01WriteBlock, base %u, offset %u\n", + mount->name, unit, base, offset); +#endif + + /* + * Convert to sector number + */ + base *= OS8_BLOCKSIZE / OS8_RX01SS_W; + + /* + * Write 4 sectors from the buffer + */ + for (i = 0; i < 4; i++) { + unsigned int sector = base + rx01interleave[(offset * 4) + i]; + unsigned char temp[OS8_RX01SS]; + + /* + * Pack 64 words into the temporary buffer + */ + for (j = 0, k = 0; j < OS8_RX01SS_W; j += 2, k += 3) { + temp[k] = (buffer[j] >> 4) & 0xFF; + temp[k + 1] = ((buffer[j] << 4) & 0xF0) | ((buffer[j + 1] >> 8) & 0xF); + temp[k + 2] = buffer[j + 1] & 0xFF; + } + +#ifdef DEBUG + if ((mount->flags & FS_DEBUG) != 0) + fprintf(DEBUGout, " >> %s%o: (os8) Writing %u, track %u sector %u\n", + mount->name, unit, sector, + base + ((rx01interleave[(offset * 4) + i] + 1) / OS8_RX01SS), + (rx01interleave[(offset * 4) + i] + 1) % OS8_RX01SS); +#endif + + if ((status = FSioWriteSector(mount, sector, OS8_RX01SS, temp)) == 0) { + ERROR("I/O error on \"%s%o:\"\n", mount->name, unit); + return 0; + } + buffer += OS8_RX01SS_W; + } + return 1; +} + +/*++ + * r x 0 2 B l o c k P r e s e n t + * + * Check if the specified block is present in the container file. + * + * Inputs: + * + * mount - pointer to a mounted file system descriptor + * unit - file system unit number + * block - logical block # in the range 0 - N + * + * Outputs: + * + * None + * + * Returns: + * + * 1 if block is present in the container file, 0 otherwise + * + --*/ +static int rx02BlockPresent( + struct mountedFS *mount, + uint8_t UNUSED(unit), + unsigned int block +) +{ + struct OS8data *data = &mount->os8data; + + return data->blocks > block; +} + +/*++ + * r x 0 2 R e a d B l o c k + * + * Read a block from an OS/8 file system on an RX01 disk. + * + * Inputs: + * + * mount - pointer to a mounted file system descriptor + * unit - file system unit number + * block - logical block # in the range 0 - N + * buf - buffer to receive data + * + * Outputs: + * + * The block will be read into the specified buffer + * + * Returns: + * + * 1 if successful, 0 otherwise + * + --*/ +static int rx02ReadBlock( + struct mountedFS *mount, + uint8_t unit, + unsigned int block, + void *buf +) +{ + unsigned int base = (block / RX02REPEAT) * RX02REPEAT; + unsigned int offset = block % RX02REPEAT; + uint16_t *buffer = buf; + int i, j, k, status; + +#ifdef DEBUG + if ((mount->flags & FS_DEBUG) != 0) + fprintf(DEBUGout, " >>%s%o: rx02ReadBlock, base %u, offset %u\n", + mount->name, unit, base, offset); +#endif + + /* + * Convert to sector number + */ + base *= OS8_BLOCKSIZE / OS8_RX02SS_W; + + /* + * Read 2 sectors into the buffer + */ + for (i = 0; i < 2; i++) { + unsigned int sector = base + rx02interleave[(offset * 2) + i]; + unsigned char temp[OS8_RX02SS]; + +#ifdef DEBUG + if ((mount->flags & FS_DEBUG) != 0) + fprintf(DEBUGout, " >> %s%o: (os8) Reading %u, track %u sector %u\n", + mount->name, unit, sector, + base + ((rx02interleave[(offset * 2) + i] + 1) / OS8_RX02SS), + (rx02interleave[(offset * 2) + i] + 1) % OS8_RX02SS); +#endif + + if ((status = FSioReadSector(mount, sector, OS8_RX02SS, temp)) == 0) { + ERROR("I/O error on \"%s%o:\"\n", mount->name, unit); + return 0; + } + + /* + * Unpack 128 words from the temporary buffer + */ + for (j = 0, k = 0; j < OS8_RX02SS_W; j += 2, k += 3) { + buffer[j] = ((temp[k] << 4) | ((temp[k + 1] >> 4) & 0xF)) & 0xFFF; + buffer[j + 1] = (((temp[k + 1] << 8) & 0xF00) | temp[k + 2]) & 0xFFF; + } + buffer += OS8_RX02SS_W; + } + return 1; +} + +/*++ + * r x 0 2 W r i t e B l o c k + * + * Write a block to an OS/8 file system on an RX01 disk. + * + * Inputs: + * + * mount - pointer to a mounted file system descriptor + * unit - file system unit number + * block - logical block # in the range 0 - N + * buf - buffer containing the data + * + * Outputs: + * + * None + * + * Returns: + * + * 1 if successful, 0 otherwise + * + --*/ +static int rx02WriteBlock( + struct mountedFS *mount, + uint8_t unit, + unsigned int block, + void *buf +) +{ + unsigned int base = (block / RX02REPEAT) * RX02REPEAT; + unsigned int offset = block % RX02REPEAT; + uint16_t *buffer = buf; + int i, j, k, status; + +#ifdef DEBUG + if ((mount->flags & FS_DEBUG) != 0) + fprintf(DEBUGout, " >>%s%o: rx01WriteBlock, base %u, offset %u\n", + mount->name, unit, base, offset); +#endif + + /* + * Convert to sector number + */ + base *= OS8_BLOCKSIZE / OS8_RX02SS_W; + + /* + * Write 2 sectors from the buffer + */ + for (i = 0; i < 2; i++) { + unsigned int sector = base + rx02interleave[(offset * 2) + i]; + unsigned char temp[OS8_RX02SS]; + + /* + * Pack 128 words into the temporary buffer + */ + for (j = 0, k = 0; j < OS8_RX02SS_W; j += 2, k += 3) { + temp[k] = (buffer[j] >> 4) & 0xFF; + temp[k + 1] = ((buffer[j] << 4) & 0xF0) | ((buffer[j + 1] >> 8) & 0xF); + temp[k + 2] = buffer[j + 1] & 0xFF; + } + +#ifdef DEBUG + if ((mount->flags & FS_DEBUG) != 0) + fprintf(DEBUGout, " >> %s%o: (os8) Writing %u, track %u sector %u\n", + mount->name, unit, sector, + base + ((rx02interleave[(offset * 2) + i] + 1) / OS8_RX02SS), + (rx02interleave[(offset * 2) + i] + 1) % OS8_RX02SS); +#endif + + if ((status = FSioWriteSector(mount, sector, OS8_RX02SS, temp)) == 0) { + ERROR("I/O error on \"%s%o:\"\n", mount->name, unit); + return 0; + } + buffer += OS8_RX02SS_W; + } + return 1; +} + +/*++ + * o s 8 B l o c k P r e s e n t + * + * Check if a block is present in the container file. + * + * Inputs: + * + * mount - pointer to a mounted file system descriptor + * unit - file system unit number + * block - logical block # in the range 0 - N + * + * Outputs: + * + * None + * + * Returns: + * + * 1 if the block is present, 0 otherwise + * + --*/ +int os8BlockPresent( + struct mountedFS *mount, + uint8_t unit, + unsigned int block +) +{ + struct OS8data *data = &mount->os8data; + + return (*data->device->blockPresent)(mount, unit, block); +} + +/*++ + * o s 8 R e a d B l o c k + * + * Read a block from an OS/8 file system. + * + * Inputs: + * + * mount - pointer to a mounted file system descriptor + * unit - file system unit number + * block - logical block # in the range 0 - N + * buf - buffer to receive data, if NULL use the mount + * point specific buffer + * + * Outputs: + * + * The block will be read into the specified buffer. + * + * Returns: + * + * 1 if successful, 0 otherwise + * + --*/ +int os8ReadBlock( + struct mountedFS *mount, + uint8_t unit, + unsigned int block, + void *buf +) +{ + struct OS8data *data = &mount->os8data; + void *buffer = buf == NULL ? data->buf : buf; + + if ((unit >= data->device->filesys) || ((data->valid & (1 << unit)) == 0)) { + ERROR("Invalid device \"%s%o:\"\n", mount->name, unit); + return 0; + } + +#ifdef DEBUG + if ((mount->flags & FS_DEBUG) != 0) + fprintf(DEBUGout, ">> %s%o: (os8) Reading logical block %u\n", + mount->name, unit, block); +#endif + + return (*data->device->readBlock)(mount, unit, block, buffer); +} + +/*++ + * o s 8 W r i t e B l o c k + * + * Write a block to an OS/8 file system. + * + * Inputs: + * + * mount - pointer to a mounted file system descriptor + * unit - file system unit number + * block - logical block # in the range 0 - N + * buf - buffer to receive data, if NULL use the mount + * point specific buffer + * + * Outputs: + * + * None + * + * Returns: + * + * 1 if successful, 0 otherwise + * + --*/ +int os8WriteBlock( + struct mountedFS *mount, + uint8_t unit, + unsigned int block, + void *buf +) +{ + struct OS8data *data = &mount->os8data; + void *buffer = buf == NULL ? data->buf : buf; + + if ((unit >= data->device->filesys) || ((data->valid & (1 << unit)) == 0)) { + ERROR("Invalid device \"%s%o:\"\n", mount->name, unit); + return 0; + } + +#ifdef DEBUG + if ((mount->flags & FS_DEBUG) != 0) + fprintf(DEBUGout, ">> %s%o: (os8) Writing logical block %u\n", + mount->name, unit, block); +#endif + + return (*data->device->writeBlock)(mount, unit, block, buffer); +} + +/*++ + * o s 8 C h e c k D i r e c t o r y + * + * Check if there is sufficient space available for a new permanent entry + * in the directory segment currently in the mount specific buffer. + * + * Inputs: + * + * mount - pointer to a mounted file system descriptor + * + * Outputs: + * + * None + * + * Returns: + * + * 1 if there is sufficient space available, 0 otherwise + * + --*/ +static int os8CheckDirectory( + struct mountedFS *mount +) +{ + struct OS8data *data = &mount->os8data; + uint16_t i, entrysz, entries, off = OS8_DH_SIZE; + + entrysz = OS8_DI_SIZE + (-os8SXT(le16toh(data->buf[OS8_DH_EXTRA]))); + entries = os8Neg(le16toh(data->buf[OS8_DH_ENTRIES])); + + for (i = 0; i < entries; i++) + if (os8Word(le16toh(data->buf[off + OS8_DI_FNAME1])) != 0) + off += entrysz; + else off += OS8_ED_SIZE; + + return (off + entrysz) < OS8_BLOCKSIZE ? 1 : 0; +} + +/*++ + * o s 8 F i n d S p a c e + * + * Determine which directory segment has available free space. This routine + * may be called to find the "best fit" (i.e. smallest free space >= the + * requested size) or the largest available free space. if successful, + * the directory segment will be loaded in the mount specific buffer. + * + * Inputs: + * + * mount - pointer to a mounted file system descriptor + * unit - file system unit number + * space - # of blocks of free space required, 0 means find + * the largest block of free space + * segment - return directory segment here + * offset - return directory offset here + * start - return starting block # here + * + * Outputs: + * + * The mount specific buffer will be modifed. + * + * Returns: + * + * 1 if free space was found, 0 otherwise + * + --*/ +static int os8FindSpace( + struct mountedFS *mount, + uint8_t unit, + uint16_t space, + uint16_t *segment, + uint16_t *offset, + uint16_t *start +) +{ + struct OS8data *data = &mount->os8data; + int found = 0; + uint16_t dirseg, diroff, dirstart, size = space == 0 ? 0 : 0xFFFF; + uint16_t dirblk = OS8_DSSTART; + + if (os8UnitValid(data, unit)) { + do { + uint16_t i, entrysz, entries, extra, startblk, off = OS8_DH_SIZE; + + if (os8ReadBlock(mount, unit, dirblk, NULL) == 0) + return 0; + + entrysz = OS8_DI_SIZE + (-os8SXT(le16toh(data->buf[OS8_DH_EXTRA]))); + entries = os8Neg(le16toh(data->buf[OS8_DH_ENTRIES])); + extra = os8Neg(le16toh(data->buf[OS8_DH_EXTRA])); + + startblk = os8Word(le16toh(data->buf[OS8_DH_START])); + + for (i = 0; i < entries; i++) { + uint16_t blks; + + if (os8Word(le16toh(data->buf[off + OS8_DI_FNAME1])) == 0) { + blks = os8Neg(le16toh(data->buf[off + OS8_ED_LENGTH])); + + if (space == 0) { + if (blks > size) { + size = blks; + dirseg = dirblk; + diroff = off; + dirstart = startblk; + found = 1; + } + } else { + if (blks >= space) + if (blks < size) { + size = blks; + dirseg = dirblk; + diroff = off; + dirstart = startblk; + found = 1; + } + } + off += OS8_ED_SIZE; + } else { + blks = os8Neg(le16toh(data->buf[off + extra + OS8_DI_LENGTH])); + off += entrysz; + } + startblk += blks; + } + + dirblk = os8Word(le16toh(data->buf[OS8_DH_NEXT])); + } while (dirblk != 0); + + /* + * If we found a suitable entry, load the directory segment and update + * return information. + */ + if (found) { + if (os8ReadBlock(mount, unit, dirseg, NULL) == 0) + return 0; + + *segment = dirseg; + *offset = diroff; + *start = dirstart; + } + } + return found; +} + +/*++ + * o s 8 S l i d e D o w n + * + * Open up a new permanent directory entry at the specified offset within + * a directory segment. This routine assumes thatnthe caller has verified + * that there is sufficient free space at the end of the directory for a new + * entry. The directory segment is in the mount specific buffer. + * + * Inputs: + * + * mount - pointer to a mounted file system descriptor + * off - offset to add the new directory entry + * entries - # of directory entries + * entrysz - size of a permanent directory entry + * remain - return remaining entries here + * + * Outputs: + * + * The mount specific buffer will be modified. + * + * Returns: + * + * None + * + --*/ +static void os8SlideDown( + struct mountedFS *mount, + uint16_t off, + uint16_t entries, + uint16_t entrysz, + uint16_t *remain +) +{ + struct OS8data *data = &mount->os8data; + uint16_t i, j, src = OS8_DH_SIZE, dst = OS8_DH_SIZE; + uint16_t buf[OS8_BLOCKSIZE]; + + /* + * Build an updated directory header + */ + buf[OS8_DH_ENTRIES] = htole16(os8Value(-(entries + 1))); + buf[OS8_DH_START] = data->buf[OS8_DH_START]; + buf[OS8_DH_NEXT] = data->buf[OS8_DH_NEXT]; + buf[OS8_DH_FLAGWD] = htole16(os8Value(0)); + buf[OS8_DH_EXTRA] = data->buf[OS8_DH_EXTRA]; + + for (i = 0; i < entries; i++) { + uint16_t len; + + if (src == off) { + dst += entrysz; + *remain = entries - (i + 1); + } + + len = + os8Word(le16toh(data->buf[src + OS8_DI_FNAME1])) ? entrysz : OS8_ED_SIZE; + + for (j = 0; j < len; j++) + buf[dst++] = data->buf[src++]; + } + + /* + * Now copy the updated directory segment back to the mount speccific buffer + */ + memcpy(data->buf, buf, sizeof(buf)); +} + +/*++ + * o s 8 S l i d e U p + * + * Remove a full or partial directory entry by sliding the remaining entries + * up. The directory segment is in the mount specific buffer. + * + * Inputs: + * + * mount - pointer to a mounted file system descriptor + * dst - offset to entry being removed + * src - offset to first entry to me moved + * entrysz - size of a permanent directory entry + * count - # of entries to "slide up" + * + * Outputs: + * + * The mount specific buffer may be modified + * + * Returns: + * + * None + * + --*/ +static void os8SlideUp( + struct mountedFS *mount, + uint16_t dst, + uint16_t src, + uint16_t entrysz, + uint16_t count +) +{ + struct OS8data *data = &mount->os8data; + uint16_t i, j, len; + + if (count != 0) { + for (i = 0; i < count; i++) { + len = os8Word(le16toh(data->buf[src + OS8_DI_FNAME1])) ? entrysz : OS8_ED_SIZE; + + for (j = 0; j < len; j++) + data->buf[dst++] = data->buf[src++]; + } + } +} + +/*++ + * o s 8 S p l i t D i r e c t o r y + * + * Split the directory segment currently in the mount specific buffer. The + * split attempts to optimize for maximal use of each directory segment + * while using a minimal number of directory segments. + * + * Inputs: + * + * mount - pointer to a mounted file system descriptor + * unit - file system unit number + * segment - directory segment to be split + * + * Outputs: + * + * The mount specific buffer may be modified. + * + * Returns: + * + * 1 if directory segment was successfully split, 0 otherwise + * + --*/ +static int os8SplitDirectory( + struct mountedFS *mount, + uint8_t unit, + uint16_t segment +) +{ + struct OS8data *data = &mount->os8data; + uint16_t inuse = 0; + uint16_t dirblk = OS8_DSSTART; + uint16_t buf[OS8_BLOCKSIZE]; + uint16_t empoff[OS8_BLOCKSIZE], emplen[OS8_BLOCKSIZE]; + uint16_t emptotal = 0, emplast = 0, empcount = 0; + uint16_t i, j, entries, entrysz, extra, startblk, split = 0; + uint16_t off, target, dst = OS8_DH_SIZE; + + /* + * First we need to find an unused directory segment. + */ + do { + inuse |= (1 << dirblk); + + if (os8ReadBlock(mount, unit, dirblk, buf) == 0) + return 0; + + dirblk = os8Word(le16toh(buf[OS8_DH_NEXT])); + } while (dirblk != 0); + + for (i = OS8_DSSTART; i <= OS8_DSLAST; i++) { + if ((inuse & (1 << i)) == 0) { + /* + * Build a new directory segment + */ + buf[OS8_DH_ENTRIES] = 0; + buf[OS8_DH_NEXT] = data->buf[OS8_DH_NEXT]; + buf[OS8_DH_FLAGWD] = htole16(os8Value(0)); + buf[OS8_DH_EXTRA] = data->buf[OS8_DH_EXTRA]; + + entrysz = OS8_DI_SIZE + (-os8SXT(le16toh(data->buf[OS8_DH_EXTRA]))); + entries = os8Neg(le16toh(data->buf[OS8_DH_ENTRIES])); + extra = os8Neg(le16toh(data->buf[OS8_DH_EXTRA])); + + startblk = os8Word(le16toh(data->buf[OS8_DH_START])); + + /* + * Analyze the current directory segment by finding all the empty + * descriptors. + */ + off = OS8_DH_SIZE; + + for (j = 0; j < entries; j++) { + if (os8Word(le16toh(data->buf[off + OS8_DI_FNAME1])) == 0) { + emplen[empcount] = os8Neg(le16toh(data->buf[off + OS8_ED_LENGTH])); + emptotal += emplen[empcount]; + empoff[empcount] = off; + empcount++; + emplast = j == (entries - 1); + + off += OS8_ED_SIZE; + } else off += entrysz; + } + + /* + * We want to avoid "stranding" unused directory entries where there + * is no free space so we apply the following heuristics: + * + * 1. If the last directory entry is "empty" and contains at least + * 50% of the free space, just move this entry to the newly + * created directory segment + * + * 2. Start the new directory segment at the "empty" entry which would + * exceed 50% of the free space. + */ + target = empoff[empcount - 1]; + + if (!emplast || (emplen[empcount - 1] < (emptotal / 2))) { + uint16_t empty = 0; + + for (j = 0; j < empcount; j++) + if ((empty + emplen[j]) >= (emptotal / 1)) { + target = empoff[j]; + break; + } else empty += emplen[j]; + } + + /* + * Find the location of the split point and copy all directory entries + * after this point to the newly created directory segment. + */ + off = OS8_DH_SIZE; + + for (j = 0; j < entries; j++) { + uint16_t blks, size; + + if (off == target) { + split = j + 1; + buf[OS8_DH_START] = htole16(os8Value(startblk)); + } + + if (os8Word(le16toh(data->buf[off + OS8_DI_FNAME1])) != 0) { + blks = os8Neg(le16toh(data->buf[off + extra + OS8_DI_LENGTH])); + size = OS8_DI_SIZE + extra; + } else { + blks = os8Neg(le16toh(data->buf[off + OS8_ED_LENGTH])); + size = OS8_ED_SIZE; + } + + if (split != 0) { + uint16_t k; + + for (k = 0; k < size; k++) + buf[dst++] = data->buf[off + k]; + + buf[OS8_DH_ENTRIES]++; + } + off += size; + startblk += blks; + } + + /* + * Update the new directory segment + */ + buf[OS8_DH_ENTRIES] = htole16(os8Value(-buf[OS8_DH_ENTRIES])); + + if (os8WriteBlock(mount, unit, i, buf) == 0) + return 0; + + /* + * Update the original directory segment + */ + data->buf[OS8_DH_ENTRIES] = htole16(os8Value(-(split - 1))); + data->buf[OS8_DH_NEXT] = htole16(os8Value(i)); + + if (os8WriteBlock(mount, unit, segment, NULL) == 0) + return 0; + + /* + * Directory segment split complete + */ + return 1; + } + } + return 0; +} + +/*++ + * o s 8 M e r g e E m p t y R e g i o n s + * + * Scan a directory segment looking for 2 consecutive empty entries which + * can be merged into a single entry. The directory segment is in the mount + * specific buffer. + * + * Inputs: + * + * mount - pointer to a mounted file system descriptor + * + * Outputs: + * + * The mount specific buffer may be modified + * + * Returns: + * + * 1 if a merge occured, 0 otherwise + * + --*/ +static int os8MergeEmptyRegions( + struct mountedFS *mount +) +{ + struct OS8data *data = &mount->os8data; + uint16_t i, entrysz, entries, off = OS8_DH_SIZE; + + entrysz = OS8_DI_SIZE + (-os8SXT(le16toh(data->buf[OS8_DH_EXTRA]))); + entries = os8Neg(le16toh(data->buf[OS8_DH_ENTRIES])); + + if (entries > 1) { + for (i = 0; i < (entries - 1); i++) { + if (os8Word(le16toh(data->buf[off + OS8_DI_FNAME1])) == 0) { + if (os8Word(le16toh(data->buf[off + OS8_ED_SIZE + OS8_DI_FNAME1])) == 0) { + data->buf[off + OS8_ED_LENGTH] = + htole16(os8Value(le16toh(data->buf[off + OS8_ED_LENGTH]) + + le16toh(data->buf[off + OS8_ED_SIZE + OS8_ED_LENGTH]))); + data->buf[OS8_DH_ENTRIES] = htole16(os8Value(-(entries - 1))); + + os8SlideUp(mount, off + OS8_ED_SIZE, + off + (2 * OS8_ED_SIZE), entrysz, entries - 2); + return 1; + } + } else off += entrysz; + } + } + return 0; +} + +/*++ + * i n f o + * + * Display information about the internal structure of a single file system. + * + * Inputs: + * + * mount - pointer to a mounted file system descriptor + * unit - file system unit number + * + * Outputs: + * + * None + * + * Returns: + * + * None + * + --*/ +static void info( + struct mountedFS *mount, + uint8_t unit +) +{ + struct OS8data *data = &mount->os8data; + unsigned int dirblk = OS8_DSSTART; + + printf("\n%s%o:\n\n", mount->name, unit); + + do { + uint16_t i, entrysz, entries, extra, startblk, off = OS8_DH_SIZE; + + if (os8ReadBlock(mount, unit, dirblk, NULL) == 0) + return; + + entrysz = OS8_DI_SIZE + (-os8SXT(le16toh(data->buf[OS8_DH_EXTRA]))); + entries = os8Neg(le16toh(data->buf[OS8_DH_ENTRIES])); + extra = os8Neg(le16toh(data->buf[OS8_DH_EXTRA])); + + startblk = os8Word(le16toh(data->buf[OS8_DH_START])); + + printf("\nDirectory Segment %d:\n\n", dirblk); + printf(" File Type Length Date Disk Region\n\n"); + + for (i = 0; i < entries; i++) { + char temp1[16], temp2[16]; + uint16_t length; + + if (os8Word(le16toh(data->buf[off + OS8_DI_FNAME1])) != 0) { + uint16_t dateval = 0; + + length = os8Neg(le16toh(data->buf[off + extra + OS8_DI_LENGTH])); + if (extra != 0) + dateval = os8Word(le16toh(data->buf[off + OS8_DI_DATE])); + + printf("%s %4hu %s %4d - %4d\n", + os8FileName(mount, off, temp1), length, + os8Date(dateval, temp2), startblk, startblk + length - 1); + + off += entrysz; + } else { + length = os8Neg(le16toh(data->buf[off + OS8_ED_LENGTH])); + + printf(" %4hu %4d - %4d\n", + os8Neg(le16toh(data->buf[off + OS8_ED_LENGTH])), + startblk, startblk + length - 1); + + off += OS8_ED_SIZE; + } + startblk += length; + } + + dirblk = os8Word(le16toh(data->buf[OS8_DH_NEXT])); + } while (dirblk != 0); +} + +/*++ + * v a l i d a t e + * + * Validate the integrity of an OS/8 file system. OS/8 file systems do not + * include any form of signature on the disk so we have to run a set of + * heuristics to verify the integrity of the file system. + * + * Inputs: + * + * mount - pointer to a mounted file system descriptor + * unit - file system unit number + * files - return # of permanent files here + * freeblks - return # of free blocks here + * addinfo - return # of additional information words here + * + * Outputs: + * + * The mount point specific buffer will be overwritten. + * + * Returns: + * + * 1 if file system is valid, 0 otherwise + * + --*/ +static int validate( + struct mountedFS *mount, + uint8_t unit, + uint16_t *files, + uint16_t *freeblks, + uint16_t *addinfo +) +{ + struct OS8data *data = &mount->os8data; + unsigned int dirblk = OS8_DSSTART; + uint8_t seen[OS8_DSLAST + 1]; + uint16_t extraVal; + + memset(seen, 0, sizeof(seen)); + + do { + uint16_t i, entrysz, entries, flagwd, extra, startblk, off = OS8_DH_SIZE; + + /* + * If we've already seen this directory segment, there must be a loop + */ + if (seen[dirblk] != 0) + return 0; + + if (!os8BlockPresent(mount, unit, dirblk) || + !os8ReadBlock(mount, unit, dirblk, NULL)) + return 0; + + seen[dirblk] = 1; + + entrysz = OS8_DI_SIZE + (-os8SXT(le16toh(data->buf[OS8_DH_EXTRA]))); + entries = os8Neg(le16toh(data->buf[OS8_DH_ENTRIES])); + flagwd = os8Word(le16toh(data->buf[OS8_DH_FLAGWD])); + extra = os8Neg(le16toh(data->buf[OS8_DH_EXTRA])); + + startblk = os8Word(le16toh(data->buf[OS8_DH_START])); + + /* + * The number of additional information words MUST be the same in all + * directory segments. + */ + if (dirblk == OS8_DSSTART) { + extraVal = extra; + *addinfo = extra; + } + + if (extraVal != extra) + return 0; + + /* + * Up to 077 extra words may be allocated to each directory entry. + */ + if ((extra & 07700) != 0) + return 0; + + /* + * The flag word must be either 0 or in the range 01400 - 01777. + */ + if ((flagwd != 0) && ((flagwd & 01400) != 01400)) + return 0; + + /* + * There must be at least one directory entry (an empty file occupying + * all the space) and, at maximum, alternating permanent and empty + * entries (256 / ((N + 5) + 2)) * 2. + */ + if ((entries == 0) || (entries > ((256 / (extra + 7))) * 2)) + return 0; + + /* + * Scan the directory checking for valid block addresses. + */ + for (i = 0; i < entries; i++) { + uint16_t length; + + if (os8Word(le16toh(data->buf[off + OS8_DI_FNAME1])) != 0) { + length = os8Neg(le16toh(data->buf[off + extra + OS8_DI_LENGTH])); + off += entrysz; + *files += 1; + } else { + length = os8Neg(le16toh(data->buf[off + OS8_ED_LENGTH])); + off += OS8_ED_SIZE; + *freeblks += length; + } + startblk += length; + + /* + * Make sure the block addresses are valid for this disk device + */ + if (startblk > data->device->blocks[unit]) + return 0; + } + + dirblk = os8Word(le16toh(data->buf[OS8_DH_NEXT])); + + if ((dirblk != 0) && ((dirblk < OS8_DSSTART) || (dirblk > OS8_DSLAST))) + return 0; + + } while (dirblk != 0); + + return 1; +} + +/*++ + * t o S i x b i t + * + * Convert an alphanumeric character to sixbit encoding and place it into + * the appropriate 6-bit field of a 12-bit value. The character will have + * already been converted to upper-case if needed. + * + * Inputs: + * + * ch - the character to convert + * ptr - pointer to the 12-bit location to update + * which - which 6-bit field to update: + * 0 - high 6-bit field + * 1 - low 6-bit field + * + * Outputs: + * + * None + * + * Returns: + * + * None + * + --*/ +static void toSixbit( + char ch, + uint16_t *ptr, + int which +) +{ + unsigned long i; + + for (i = 0; i < sizeof(sixbit); i++) + if (ch == sixbit[i]) { + if (which == 0) + *ptr = (i << 6) | (*ptr & 0x3F); + else *ptr = (*ptr & 0xFC0) | i; + return; + } +} + +/*++ + * o s 8 P a r s e F i l e s p e c + * + * Parse a character string representing an OS/8 file specification. + * + * Inputs: + * + * ptr - pointer to the file specification string + * spec - pointer to the file specification block + * wildcard - wildcard processing options: + * 0 (OS8_M_NONE) - wildcards not allowed + * 1 (OS8_M_ALLOW) - wildcards allowed + * 2 (OS8_M_NONAME) - wildcards allowed + * if filename + ext not + * present default to *.* + * + * Outputs: + * + * The file specification block will be filled with the file information. + * + * Returns: + * + * 1 if parse is successful, 0 otherwise + * + --*/ +#define P_DONE 0 +#define P_NAME 1 +#define P_EXT 2 + +int os8ParseFilespec( + char *ptr, + struct os8FileSpec *spec, + int wildcard +) +{ + int state = P_NAME; + unsigned int i; + + memset(spec, 0, sizeof(struct os8FileSpec)); + memset(&spec->fname, ' ', sizeof(spec->fname)); + memset(&spec->fext, ' ', sizeof(spec->fext)); + + if (wildcard == OS8_M_NONAME) + if (*ptr == '\0') { + spec->flags = OS8_WC_NAME | OS8_WC_EXT; + return 1; + } + + while (state != P_DONE) { + i = 0; + + switch (state) { + case P_NAME: + while ((*ptr != '\0') && + (*ptr != '.') && + (i < sizeof(spec->fname))) { + char ch = toupper(*ptr++); + + if (isalnum(ch)) { + toSixbit(ch, &spec->name[i >> 1], i & 1); + spec->fname[i++] = ch; + } else { + if (ch == '*') + if (i == 0) { + spec->flags |= OS8_WC_NAME; + break; + } + return 0; + } + } + + switch (*ptr) { + case '\0': + state = P_DONE; + break; + + case '.': + state = P_EXT; + ptr++; + break; + + default: + return 0; + } + break; + + case P_EXT: + while ((*ptr != '\0') && + (i < sizeof(spec->fext))) { + char ch = toupper(*ptr++); + + if (isalnum(ch)) { + toSixbit(ch, &spec->ext, i); + spec->fext[i++] = ch; + } else { + if (ch == '*') + if (i == 0) { + spec->flags |= OS8_WC_EXT; + break; + } + return 0; + } + } + + if (*ptr != '\0') + return 0; + + state = P_DONE; + break; + } + } + return 1; +} + +/*++ + * o s 8 C r e a t e F i l e + * + * Create a new file within the OS/8 file system. + * + * Inputs: + * + * mount - pointer to a mounted file system descriptor + * unit - file system unit number + * spec - pointer to the file specification block + * file - pointer to open file descriptor to receive results + * size - file size in 256-word blocks + * 0 means use the largest free space region + * + * Outputs: + * + * The mount point specific buffer area will be modified + * + * Returns: + * + * 1 if file successfully created, 0 otherwise + * + --*/ +int os8CreateFile( + struct mountedFS *mount, + uint8_t unit, + struct os8FileSpec *spec, + struct os8OpenFile *file, + uint16_t size +) +{ + struct OS8data *data = &mount->os8data; + uint16_t segment, off, start, entries, extra, entrysz, space, left; + + retry: + /* + * Check if suitable free space is available + */ + if (os8FindSpace(mount, unit, size, &segment, &off, &start) == 0) { + ERROR("Free space not available on \"%s%o:\"\n", mount->name, unit); + return 0; + } + + /* + * Check if there is space to create a new directory entry in this + * directory segment. If not, split the directory segment in 2 and retry + * from the beginning. + */ + if (!os8CheckDirectory(mount)) { + if (!os8SplitDirectory(mount, unit, segment)) { + ERROR("No directory space on \"%s%o:\"\n", mount->name, unit); + return 0; + } + goto retry; + } + + entries = os8Neg(le16toh(data->buf[OS8_DH_ENTRIES])); + extra = -os8SXT(le16toh(data->buf[OS8_DH_EXTRA])); + entrysz = OS8_DI_SIZE + extra; + + space = size == 0 ? os8Neg(le16toh(data->buf[off + OS8_ED_LENGTH])) : size; + left = os8Neg(le16toh(data->buf[off + OS8_ED_LENGTH])) - space; + + /* + * Remove the requested space from the empty directory entry. + */ + data->buf[off + OS8_ED_LENGTH] = htole16(os8Value(-left)); + + os8SlideDown(mount, off, entries, entrysz, &file->remain); + + /* + * Build the directory entry + */ + data->buf[off + OS8_DI_FNAME1] = htole16(os8Value(spec->name[0])); + data->buf[off + OS8_DI_FNAME2] = htole16(os8Value(spec->name[1])); + data->buf[off + OS8_DI_FNAME3] = htole16(os8Value(spec->name[2])); + data->buf[off + OS8_DI_EXT] = htole16(os8Value(spec->ext)); + + if (extra != 0) + data->buf[off + OS8_DI_DATE] = htole16(os8Value(data->date)); + + data->buf[off + extra + OS8_DI_LENGTH] = htole16(os8Value(-space)); + + if (os8WriteBlock(mount, unit, segment, NULL) == 0) + return 0; + + /* + * Fill in the open file descriptor + */ + file->name[0] = spec->name[0]; + file->name[1] = spec->name[1]; + file->name[2] = spec->name[2]; + file->ext = spec->ext; + file->creation = data->date; + file->length = space; + + file->segment = segment; + file->offset = off; + file->entrysz = entrysz; + file->extra = extra; + + file->mount = mount; + file->unit = unit; + + file->current = 0; + file->start = start; + file->last = start + space - 1; + + file->wordpos = OS8_BLOCKSIZE; + file->bytepos = OS8_CHECK; + file->written = 0; + + return 1; +} + +/*++ + * o s 8 L o o k u p F i l e + * + * Lookup a specific file within the OS/8 file system. This routine fills in + * an open file descriptor with information about the file and the directory + * entry it resides in. + * + * Inputs: + * + * mount - pointer to a mounted file system descriptor + * unit - file system unit number + * spec - pointer to the file specification block + * file - pointer to open file descriptor to receive results + * + * Outputs: + * + * The mount point specific buffer will be modified + * + * Returns: + * + * 1 if file found, 0 otherwise + * + --*/ +int os8LookupFile( + struct mountedFS *mount, + uint8_t unit, + struct os8FileSpec *spec, + struct os8OpenFile *file +) +{ + struct OS8data *data = &mount->os8data; + unsigned int dirblk = OS8_DSSTART; + + if (os8UnitValid(data, unit)) { + do { + uint16_t i, entrysz, entries, extra, startblk, off = OS8_DH_SIZE; + + if (os8ReadBlock(mount, unit, dirblk, NULL) == 0) + return 0; + + entrysz = OS8_DI_SIZE + (-os8SXT(le16toh(data->buf[OS8_DH_EXTRA]))); + entries = os8Neg(le16toh(data->buf[OS8_DH_ENTRIES])); + extra = os8Neg(le16toh(data->buf[OS8_DH_EXTRA])); + + startblk = os8Word(le16toh(data->buf[OS8_DH_START])); + + for (i = 0; i < entries; i++) { + if (os8Word(le16toh(data->buf[off + OS8_DI_FNAME1])) != 0) { + if ((os8Word(le16toh(data->buf[off + OS8_DI_FNAME1])) == spec->name[0]) && + (os8Word(le16toh(data->buf[off + OS8_DI_FNAME2])) == spec->name[1]) && + (os8Word(le16toh(data->buf[off + OS8_DI_FNAME3])) == spec->name[2]) && + (os8Word(le16toh(data->buf[off + OS8_DI_EXT])) == spec->ext)) { + file->name[0] = spec->name[0]; + file->name[1] = spec->name[1]; + file->name[2] = spec->name[2]; + file->ext = spec->ext; + file->creation = + extra != 0 ? os8Word(le16toh(data->buf[off + OS8_DI_DATE])) : 0; + file->length = os8Neg(le16toh(data->buf[off + extra + OS8_DI_LENGTH])); + + file->segment = dirblk; + file->offset = off; + file->entrysz = entrysz; + file->extra = extra; + file->remain = entries - (i + 1); + + file->mount = mount; + file->unit = unit; + + file->current = 0; + if (file->length != 0) { + file->start = startblk; + file->last = startblk + file->length - 1; + } else file->start = file->last = 0; + + file->wordpos = OS8_BLOCKSIZE; + file->bytepos = OS8_CHECK; + file->written = 0; + + return 1; + } + startblk += os8Neg(le16toh(data->buf[off + extra + OS8_DI_LENGTH])); + off += entrysz; + } else { + startblk += os8Neg(le16toh(data->buf[off + OS8_ED_LENGTH])); + off += OS8_ED_SIZE; + } + } + dirblk = os8Word(le16toh(data->buf[OS8_DH_NEXT])); + } while (dirblk != 0); + } + return 0; +} + +/*++ + * o s 8 U p d a t e F i l e + * + * Update an OS/8 file by writing back the directory entry associated with + * the file. + * + * Inputs: + * + * mount - pointer to a mounted file system descriptor + * unit - file system unit number + * file - pointer to open file descriptor + * + * Outputs: + * + * The mount point specific buffer will be modified + * + * Returns: + * + * None + * + --*/ +void os8UpdateFile( + struct mountedFS *mount, + uint8_t unit, + struct os8OpenFile *file +) +{ + struct OS8data *data = &mount->os8data; + uint16_t off = file->offset; + uint16_t empty = off + file->extra + OS8_DI_SIZE; + uint16_t length = file->current == 0 ? 0 : file->current - file->start; + uint16_t left = file->length - length; + uint16_t space; + + if (os8ReadBlock(mount, unit, file->segment, NULL) == 0) + return; + + /* + * The directory entry is complete except for the # of blocks in use + * which we can now update. + */ + data->buf[off + file->extra + OS8_DI_LENGTH] = htole16(os8Value(-length)); + + /* + * Update the remaining space in the following empty file descriptor. + */ + space = os8Neg(le16toh(data->buf[empty + OS8_ED_LENGTH])) + left; + data->buf[empty + OS8_ED_LENGTH] = htole16(os8Value(-space)); + + if (space == 0) + /* + * Remove the empty directory entry since it now occupies 0 blocks + */ + os8SlideUp(mount, empty, empty + OS8_ED_SIZE, file->entrysz, file->remain); + + os8WriteBlock(mount, unit, file->segment, NULL); +} + +/*++ + * o s 8 R e a d B y t e s + * + * Read a sequence of bytes from an open file. Each pair of 12-bit words in + * the file will be unpacked into 3 bytes. + * + * Inputs: + * + * file - pointer to an open file descriptor + * buf - pointer to a buffer to receive the data + * len - # of bytes of data to read + * + * Outputs: + * + * The buffer will be overwritten by up to "len" bytes + * + * Returns: + * + * # of bytes read from the file (may be less than "len"), 0 if EOF + * + --*/ +int os8ReadBytes( + struct os8OpenFile *file, + char *buf, + int len +) +{ + struct mountedFS *mount = file->mount; + int count = 0; + uint16_t temp1, temp2; + char mask = SWISSET('a') ? 0x7F : 0xFF; + + while (len) { + switch (file->bytepos) { + case OS8_CHECK: + if (file->wordpos == OS8_BLOCKSIZE) { + if (file->current == 0) { + /* + * For an empty file (does OS/8 support this?) + * start == current == last == 0 + */ + if (file->start == 0) + return 0; + + file->current = file->start; + } else { + if (file->current == file->last) + return count; + file->current++; + } + if (os8ReadBlock(mount, file->unit, file->current, file->buffer) == 0) + return 0; + file->wordpos = 0; + } + file->bytepos = OS8_BYTE0; + /* FALLTHROUGH */ + + case OS8_BYTE0: + temp1 = os8Word(le16toh(file->buffer[file->wordpos])); + *buf++ = temp1 & mask; + file->bytepos = OS8_BYTE1; + break; + + case OS8_BYTE1: + temp1 = os8Word(le16toh(file->buffer[file->wordpos + 1])); + *buf++ = temp1 & mask; + file->bytepos = OS8_BYTE2; + break; + + case OS8_BYTE2: + temp1 = os8Word(le16toh(file->buffer[file->wordpos])); + temp2 = os8Word(le16toh(file->buffer[file->wordpos + 1])); + *buf++ = (((temp1 & 0xF00) >> 4) | ((temp2 & 0xF00) >> 8)) & mask; + file->wordpos += 2; + file->bytepos = OS8_CHECK; + break; + } + len--; + count++; + } + return count; +} + +/*++ + * o s 8 W r i t e B y t e s + * + * Write a sequence of bytes to an open file. Each sequence of 3 bytes will + * generate 2 words in the file. + * + * Inputs: + * + * file - pointer to an open file descriptor + * buf - pointer to a buffer to receive the data + * len - # of bytes of data to read + * + * Outputs: + * + * None + * + * Returns: + * + * # of bytes written to the file (may be less than "len"), 0 if errort + * + --*/ +int os8WriteBytes( + struct os8OpenFile *file, + char *buf, + int len +) +{ + struct mountedFS *mount = file->mount; + int count = 0; + + while (len) { + switch (file->bytepos) { + case OS8_CHECK: + if (file->wordpos == OS8_BLOCKSIZE) { + if (file->current == file->last) + return count; + + if (file->current != 0) { + if (os8WriteBlock(mount, file->unit, file->current, file->buffer) == 0) + return 0; + + file->current++; + } else file->current = file->start; + + file->wordpos = 0; + memset(file->buffer, 0, OS8_BLOCKSIZE * sizeof(uint16_t)); + } + file->bytepos = OS8_BYTE0; + /* FALLTHROUGH */ + + case OS8_BYTE0: + file->buffer[file->wordpos++] = os8Value(*buf++); + file->bytepos = OS8_BYTE1; + break; + + case OS8_BYTE1: + file->buffer[file->wordpos++] = os8Value(*buf++); + file->bytepos = OS8_BYTE2; + break; + + case OS8_BYTE2: + file->buffer[file->wordpos - 2] = + os8Value(file->buffer[file->wordpos - 2] | ((*buf & 0xF0) << 4)); + file->buffer[file->wordpos - 1] = + os8Value(file->buffer[file->wordpos - 1] | ((*buf++ & 0xF) << 8)); + file->bytepos = OS8_CHECK; + break; + } + len--; + count++; + file->written++; + } + return count; +} + +/*++ + * o s 8 M o u n t + * + * Verify that the open container file contains 1 or more OS/8 file systems. + * + * Inputs: + * + * mount - pointer to a mounted file system descriptor + * (not in the mounted file system list) + * + * Outputs: + * + * None + * + * Returns: + * + * 1 if 1 or more valid OS/8 file systems + * 0 if no valid OS/8 file system present + * -1 if no valid OS/8 file system present and an error message has + * already been printed + * + --*/ +static int os8Mount( + struct mountedFS *mount +) +{ + struct OS8data *data = &mount->os8data; + struct stat stat; + uint8_t valid = 0; + uint16_t files[8], freeblks[8], extra[8]; + + memset(files, 0, sizeof(files)); + memset(freeblks, 0, sizeof(freeblks)); + memset(extra, 0, sizeof(extra)); + + /* + * Allow access to all filesystems for validation + */ + data->device = NULL; + data->valid = 0xFF; + + if (fstat(fileno(mount->container), &stat) == 0) { + data->blocks = stat.st_size / OS8_BLOCKSIZE; + + if (SWISSET('t')) { + struct OS8device *dev = OS8Devices; + + while (dev->name != NULL) { + if (strcmp(SWGETVAL('t'), dev->name) == 0) { + uint8_t i, count = 0; + + mount->skip = dev->skip; + data->device = dev; + + /* + * Validate all possible file systems. + */ + for (i = 0; i < dev->filesys; i++) + if (validate(mount, i, &files[i], &freeblks[i], &extra[i]) != 0) { + count++; + valid |= 1 << i; + } + + if (valid != 0) { + if (!quiet) + printf("%s: successfully mounted (%d file system%s)\n\n", + mount->name, count, count == 1 ? "" : "s"); + + for (i = 0; i < dev->filesys; i++) + if ((valid & 1 << i) != 0) { + printf("%s%o:\n", mount->name, i); + printf(" Total Blocks: %4d, Free Blocks: %4d\n" + " Files: %4d\n" + " Extra words/directory entry: %d\n", + dev->blocks[i], freeblks[i], files[i], extra[i]); + } + } + + data->valid = valid; + return valid != 0 ? 1 : 0; + } + dev++; + } + fprintf(stderr, "mount: unknown disk type - \"%s\"\n", + SWGETVAL('t')); + return -1; + } else { + fprintf(stderr, "mount: unable to determine disk type," + " use \"-t type\"\n"); + return -1; + } + } + return 0; +} + +/*++ + * o s 8 U m o u n t + * + * Unmount the OS/8 file system(s), releasing any storage allocated. + * + * Inputs: + * + * mount - pointer to the mounted file system descriptor + * + * Outputs: + * + * None + * + * Returns: + * + * None + * + --*/ +static void os8Umount( + struct mountedFS *UNUSED(mount) +) +{ +} + +/*++ + * o s 8 S i z e + * + * Return the size of an OS/8 container file. + * + * Inputs: + * + * None + * + * Outputs: + * + * None + * + * Returns: + * + * Size of the container file in block of default file system size + * + --*/ +static size_t os8Size(void) +{ + if (SWISSET('t')) { + struct OS8device *dev = OS8Devices; + + while (dev->name != NULL) { + if (strcmp(SWGETVAL('t'), dev->name) == 0) + return dev->diskSize; + + dev++; + } + } + return OS8Devices[0].diskSize; +} + +/*++ + * o s 8 N e w f s + * + * Create empty OS/8 file system(s). + * + * Inputs: + * + * mount - pointer to a mounted file system descriptor + * (nit in the mounted file system list) + * size - the sized (in blocks) of the filesystem + * + * Outputs: + * + * None + * + * Returns: + * + * 1 if the file system(s) were successfully created, 0 otherwise + * + --*/ +static int os8Newfs( + struct mountedFS *mount, + size_t UNUSED(size) +) +{ + struct OS8data *data = &mount->os8data; + uint16_t extra = 1; + struct OS8device *dev = OS8Devices; + uint8_t unit; + uint16_t i; + + if (SWISSET('t')) { + while (dev->name != NULL) { + if (strcmp(SWGETVAL('t'), dev->name) == 0) + goto found; + + dev++; + } + dev = OS8Devices; + } + found: + mount->skip = dev->skip; + data->device = dev; + + if (SWISSET('e')) { + char *endptr; + + extra = strtoul(SWGETVAL('e'), &endptr, 10); + if ((extra > 63) || (*endptr != '\0')) + return 0; + } + + for (unit = 0; unit < dev->filesys; unit++) { + data->valid |= (1 << unit); + + for (i = OS8_DSSTART; i <= OS8_DSLAST; i++) { + uint16_t buf[OS8_BLOCKSIZE]; + uint16_t entries = i == OS8_DSSTART ? 1 : 0; + + buf[OS8_DH_ENTRIES] = htole16(os8Value(-entries)); + buf[OS8_DH_START] = htole16(os8Value(OS8_DATA)); + buf[OS8_DH_NEXT] = htole16(os8Value(0)); + buf[OS8_DH_FLAGWD] = htole16(os8Value(0)); + buf[OS8_DH_EXTRA] = htole16(os8Value(-extra)); + + buf[OS8_DH_SIZE + OS8_ED_IND] = htole16(os8Value(0)); + buf[OS8_DH_SIZE + OS8_ED_LENGTH] = + htole16(os8Value(-(dev->blocks[unit] - OS8_DATA))); + + if (os8WriteBlock(mount, unit, i, buf) == 0) + return 0; + } + } + return 1; +} + +/*++ + * o s 8 S e t + * + * Set mount point specific values. + * + * Inputs: + * + * mount - pointer to a mounted file system descriptor + * unit - file system unit number (unused) + * present - file system unit number present (unused) + * + * Outputs: + * + * None + * + * Returns: + * + * None + * + --*/ +static void os8Set( + struct mountedFS *mount, + uint8_t UNUSED(unit), + uint8_t UNUSED(present) +) +{ + struct OS8data *data = &mount->os8data; + int idx = 0; + unsigned long year; + char *endptr; + + while (setCmds[idx] != NULL) { + if (strcmp(words[1], setCmds[idx]) == 0) { + switch (idx) { + case OS8SET_YEAR: + if (args == 3) { + if (strcmp(words[2], "none") == 0) { + data->date = 0; + return; + } + year = strtoul(words[2], &endptr, 10); + if ((*endptr == '\0') && (year <= 7)) { + struct tm tm; + time_t now = time(NULL); + + localtime_r(&now, &tm); + + data->date = (tm.tm_mon + 1) << 8; + data->date |= (tm.tm_mday + 1) << 3; + data->date |= year; + return; + } + } + fprintf(stderr, "os8: Invalid syntax for \"set year\"\n"); + return; + + default: + fprintf(stderr, "os8: \"%s\" not implemented\n", words[1]); + return; + } + } + idx++; + } + fprintf(stderr, "os8: Unknown set command \"%s\"\n", words[1]); +} + +/*++ + * o s 8 I n f o + * + * Display information about the internal structure of the OS/8 file + * system(s). + * + * Inputs: + * + * mount - pointer to the mounted file system descriptor + * unit - file system unit number + * present - file system unit number present + * + * Outputs: + * + * None + * + * Returns: + * + * None + * + --*/ +static void os8Info( + struct mountedFS *mount, + uint8_t unit, + uint8_t present +) +{ + struct OS8data *data = &mount->os8data; + struct OS8device *device = data->device; + + if (present) { + if (os8UnitValid(data, unit)) + info(mount, unit); + } else { + uint16_t i; + + /* + * Display information about all valid units + */ + for (i = 0; i < device->filesys; i++) + if (os8UnitValid(data, i)) + info(mount, i); + } +} + +/*++ + * o s 8 D i r + * + * Produce a full or brief directory listing. + * + * Inputs: + * + * mount - pointer to the mounted file system descriptor + * unit - file system unit number + * fname - pointer to filename string + * + * Outputs: + * + * None + * + * Returns: + * + * None + * + --*/ +static void os8Dir( + struct mountedFS *mount, + uint8_t unit, + char *fname +) +{ + struct OS8data *data = &mount->os8data; + struct os8FileSpec spec; + + if (os8ParseFilespec(fname, &spec, OS8_M_NONAME) == 0) { + fprintf(stderr, "dir: syntax error in file spec \"%s\"\n", fname); + return; + } + + if (os8UnitValid(data, unit)) { + unsigned int dirblk = OS8_DSSTART; + + do { + uint16_t i, entrysz, entries, extra, off = OS8_DH_SIZE; + + if (os8ReadBlock(mount, unit, dirblk, NULL) == 0) + return; + + entrysz = OS8_DI_SIZE + (-os8SXT(le16toh(data->buf[OS8_DH_EXTRA]))); + entries = os8Neg(le16toh(data->buf[OS8_DH_ENTRIES])); + extra = os8Neg(le16toh(data->buf[OS8_DH_EXTRA])); + + for (i = 0; i < entries; i++) { + char temp1[16], temp2[16]; + + if (os8Word(le16toh(data->buf[off + OS8_DI_FNAME1])) != 0) { + if ((spec.flags & OS8_WC_NAME) == 0) + if ((os8Word(le16toh(data->buf[off + OS8_DI_FNAME1])) != spec.name[0]) || + (os8Word(le16toh(data->buf[off + OS8_DI_FNAME2])) != spec.name[1]) || + (os8Word(le16toh(data->buf[off + OS8_DI_FNAME3])) != spec.name[2])) { + off += entrysz; + continue; + } + + if ((spec.flags & OS8_WC_EXT) == 0) + if (os8Word(le16toh(data->buf[off + OS8_DI_EXT])) != spec.ext) { + off += entrysz; + continue; + } + + if (SWISSET('f')) { + uint16_t length, dateval = 0; + + length = os8Neg(le16toh(data->buf[off + extra + OS8_DI_LENGTH])); + if (extra) + dateval = os8Word(le16toh(data->buf[off + OS8_DI_DATE])); + + printf("%s %4hu %s\n", + os8FileName(mount, off, temp1), length, + os8Date(dateval, temp2)); + } else printf("%s\n", os8FileName(mount, off, temp1)); + off += entrysz; + } else off += OS8_ED_SIZE; + } + + dirblk = os8Word(le16toh(data->buf[OS8_DH_NEXT])); + } while (dirblk != 0); + } +} + +/*++ + * o s 8 O p e n F i l e R + * + * Open an OS/8 file for reading. + * + * Inputs: + * + * mount - pointer to the mounted file system descriptor + * unit - file system unit number + * fname - pointer to filename string + * + * Outputs: + * + * None + * + * Returns: + * + * Pointer to open file descriptor, NULL if open fails + * + --*/ +static void *os8OpenFileR( + struct mountedFS *mount, + uint8_t unit, + char *fname +) +{ + struct os8OpenFile *file; + struct os8FileSpec spec; + + if (os8ParseFilespec(fname, &spec, OS8_M_NONE) == 0) { + fprintf(stderr, "Failed to parse filename \"%s\"\n", fname); + return NULL; + } + + if ((file = malloc(sizeof(struct os8OpenFile))) != NULL) { + memset(file, 0, sizeof(struct os8OpenFile)); + + if (os8LookupFile(mount, unit, &spec, file) != 0) { + /* + * Allocate local buffer space for the file. + */ + if ((file->buffer = malloc(mount->blocksz)) == NULL) { + free(file); + return NULL; + } + file->mode = M_RD; + } else { + free(file); + return NULL; + } + } + return file; +} + +/*++ + * o s 8 O p e n F i l e W + * + * Open an OS/8 file for writing. + * + * Inputs: + * + * mount - pointer to the mounted file system descriptor + * unit - file system unit number + * fname - pointer to filename string + * size - estimated file size (in bytes) + * 0 means allocate as much space as possible + * + * Outputs: + * + * None + * + * Returns: + * + * Pointer to open file descriptor, NULL is open fails + * + --*/ +void *os8OpenFileW( + struct mountedFS *mount, + uint8_t unit, + char *fname, + off_t size +) +{ + struct os8OpenFile *file; + struct os8FileSpec spec; + uint16_t blocks = 0; + + if (size != 0) + blocks = (size + ((OS8_BLOCKSIZE * 3) / 2) - 1) / (((OS8_BLOCKSIZE) * 3) / 2); + + if (os8ParseFilespec(fname, &spec, OS8_M_NONE) == 0) { + fprintf(stderr, "Failed to parse filename \"%s\"\n", fname); + return NULL; + } + + if ((file = malloc(sizeof(struct os8OpenFile))) != NULL) { + memset(file, 0, sizeof(struct os8OpenFile)); + + if (os8LookupFile(mount, unit, &spec, file) == 0) { + /* + * Allocate local buffer space for the file.\ + */ + if ((file->buffer = malloc(mount->blocksz)) != NULL) { + memset(file->buffer, 0, mount->blocksz); + + if (os8CreateFile(mount, unit, &spec, file, blocks) == 0) { + ERROR("Failed to create file \"%s\"\n", fname); + free(file->buffer); + free(file); + return NULL; + } else file->mode = M_WR; + } else { + ERROR("Buffer allocation failure for \"%s\"\n", fname); + free(file); + return NULL; + } + } else { + ERROR("File \"%s\" already exists\n", fname);\ + free(file); + return NULL; + } + } else ERROR("Memory allocation failure\n"); + return file; +} + +/*++ + * o s 8 F i l e S i z e + * + * Return an estimate of the size of a currently open file. This routine + * bases the file size on the number of blocks allocated to the file and + * may over-report the actual sized of the file. + * + * Inputs: + * + * filep - pointer to open file descriptor + * + * Outputs: + * + * None + * + * Returns: + * + * Estimate of the file size + * + --*/ +static off_t os8FileSize( + void *filep +) +{ + struct os8OpenFile *file = filep; + + return (file->length * RT11_BLOCKSIZE * 2) / 3; +} + +/*++ + * o s 8 D e l e t e F i l e + * + * Delete a file from an O/8 file system. + * + * Inputs: + * + * filep - pointer to open file descriptor + * fname - pointer to filename string + * + * Outputs: + * + * The mount specific buffer will be modified. + * + * Returns: + * + * None + * + --*/ +static void os8DeleteFile( + void *filep, + char *UNUSED(fname) +) +{ + struct os8OpenFile *file = filep; + struct mountedFS *mount = file->mount; + struct OS8data *data = &mount->os8data; + + if (os8ReadBlock(mount, file->unit, file->segment, NULL) == 0) + return; + + data->buf[file->offset + OS8_DI_FNAME1] = 0; + data->buf[file->offset + OS8_ED_LENGTH] = + data->buf[file->offset + file->extra + OS8_DI_LENGTH]; + data->buf[OS8_DH_ENTRIES] = + htole16(os8Value(-(os8Neg(le16toh(data->buf[OS8_DH_ENTRIES])) - 1))); + + os8SlideUp(mount, file->offset + OS8_ED_SIZE, + file->offset + file->entrysz, file->entrysz, file->remain); + + /* + * Merge any adjacent empty directory entries. + */ + if (os8MergeEmptyRegions(mount) != 0) + os8MergeEmptyRegions(mount); + + if (os8WriteBlock(mount, file->unit, file->segment, NULL) == 0) + return; + + os8CloseFile(file); +} + +/*++ + * o s 8 C l o s e F i l e + * + * Close an open OS/8 file. + * + * Inputs: + * + * filep - pointer to open file descriptor + * + * Outputs: + * + * None + * + * Returns: + * + * None + * + --*/ +static void os8CloseFile( + void *filep +) +{ + struct os8OpenFile *file = filep; + + if (file->mode == M_WR) { + if (file->current != 0) + /* + * Flush the current buffer. + */ + os8WriteBlock(file->mount, file->unit, file->current++, file->buffer); + + os8UpdateFile(file->mount, file->unit, file); + } + + if (file->buffer != NULL) + free(file->buffer); + free(file); +} + +/*++ + * o s 8 R e a d F i l e + * + * Read data from an OS/8 file to a supplied buffer + * + * Inputs: + * + * filep - pointer to open file descriptor + * buf - pointer to buffer + * buflen - length of the supplied buffer + * + * Outputs: + * + * None + * + * Returns: + * + * # of bytes of data read, 0 means EOF or error + * + --*/ +static size_t os8ReadFile( + void *filep, + void *buf, + size_t buflen +) +{ + struct os8OpenFile *file = filep; + + return os8ReadBytes(file, buf, buflen); +} + +/*++ + * o s 8 W r i t e F i l e + * + * Write data to an OS/8 file from a supplied buffer. + * + * Inputs: + * + * filep - pointer to open file descriptor + * buf - pointer to buffer + * buflen - length of the supplied buffer + * + * Outputs: + * + * None + * + * Returns: + * + * # of bytes of data written, 0 means EOF or error + * + --*/ +static size_t os8WriteFile( + void *filep, + void *buf, + size_t buflen +) +{ + struct os8OpenFile *file = filep; + + return os8WriteBytes(file, buf, buflen); +} + +/*++ + * o s 8 F S + * + * Descriptor for accessing OS/8 file systems. + * + --*/ +struct FSdef os8FS = { + NULL, + "os8", + "os8 PDP-8 OS/8 file system (RX01, RX02 or RK05 disks)\n", + FS_UNITVALID, + OS8_BLOCKSIZE * sizeof(uint16_t), + os8Mount, + os8Umount, + os8Size, + os8Newfs, + os8Set, + os8Info, + os8Dir, + os8OpenFileR, + os8OpenFileW, + os8FileSize, + os8CloseFile, + os8ReadFile, + os8WriteFile, + os8DeleteFile, + NULL, /* No tape support functions */ + NULL, + NULL, + NULL +}; diff --git a/converters/fsio/os8.h b/converters/fsio/os8.h new file mode 100644 index 0000000..4346503 --- /dev/null +++ b/converters/fsio/os8.h @@ -0,0 +1,248 @@ +/* + * Copyright (C) 2019 John Forecast. All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY JOHN FORECAST "AS IS" AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef __OS8_H__ +#define __OS8_H__ + +/* + * General disk layout (each block is 256 12-bit words). + * + * Block + * +---+---+---+---+---+---+---+---+---+---+---+---+ + * 0 | Reserved | + * +---+---+---+---+---+---+---+---+---+---+---+---+ + * 1-6 | Directory segments | + * +---+---+---+---+---+---+---+---+---+---+---+---+ + * 7-12 | Keyboard Monitor | ** + * +---+---+---+---+---+---+---+---+---+---+---+---+ + * 13-15 | User Service Routine | ** + * +---+---+---+---+---+---+---+---+---+---+---+---+ + * 16-25 | Device Handlers | ** + * +---+---+---+---+---+---+---+---+---+---+---+---+ + * 26 | Enter Processor For USR | ** + * +---+---+---+---+---+---+---+---+---+---+---+---+ + * 27-50 | System Scratch Blocks | ** + * +---+---+---+---+---+---+---+---+---+---+---+---+ + * 51-53 | Command Decoder | ** + * +---+---+---+---+---+---+---+---+---+---+---+---+ + * 54-55 | SAVE And DATE Overlays | ** + * +---+---+---+---+---+---+---+---+---+---+---+---+ + * 56 | Monitor Error Routine | ** + * +---+---+---+---+---+---+---+---+---+---+---+---+ + * 57 | CHAIN Processor For USR | ** + * +---+---+---+---+---+---+---+---+---+---+---+---+ + * 60-63 | System ODT | ** + * +---+---+---+---+---+---+---+---+---+---+---+---+ + * 64 | Reserved For System Expansion | ** + * +---+---+---+---+---+---+---+---+---+---+---+---+ + * 65 | CCL Reminiscences | ** + * +---+---+---+---+---+---+---+---+---+---+---+---+ + * 66 | 12K TD8E Resident Code | ** + * +---+---+---+---+---+---+---+---+---+---+---+---+ + * 67 | CCL Overlay | ** + * +---+---+---+---+---+---+---+---+---+---+---+---+ + * Files | ... | + * | ... | + * | ... | + * | ... | + * +---+---+---+---+---+---+---+---+---+---+---+---+ + * End Of Volume + * + * Blocks marked "**" are only reserved on System Devices, on non-System + * Devices, file storage starts at block 7. + * + * Directory Segment Header: + * + * Word + * +---+---+---+---+---+---+---+---+---+---+---+---+ + * 0 | Minus # of entries in this segment | + * +---+---+---+---+---+---+---+---+---+---+---+---+ + * 1 | Starting block # of first file in this segment| + * +---+---+---+---+---+---+---+---+---+---+---+---+ + * 2 | Link to next directory segment | + * +---+---+---+---+---+---+---+---+---+---+---+---+ + * 3 | Flag Word | + * +---+---+---+---+---+---+---+---+---+---+---+---+ + * 4 | Minus # of additional information words | + * +---+---+---+---+---+---+---+---+---+---+---+---+ + * + * Directory Entry: + * + * Word + * +---+---+---+---+---+---+---+---+---+---+---+---+ + * 0 | Sixbit ASCII File Name (chars 1 - 2) | + * +---+---+---+---+---+---+---+---+---+---+---+---+ + * 1 | Sixbit ASCII File Name (chars 3 - 4) | + * +---+---+---+---+---+---+---+---+---+---+---+---+ + * 2 | Sixbit ASCII File Name (chars 5 - 6) | + * +---+---+---+---+---+---+---+---+---+---+---+---+ + * 3 | Sixbit ASCII File Extension (chars 1 - 2) | + * +---+---+---+---+---+---+---+---+---+---+---+---+ + * | N additional information words | + * | ... | + * | ... | + * +---+---+---+---+---+---+---+---+---+---+---+---+ + * N + 4 | Minus file length in blocks | + * +---+---+---+---+---+---+---+---+---+---+---+---+ + * + * If there are additional informations words in the directory, the file + * creation time is stored in word 4 (zero means not available): + * + * +---+---+---+---+---+---+---+---+---+---+---+---+ + * | | | | + * +---+---+---+---+---+---+---+---+---+---+---+---+ + * | | | | | | + * | | | | +-------+ + * | | +---------------+ | + * +-----------+ | Year - 1970 + * | Day of Month (1 - 31) + * Month (1 - 12) + */ + +#define OS8_BLOCKSIZE 256 /* Size of a block on disk (words) */ + +#define OS8_DSSTART 1 /* Start of directory segments */ +#define OS8_DSLAST 6 /* Last directory segment */ +#define OS8_DATA 7 /* Start of data (non-system device) */ + +#define OS8_DH_ENTRIES 0 /* -(# of entries in segment) */ +#define OS8_DH_START 1 /* Block # for segment start */ +#define OS8_DH_NEXT 2 /* Next directory segment */ +#define OS8_DH_FLAGWD 3 /* Flag word */ +#define OS8_DH_EXTRA 4 /* -(# of additional info. words) */ +#define OS8_DH_SIZE 5 /* Size of directory header */ + +#define OS8_DI_FNAME1 0 /* File name (chars 1 - 2) */ +#define OS8_DI_FNAME2 1 /* File name (chars 3 - 4) */ +#define OS8_DI_FNAME3 2 /* File name (chars 5 - 6) */ +#define OS8_DI_EXT 3 /* File extension (1 - 2 chars) */ + /* Additional words go here */ +#define OS8_DI_DATE 4 /* Optional creation date */ +#define OS8_DI_LENGTH 4 /* -(File length in blocks) */ +#define OS8_DI_SIZE 5 /* Default entry size */ + +#define OS8_ED_IND 0 /* Empty directory entry */ +#define OS8_ED_LENGTH 1 /* -(Empty file length in blocks) */ +#define OS8_ED_SIZE 2 /* Entry size */ + +/* + * Device specific definitions + */ +#define OS8_RK05FS_BLKS 3248 /* Blocks in an RK05 file system */ + +#define OS8_RX01SS 128 /* Byte sector size of RX01 floppy */ +#define OS8_RX02SS 256 /* Byte sector size of RX02 floppy */ +#define OS8_RX01SS_W 64 /* Word sector size of RX01 floppy */ +#define OS8_RX02SS_W 128 /* Word sector size of RX02 floppy */ +#define OS8_RX0xNSECT 26 /* Sectors/track on RX01/RX02 */ +#define OS8_RX0xSZ 2002 /* Sectors on an RX01/RX02 */ + +/* + * Structure to describe a filename. Asterisks may be used as wild card + * characters for the 2 components of a filename; namd and extension + */ +struct os8FileSpec { + uint8_t flags; /* Wild card indicators */ + uint16_t name[3]; /* File name (sixbit) */ + uint16_t ext; /* File extension (sixbit) */ + char fname[6]; /* File name (ASCII) */ + char fext[2]; /* File extension (ASCII) */ +}; +#define OS8_WC_NAME 001 /* Wild card in name */ +#define OS8_WC_EXT 002 /* Wild card in extension */ + +#define OS8_M_NONE 0000 /* Wild cards not allowed */ +#define OS8_M_ALLOW 0001 /* Wild cards allowed */ +#define OS8_M_NONAME 0002 /* Wild cards allowed */ + /* If no filename + extension */ + /* present, default to *.* */ +/* + * OS/8 files pack 3 bytes into a pair of 12-bit words. The following states + * are used to pack/unpack bytes. + */ +#define OS8_BYTE0 0 /* Pack/unpack byte 0 */ +#define OS8_BYTE1 1 /* Pack/unpack byte 1 */ +#define OS8_BYTE2 2 /* Pack/unpack byte 2 */ +#define OS8_CHECK 3 /* Check for end of block/EOF */ + +/* + * Structure to define an open file. This is an OS/8 directory entry along + * with sufficient information to be able to write the directory entry back + * to disk. + */ +struct os8OpenFile { + uint16_t name[3]; /* File name */ + uint16_t ext; /* File extension */ + uint16_t creation; /* Creation date */ + uint16_t length; /* File length (blocks) */ + /* End of directory entry */ + uint8_t segment; /* Directory segment */ + uint16_t offset; /* Directory offset */ + uint16_t entrysz; /* Size of directory entries */ + uint16_t extra; /* Extra words in directory entries */ + uint16_t remain; /* Remaining directory entries */ + /* Start of read/write info */ + enum openMode mode; /* Open mode (read/write) */ + struct mountedFS *mount; /* Mounted file system descriptor */ + uint8_t unit; /* File system # */ + uint16_t *buffer; /* Private buffer for file I/O */ + uint16_t start; /* Starting block # */ + uint16_t current; /* Current block # */ + uint16_t last; /* Last block # */ + uint16_t wordpos; /* Current word offset */ + uint8_t bytepos; /* Current byte position */ + off_t written; /* # of bytes written to the file */ +}; + +/* + * Device descriptor + */ +struct OS8device { + char *name; /* Device name */ + uint8_t filesys; /* # of file systems on device */ + size_t diskSize; /* # of blocks on device */ + off_t skip; /* Reserved space at start of disk */ + uint16_t blocks[8]; /* File system sizes */ + int (*blockPresent)(struct mountedFS *, uint8_t, unsigned int); + int (*readBlock)(struct mountedFS *, uint8_t, unsigned int, void *); + int (*writeBlock)(struct mountedFS *, uint8_t, unsigned int, void *); +}; + +/* + * OS/8 specific data area + */ +struct OS8data { + unsigned int blocks; /* Size of container */ + struct OS8device *device; /* Device type */ + uint8_t devices; /* # of "devices" present */ + uint8_t valid; /* Bitmap of valid devices */ + uint16_t date; /* Date for creating files */ + uint16_t buf[OS8_BLOCKSIZE]; /* Disk buffer */ +}; + +#endif diff --git a/converters/fsio/rt11.c b/converters/fsio/rt11.c new file mode 100644 index 0000000..94c1e48 --- /dev/null +++ b/converters/fsio/rt11.c @@ -0,0 +1,2586 @@ +/* + * Copyright (C) 2018 John Forecast. All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY JOHN FORECAST "AS IS" AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/* + * Support routines for handling RT-11 file systems under fsio + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "fsio.h" + +static struct DiskSize { + char *name; /* Disk name */ + size_t size; /* Disk size */ +} rt11DiskSize[] = { + { "rk05", RT11_RK05SZ * RT11_BLOCKSIZE }, + { "rl01", RT11_RL01SZ * RT11_BLOCKSIZE }, + { "rl02", RT11_RL02SZ * RT11_BLOCKSIZE }, + { "rx01", RT11_RX0xSZ * RT11_RX01SS }, + { "rx02", RT11_RX0xSZ * RT11_RX02SS }, + { NULL, 0 } +}; + +static int rt11BestFit(struct mountedFS *, uint8_t, uint16_t, + uint16_t *, uint16_t *, uint16_t *, uint16_t *); +static void rt11CloseFile(void *); + +extern int quiet; + +/*++ + * r t 1 1 P a r s e F i l e s p e c + * + * Parse a character string representing an RT-11 file specification. If + * wildcards are permitted, '*' may be used to represent zero or more + * characters in the file specification and '%' represents a single + * character. For the purpose of wildcard matches, filename and filetype + * are considered separate strings. + * + * Inputs: + * + * ptr - pointer to the file specification string + * spec - pointer to the file specification block + * wildcard - wildcard processing options: + * 0 (RT11_M_NONE) - wildcards not allowed + * 1 (RT11_M_ALLOW) - wildcards allowed + * 2 (RT11_M_NONAME) - wildcards allowed + * if filename + ext not + * present default to *.* + * + * Outputs: + * + * The file specification block will be filled with the file information. + * + * Returns: + * + * 1 if parse is successful, 0 otherwise + * + --*/ +#define P_DONE 0 +#define P_NAME 1 +#define P_TYPE 2 + +int rt11ParseFilespec( + char *ptr, + struct rt11FileSpec *spec, + int wildcard +) +{ + int state = P_NAME; + unsigned int i; + + memset(spec, 0, sizeof(struct rt11FileSpec)); + memset(&spec->fname, ' ', sizeof(spec->fname)); + memset(&spec->ftype, ' ', sizeof(spec->ftype)); + + if (wildcard == RT11_M_NONAME) + if (*ptr == '\0') { + spec->flags = RT11_WC_NAME | RT11_WC_TYPE; + spec->fname[0] = spec->ftype[0] = '*'; + return 1; + } + + while (state != P_DONE) { + i = 0; + + switch (state) { + case P_NAME: + while ((*ptr != '\0') && + (*ptr != '.') && + (i < sizeof(spec->fname))) { + char ch = *ptr++; + + spec->fname[i++] = ch; + + if (!isalnum(ch)) + switch (ch) { + case '*': + case '%': + if (wildcard) { + spec->flags |= RT11_WC_NAME; + break; + } + /* FALLTHROUGH */ + + default: + return 0; + } + } + + switch (*ptr) { + case '\0': + state = P_DONE; + break; + + case '.': + state = P_TYPE; + ptr++; + break; + + default: + return 0; + } + break; + + case P_TYPE: + while ((*ptr != '\0') && + (i < sizeof(spec->ftype))) { + char ch = *ptr++; + + spec->ftype[i++] = ch; + + if (!isalnum(ch)) + switch (ch) { + case '*': + case '%': + if (wildcard) { + spec->flags |= RT11_WC_TYPE; + break; + } + /* FALLTHROUGH */ + + default: + return 0; + } + } + + if (*ptr != '\0') + return 0; + + state = P_DONE; + break; + } + } + + if ((spec->flags & RT11_WC_NAME) == 0) { + spec->name[0] = ascR50(&spec->fname[0]); + spec->name[1] = ascR50(&spec->fname[3]); + } + if ((spec->flags & RT11_WC_TYPE) == 0) + spec->type = ascR50(&spec->ftype[0]); + + return 1; +} + +/*++ + * r t 1 1 B u i l d R e g e x + * + * Build and compile a regular expression to match a filename with wildcard + * characters. + * + * Inputs: + * + * spec - pointer to the file specification block + * preg - pointer to regex_t structure to receive the + * compiled regular expression + * + * Outputs: + * + * The compilation may dynamically allocate storage which must be + * released using regfree(). + * + * Returns: + * + * 1 if regular expression was successfully compiled, 0 otherwise + * + --*/ +static int rt11BuildRegex( + struct rt11FileSpec *spec, + regex_t *preg +) +{ + char temp[64]; + unsigned int i, j = 0; + + /* + * Convert the filename into a regular expression suitable for compilation. + */ + temp[j++] = '^'; + + for (i = 0; i < sizeof(spec->fname); i++) { + if (spec->fname[i] == ' ') + break; + + switch (spec->fname[i]) { + case '*': + temp[j++] = '.'; + temp[j++] = '*'; + break; + + case '%': + temp[j++] = '.'; + break; + + default: + temp[j++] = toupper(spec->fname[i]); + break; + } + } + + temp[j++] = '\\'; + temp[j++] = '.'; + + for (i = 0; i < sizeof(spec->ftype); i++) { + if (spec->ftype[i] == ' ') + break; + + switch (spec->ftype[i]) { + case '*': + temp[j++] = '.'; + temp[j++] = '*'; + break; + + case '%': + temp[j++] = '.'; + break; + + default: + temp[j++] = toupper(spec->ftype[i]); + break; + } + } + + temp[j++] = '$'; + temp[j++] = '\0'; + + /* + * Compile the regular expression. + */ + if (regcomp(preg, temp, 0) == 0) + return 1; + + return 0; +} + +/*++ + * r t 1 1 M a t c h R e g e x + * + * Match a pre-compiled regular expression against a filename and type. + * + * Inputs: + * + * preg - pointer to pre-compiler regular expression + * fname1 - first 3 characters of filename (RAD50) + * fname2 - next 3 characters of filename (RAD50) + * ftype - 3 characters of file type (RAD50) + * + * Outputs: + * + * ... + * + * Returns: + * + * 1 if regular expression matches, 0 otherwise + * + --*/ +int rt11MatchRegex( + regex_t *preg, + uint16_t fname1, + uint16_t fname2, + uint16_t ftype +) +{ + char temp[16]; + int i = 5; + + /* + * Build a filename + type string with no embedded spaces. + */ + r50Asc(fname1, &temp[0]); + r50Asc(fname2, &temp[3]); + + while ((i >= 0) && (temp[i] == ' ')) + i--; + + temp[++i] = '.'; + + r50Asc(ftype, &temp[++i]); + + i += 2; + while (temp[i] == ' ') + i--; + + temp[i + 1] = '\0'; + + if (regexec(preg, &temp[0], 0, NULL, 0) == 0) + return 1; + + return 0; +} + +/*++ + * M a p L o g T o P h y s + * + * Map a logical sector address to a physical sector address for an RX01/RX02 + * drive. + * + * Inputs: + * + * sectno - logical sector number + * + * Outputs: + * + * None + * + * Returns: + * + * Physical sector number + * + --*/ +unsigned int MapLogToPhys( + unsigned int sectno +) +{ + unsigned int i, track, sector; + + track = sectno / RT11_RX0xNSECT; + i = (sectno % RT11_RX0xNSECT) << 1; + if (i >= RT11_RX0xNSECT) + i++; + sector = (i + (6 * track)) % RT11_RX0xNSECT; + return sector + (track * RT11_RX0xNSECT); +} + +/*++ + * r t 1 1 R e a d B l o c k + * + * Read a block from an RT-11 file system. + * + * Inputs: + * + * mount - pointer to a mounted file system descriptor + * unit - partition number + * block - logical block # in the range 0 - N + * buf - buffer to receive data, if NULL use the mount + * point specific buffer + * + * Outputs: + * + * The block will be read into the specified buffer. + * + * Returns: + * + * 1 if successful, 0 otherwise + * + --*/ +int rt11ReadBlock( + struct mountedFS *mount, + uint8_t unit, + unsigned int block, + void *buf +) +{ + struct RT11data *data = &mount->rt11data; + char *buffer = buf == NULL ? data->buf : buf; + int status = 0; + + if (RT11_PARTITIONVALID(data, unit)) { + if (block > data->maxblk[unit]) { + ERROR("Attempt to read block (%u) outside file system \"%s%o:\"\n", + block, mount->name, unit); + return 0; + } + +#ifdef DEBUG + if ((mount->flags & FS_DEBUG) != 0) + fprintf(DEBUGout, ">> %s%o: (rt11) Reading logical block %o\n", + mount->name, unit, block); +#endif + + if (data->sectorsz != 0) { + /* + * The sectors making up the disk are sector-interleaved. The algorithm + * below is for RX01/RX02 interleave. Note that all such devices are + * small enough that there can only be a single file system present. + */ + unsigned int count = RT11_BLOCKSIZE / data->sectorsz; + unsigned int sectno = block * count; + + do { + unsigned int sector = MapLogToPhys(sectno); + + status = FSioReadSector(mount, sector, data->sectorsz, buffer); + + count--; + buffer += data->sectorsz; + sectno++; + } while ((count != 0) && (status != 0)); + } else status = FSioReadBlock(mount, (unit << 16) | block, buffer); + + if (status == 0) + ERROR("I/O error on \"%s%o:\"\n", mount->name, unit); + } else ERROR("Invalid device \"%s%o:\"\n", mount->name, unit); + + return status; +} + +/*++ + * r t 1 1 W r i t e B l o c k + * + * Write a block to an RT-11 file system. + * + * Inputs: + * + * mount - pointer to a mounted file system descriptor + * unit - partition number + * block - logical block # in the range 0 - N + * buf - buffer to receive data, if NULL use the mount + * point specific buffer + * + * Outputs: + * + * None + * + * Returns: + * + * 1 if successful, 0 otherwise + * + --*/ +int rt11WriteBlock( + struct mountedFS *mount, + uint8_t unit, + unsigned int block, + void *buf +) +{ + struct RT11data *data = &mount->rt11data; + char *buffer = buf == NULL ? data->buf : buf; + int status = 0; + + if (RT11_PARTITIONVALID(data, unit)) { + if (block > data->maxblk[unit]) { + ERROR("Attempt to write block (%u) outside file system \"%s%o:\"\n", + block, mount->name, unit); + return 0; + } + +#ifdef DEBUG + if ((mount->flags & FS_DEBUG) != 0) + fprintf(DEBUGout, ">> %s%o: (rt11) Writing logical block %o\n", + mount->name, unit, block); +#endif + + if (data->sectorsz != 0) { + /* + * The sectors making up the disk are sector-interleaved. The algorithm + * below is for RX01/RX02 interleave. Note that all such devices are + * small enough that there can only be a single file system present. + */ + unsigned int count = RT11_BLOCKSIZE / data->sectorsz; + unsigned int sectno = block * count; + + do { + unsigned int sector = MapLogToPhys(sectno); + + status = FSioWriteSector(mount, sector, data->sectorsz, buffer); + + count--; + buffer += data->sectorsz; + sectno++; + } while ((count != 0) && (status != 0)); + } else status = FSioWriteBlock(mount, (unit << 16) | block, buffer); + + if (status == 0) + ERROR("I/O error on \"%s%o:\"\n", mount->name, unit); + } else ERROR("Invalid device \"%s%o:\"\n", mount->name, unit); + + return status; +} + +/*++ + * r t 1 1 R e a d D i r S e g m e n t + * + * Read a directory segment (2 file system blocks) into the mount specific + * buffer. + * + * Inputs: + * + * mount - pointer to a mounted file system descriptor + * unit - partition number + * segment - logical directory segment # (1 - 31) + * + * Outputs: + * + * The directory segment will be read into the mount specific buffer. + * + * Returns: + * + * 1 if successful, 0 otherwise + * + --*/ +int rt11ReadDirSegment( + struct mountedFS *mount, + uint8_t unit, + uint16_t segment +) +{ + struct RT11data *data = &mount->rt11data; + unsigned int block = data->first[unit] + ((segment - 1) * 2); + + if (rt11ReadBlock(mount, unit, block, &data->buf[0]) != 0) + if (rt11ReadBlock(mount, unit, block + 1, &data->buf[256]) != 0) + return 1; + + return 0; +} + +/*++ + * r t 1 1 W r i t e D i r S e g m e n t + * + * Write a directory segment (2 disk blocks) from the mount specific buffer. + * + * Inputs: + * + * mount - pointer to a mounted file system descriptor + * unit - partition number + * segment - logical directory segment # (1 - 31) + * + * Outputs: + * + * None + * + * Returns: + * + * 1 if successful, 0 otherwise + * + --*/ +int rt11WriteDirSegment( + struct mountedFS *mount, + uint8_t unit, + uint16_t segment +) +{ + struct RT11data *data = &mount->rt11data; + unsigned int block = data->first[unit] + ((segment - 1) * 2); + + if (rt11WriteBlock(mount, unit, block, &data->buf[0]) != 0) + if (rt11WriteBlock(mount, unit, block + 1, &data->buf[256]) != 0) + return 1; + + return 0; +} + +/*++ + * r t 1 1 M a k e D i r e c t o r y E n t r y + * + * Make space available for a new directory entry. The directory segment + * has been read into the mount point specific buffer. + * + * Inputs: + * + * mount - pointer to the mounted file system descriptor + * entrysz - size of directory entries + * offset - offset to empty directory entry + * + * Outputs: + * + * If space is available, the directory segment will be modified + * + * Returns: + * + * 1 if a new directory entry has been created, 0 otherwise + * + --*/ +int rt11MakeDirectoryEntry( + struct mountedFS *mount, + uint16_t entrysz, + uint16_t offset +) +{ + struct RT11data *data = &mount->rt11data; + uint16_t off = offset; + + /* + * Scan forward looking for the end-of-segment marker. + */ + while (!RT11EOS(le16toh(data->buf[off + RT11_DI_STATUS])) && + ((RT11_DS_SIZE - off) >= entrysz)) + off += entrysz; + + if ((RT11_DS_SIZE - off) > entrysz) { + /* + * There is enough space available for a new directory entry. Slide + * the existing entries up to make a new one available at the + * requested position. + */ + data->buf[off + entrysz + RT11_DI_STATUS] = htole16(RT11_E_EOS); + + while (off != offset) { + uint16_t i, prev = off - entrysz; + + for (i = RT11_DI_STATUS; i < entrysz; i++) + data->buf[off + i] = data->buf[prev + i]; + + off -= entrysz; + } + return 1; + } + return 0; +} + +/*++ + * r t 1 1 B e s t F i t + * + * Search the entire directory structure for the best fit for the requested + * file size. If the requested file size is 0, we return the largest + * empty directory entry. + * + * Inputs: + * + * mount - pointer to the mounted file system descriptor + * unit - partition number + * rqsize - requested size in 512-byte blocks + * start - return starting block # here + * segment - return directory segment here + * offset - return directory segment offset here + * next - return next available directory segment here + * NULL if not required + * + * Outputs: + * + * The mount specific buffer will be modified + * + * Returns: + * + * 1 if space is available, 0 if not + * + --*/ +static int rt11BestFit( + struct mountedFS *mount, + uint8_t unit, + uint16_t rqsize, + uint16_t *start, + uint16_t *segment, + uint16_t *offset, + uint16_t *next +) +{ + struct RT11data *data = &mount->rt11data; + uint16_t bestsize, beststart, bestsegment, bestoffset, dsseg = 1; + + bestsize = rqsize == 0 ? 0 : 0177777; + bestsegment = 0; + + do { + uint16_t entrysz, startblk, off = RT11_DH_SIZE; + + if (rt11ReadDirSegment(mount, unit, dsseg) == 0) + return 0; + + /* + * Return the next available directory segment or 0 if no more are + * available. + */ + if ((next != NULL) && (dsseg == 1)) { + if (data->buf[RT11_DH_COUNT] != data->buf[RT11_DH_HIGHEST]) + *next = le16toh(data->buf[RT11_DH_HIGHEST]) + 1; + else *next = 0; + } + + entrysz = RT11_DI_SIZE + (le16toh(data->buf[RT11_DH_EXTRA]) >> 1); + startblk = le16toh(data->buf[RT11_DH_START]); + + /* + * Loop until we see and end-of-segment marker or there is no room for + * another directory entry. + */ + while (!RT11EOS(le16toh(data->buf[off + RT11_DI_STATUS])) && + ((RT11_DS_SIZE - off) >= entrysz)) { + uint16_t status = le16toh(data->buf[off + RT11_DI_STATUS]); + uint16_t length = le16toh(data->buf[off + RT11_DI_LENGTH]); + + if ((status & RT11_E_MPTY) != 0) { + /* + * Check for largest or best fit + */ + if (((rqsize == 0) && (length > bestsize)) || + ((rqsize != 0) && + ((length >= rqsize) && (length < bestsize)))) { + bestsize = length; + beststart = startblk; + bestsegment = dsseg; + bestoffset = off; + } + } + startblk += length; + off += entrysz; + } + dsseg = le16toh(data->buf[RT11_DH_NEXT]); + } while (dsseg != 0); + + if (bestsegment != 0) { + *start = beststart; + *segment = bestsegment; + *offset = bestoffset; + return 1; + } + return 0; +} + +/*++ + * r t 1 1 M e r g e E m p t y R e g i o n s + * + * Following a delete operation, check if we can merge empty regions + * together and compact the directory segment. In the worst case there + * can be 3 directory entries involved; , , . If + * is deleted we need to collapse all three entries to one. + * The directory segment is currently in the mount point specific buffer. + * + * Inputs: + * + * mount - pointer to a mounted file system descriptor + * offset - offset of the directory entry just deleted + * + * Outputs: + * + * The mount point specific buffer may be modified + * + * Returns: + * + * None + * + --*/ +static void rt11MergeEmptyRegions( + struct mountedFS *mount, + uint16_t offset +) +{ + struct RT11data *data = &mount->rt11data; + uint16_t entrysz = RT11_DI_SIZE + (le16toh(data->buf[RT11_DH_EXTRA]) >> 1); + uint16_t src = offset, dest = offset; + uint16_t free = le16toh(data->buf[offset + RT11_DI_LENGTH]); + + /* + * Check for previous empty entry. + */ + if (offset != RT11_DH_SIZE) { + uint16_t off = offset - entrysz; + + if ((le16toh(data->buf[off + RT11_DI_STATUS]) & RT11_E_MPTY) != 0) { + free += le16toh(data->buf[off + RT11_DI_LENGTH]); + dest = off; + } + } + + /* + * Check for following empty entry + */ + if ((RT11_DS_SIZE - (offset + entrysz)) >= entrysz) { + uint16_t off = offset + entrysz; + + if ((le16toh(data->buf[off + RT11_DI_STATUS]) & RT11_E_MPTY) != 0) { + free += le16toh(data->buf[off + RT11_DI_LENGTH]); + src = off; + } + } + + if (src != dest) { + /* + * We need to adjust the size of the unused region and slide the rest + * of the directory down 1 or 2 entries. Since we are collapsing at + * least 2 directory entries together, we will zero out the file name + * and type. + */ + data->buf[dest + RT11_DI_LENGTH] = htole16(free); + data->buf[dest + RT11_DI_FNAME1] = 0; + data->buf[dest + RT11_DI_FNAME2] = 0; + data->buf[dest + RT11_DI_FTYPE] = 0; + + dest += entrysz; + src += entrysz; + + /* + * Now slide the directory down + */ + for (;src < RT11_DS_SIZE;) { + uint16_t i; + + data->buf[dest + RT11_DI_STATUS] = data->buf[src + RT11_DI_STATUS]; + + if (RT11EOS(le16toh(data->buf[dest + RT11_DI_STATUS])) || + ((RT11_DS_SIZE - src) < entrysz)) + break; + + for (i = RT11_DI_FNAME1;i < entrysz; i++) + data->buf[dest + i] = data->buf[src + i]; + + dest += entrysz; + src += entrysz; + } + + /* + * Make sure the last entry contains the end-of-segment marker. + */ + data->buf[dest + RT11_DI_STATUS] = htole16(RT11_E_EOS); + } +} + +/*++ + * r t 1 1 S p l i t D i r S e g m e n t + * + * Split a directory segment into 2 pieces. The directory segment to be + * split is currently in the mount specific buffer. + * + * Inputs: + * + * mount - pointer to the mounted file system descriptor + * unit - partition number + * entrysz - size of directory entries + * segment - the directory segment # to be split + * newsegment - the new directory segment to be added + * + * Outputs: + * + * The mount specific buffer will be modified + * + * Returns: + * + * 1 if split was successful, 0 otherwise + * + --*/ +static int rt11SplitDirSegment( + struct mountedFS *mount, + uint8_t unit, + uint16_t entrysz, + uint16_t segment, + uint16_t newsegment +) +{ + struct RT11data *data = &mount->rt11data; + uint16_t entries = RT11_DS_DISPACE / entrysz; + uint16_t nextsegment = data->buf[RT11_DH_NEXT]; + uint16_t entry, count, status, startblk = le16toh(data->buf[RT11_DH_START]); + uint16_t dest; + + /* + * Find first permanent or tentative file entry in the middle of the + * directory segment. + */ + for (count = 0, entry = RT11_DH_SIZE;;count++, entry += entrysz) { + status = le16toh(data->buf[entry + RT11_DI_STATUS]); + if ((count > (entries / 2)) && + ((status & (RT11_E_PERM | RT11_E_TENT)) != 0)) + break; + startblk += le16toh(data->buf[entry + RT11_DI_LENGTH]); + } + + /* + * Terminate the current directory segment at this point. + */ + data->buf[entry + RT11_DI_STATUS] = htole16(RT11_E_EOS); + + /* + * Link to the new segment and update the directory segment on disk. + */ + data->buf[RT11_DH_NEXT] = htole16(newsegment); + if (rt11WriteDirSegment(mount, unit, segment) == 0) + return 0; + + /* + * Update the directory header to represent the new directory segment. + */ + data->buf[RT11_DH_NEXT] = nextsegment; + data->buf[RT11_DH_START] = htole16(startblk); + + /* + * Restore the directory entry status word, slide down the directory entries + * to the beginning of the directory segment and update the new directory + * segment on disk. + */ + data->buf[entry + RT11_DI_STATUS] = htole16(status); + + for (dest = RT11_DH_SIZE;entry < RT11_DS_SIZE;) { + uint16_t i; + + data->buf[dest + RT11_DI_STATUS] = data->buf[entry + RT11_DI_STATUS]; + + if (RT11EOS(le16toh(data->buf[dest + RT11_DI_STATUS])) || + ((RT11_DS_SIZE - entry) < entrysz)) + break; + + for (i = RT11_DI_FNAME1;i < entrysz; i++) + data->buf[dest + i] = data->buf[entry + i]; + + dest += entrysz; + entry += entrysz; + } + + /* + * Make sure the last entry contains the end-of-segment marker. + */ + data->buf[dest + RT11_DI_STATUS] = htole16(RT11_E_EOS); + + if (rt11WriteDirSegment(mount, unit, newsegment) == 0) + return 0; + + /* + * Update the highest directory segment in use in the first directory + * segment. + */ + if (rt11ReadDirSegment(mount, unit, 1) == 0) + return 0; + + data->buf[RT11_DH_HIGHEST] = htole16(newsegment); + + if (rt11WriteDirSegment(mount, unit, 1) == 0) + return 0; + + return 1; +} + +/*++ + * r t 1 1 D a t e + * + * Convert an RT-11 date value into an ASCII string. + * + * Inputs: + * + * value - RT-11 date value + * buf - buffer to receive the string (requires 12 bytes) + * + * Outputs: + * + * None + * + * Returns: + * + * Pointer to the date string + * + --*/ +char *rt11Date( + uint16_t value, + char *buf +) +{ + sprintf(buf, "%02d-%s-%4d", + (value & RT11_DW_DAY) >> 5, + month[((value & RT11_DW_MONTH) >> 10) - 1], + 1972 + (value & RT11_DW_YEAR) + ((value & RT11_DW_AGE) >> 14) * 32); + + return buf; +} + +/*++ + * r t 1 1 D i s p l a y D i r + * + * Print a directory entry on stdout. + * + * Inputs: + * + * dir - pointer to the directory entry for the file + * full - if non-zero, print a full directory entry + * + * Outputs: + * + * None + * + * Returns: + * + * None + * + --*/ +void rt11DisplayDir( + uint16_t *dir, + int full +) +{ + char temp[64], creation[16]; + + r50Asc(le16toh(dir[RT11_DI_FNAME1]), &temp[0]); + r50Asc(le16toh(dir[RT11_DI_FNAME2]), &temp[3]); + temp[6] = '.'; + r50Asc(le16toh(dir[RT11_DI_FTYPE]), &temp[7]); + + if (full) + sprintf(&temp[10], " %5u %s", + le16toh(dir[RT11_DI_LENGTH]), + rt11Date(le16toh(dir[RT11_DI_CREATE]), creation)); + else temp[10] ='\0'; + puts(temp); +} + +/*++ + * r t 1 1 C r e a t e F i l e + * + * Create a new file within the RT-11 file system. The new file will be + * marked as tentative since the resources allocated to the file may be + * in flux. + * + * Inputs: + * + * mount - pointer to a mounted file system descriptor + * unit - partition number + * spec - pointer to the file specification block + * file - pointer to open file descriptor to receive results + * size - file size in 512-byte blocks + * 0 means use the largest free space region + * + * Outputs: + * + * The mount point specific buffer area will be modified. + * + * Returns: + * + * 1 if file successfully created, 0 otherwise + * + --*/ +int rt11CreateFile( + struct mountedFS *mount, + uint8_t unit, + struct rt11FileSpec *spec, + struct rt11OpenFile *file, + uint16_t size +) +{ + struct RT11data *data = &mount->rt11data; + struct tm tm; + time_t now = time(NULL); + uint16_t year, today; + uint16_t entrysz, empty, dstart, dseg, doffset, next; + + /* + * Check if suitable free space is available + */ + if (rt11BestFit(mount, unit, size, &dstart, &dseg, &doffset, &next) == 0) { + ERROR("Free space not available on \"%s%o:\"\n", mount->name, unit); + return 0; + } + + /* + * Create an RT-11 timestamp for today + */ + localtime_r(&now, &tm); + + year = tm.tm_year - 72; + today = ((year / 32) << 14) | (year % 32); + today |= (tm.tm_mday << 5) | ((tm.tm_mon + 1) << 10); + + if (rt11ReadDirSegment(mount, unit, dseg) == 0) + return 0; + + entrysz = RT11_DI_SIZE + (le16toh(data->buf[RT11_DH_EXTRA]) >> 1); + + /* + * Try to open up a new directory entry in the current directory segment + */ + if (rt11MakeDirectoryEntry(mount, entrysz, doffset) == 0) { + /* + * No space for a new directory entry. Split the current directory + * segment and try again. + */ + if (next == 0) { + ERROR("No directory segment available for split on \"%s%o:\"\n", + mount->name, unit); + return 0; + } + + if (rt11SplitDirSegment(mount, unit, entrysz, dseg, next) == 0) { + ERROR("Error splitting directory segment on \"%s%o:\"\n", + mount->name, unit); + return 0; + } + + if (rt11BestFit(mount, unit, size, &dstart, &dseg, &doffset, NULL) == 0) { + ERROR("Panic: second best fit failed on\"%s%o:\"\n", mount->name, unit); + exit(1); + } + + if (rt11ReadDirSegment(mount, unit, dseg) == 0) + return 0; + + if (rt11MakeDirectoryEntry(mount, entrysz, doffset) == 0) { + ERROR("Panic: second directory make operation failed on \"%s%o:\"\n", + mount->name, unit); + exit(2); + } + } + + /* + * Fill in the newly created directory entry + */ + empty = doffset + RT11_DI_SIZE + (le16toh(data->buf[RT11_DH_EXTRA]) >> 1); + + data->buf[doffset + RT11_DI_STATUS] = RT11_E_TENT; + data->buf[doffset + RT11_DI_FNAME1] = htole16(spec->name[0]); + data->buf[doffset + RT11_DI_FNAME2] = htole16(spec->name[1]); + data->buf[doffset + RT11_DI_FTYPE] = htole16(spec->type); + data->buf[doffset + RT11_DI_JOB_CHN] = 0; + data->buf[doffset + RT11_DI_CREATE] = htole16(today); + + if (size == 0) { + data->buf[doffset + RT11_DI_LENGTH] = data->buf[empty + RT11_DI_LENGTH]; + data->buf[empty + RT11_DI_LENGTH] = 0; + } else { + data->buf[doffset + RT11_DI_LENGTH] = htole16(size); + data->buf[empty + RT11_DI_LENGTH] = + htole16(le16toh(data->buf[empty + RT11_DI_LENGTH]) - size); + } + + if (rt11WriteDirSegment(mount, unit, dseg) == 0) + return 0; + + /* + * Fill in the open file descriptor. + */ + file->status = data->buf[doffset + RT11_DI_STATUS]; + file->name[0] = data->buf[doffset + RT11_DI_FNAME1]; + file->name[1] = data->buf[doffset + RT11_DI_FNAME2]; + file->type = data->buf[doffset + RT11_DI_FTYPE]; + file->length = data->buf[doffset + RT11_DI_LENGTH]; + file->creation = data->buf[doffset + RT11_DI_CREATE]; + + file->segment = dseg; + file->offset = doffset; + + file->mount = mount; + file->unit = unit; + + file->start = dstart; + + return 1; +} + +/*++ + * r t 1 1 L o o k u p F i l e + * + * Lookup a specific file within the RT-11 file system. This routine fills + * in an open file descriptor with information about the file and the + * directory entry it resides in. + * + * Inputs: + * + * mount - pointer to a mounted file system descriptor + * unit - partition number + * spec - pointer to the file specification block + * file - pointer to open file descriptor to receive results + * + * Outputs: + * + * The mount point specific buffer will be modified + * + * Returns: + * + * 1 if file found, 0 otherwise + * + --*/ +int rt11LookupFile( + struct mountedFS *mount, + uint8_t unit, + struct rt11FileSpec *spec, + struct rt11OpenFile *file +) +{ + struct RT11data *data = &mount->rt11data; + + if (RT11_PARTITIONVALID(data, unit)) { + uint16_t entrysz, position, dsseg = 1; + + do { + uint16_t off = RT11_DH_SIZE; + + if (rt11ReadDirSegment(mount, unit, dsseg) == 0) + return 0; + + entrysz = RT11_DI_SIZE + (le16toh(data->buf[RT11_DH_EXTRA]) >> 1); + position = le16toh(data->buf[RT11_DH_START]); + + /* + * Loop until we see and end-of-segment marker or there is no room for + * another directory entry. + */ + while (!RT11EOS(le16toh(data->buf[off + RT11_DI_STATUS])) && + ((RT11_DS_SIZE - off) >= entrysz)) { + uint16_t status = le16toh(data->buf[off + RT11_DI_STATUS]); + + if ((status & RT11_E_MPTY) == 0) { + if ((le16toh(data->buf[off + RT11_DI_FNAME1]) == spec->name[0]) && + (le16toh(data->buf[off + RT11_DI_FNAME2]) == spec->name[1]) && + (le16toh(data->buf[off + RT11_DI_FTYPE]) == spec->type)) { + + /* + * Save directory entry and it's location in the open file + * descriptor. + */ + file->status = data->buf[off + RT11_DI_STATUS]; + file->name[0] = data->buf[off + RT11_DI_FNAME1]; + file->name[1] = data->buf[off + RT11_DI_FNAME2]; + file->type = data->buf[off + RT11_DI_FTYPE]; + file->length = data->buf[off + RT11_DI_LENGTH]; + file->creation = data->buf[off + RT11_DI_CREATE]; + + file->segment = dsseg; + file->offset = off; + + file->mount = mount; + file->unit = unit; + + file->start = position; + + return 1; + } + } + position += le16toh(data->buf[off + RT11_DI_LENGTH]); + + off += entrysz; + } + dsseg = le16toh(data->buf[RT11_DH_NEXT]); + } while (dsseg != 0); + } + return 0; +} + +/*++ + * r t 1 1 U p d a t e F i l e + * + * Update an RT-11 file by writing back the directory entry associated with + * the file. + * + * Inputs: + * + * mount - pointer to a mounted file system descriptor + * unit - partition number + * file - pointer to open file descriptor + * + * Outputs: + * + * The mount point specific buffer will be modified + * + * Returns: + * + * None + * + --*/ +void rt11UpdateFile( + struct mountedFS *mount, + uint8_t unit, + struct rt11OpenFile *file +) +{ + struct RT11data *data = &mount->rt11data; + uint16_t offset, length, left, entrysz; + + if (rt11ReadDirSegment(mount, unit, file->segment) == 0) + return; + + entrysz = RT11_DI_SIZE + (le16toh(data->buf[RT11_DH_EXTRA]) >> 1); + offset = file->offset; + + length = file->current - file->start + 1; + left = file->length - length; + + /* + * Fix up the directory entry and the following empty entry + */ + data->buf[offset + RT11_DI_STATUS] = htole16(RT11_E_PERM); + data->buf[offset + RT11_DI_LENGTH] = htole16(length); + + data->buf[offset + entrysz + RT11_DI_LENGTH] = + htole16(le16toh(data->buf[offset + entrysz + RT11_DI_LENGTH]) + left); + + if (le16toh(data->buf[offset + entrysz + RT11_DI_LENGTH]) == 0) { + /* + * Remove the now empty directory entry + */ + uint16_t dest = offset + entrysz; + uint16_t src = dest + entrysz; + + for (;src < RT11_DS_SIZE;) { + uint16_t i; + + data->buf[dest + RT11_DI_STATUS] = data->buf[src + RT11_DI_STATUS]; + + if (RT11EOS(le16toh(data->buf[dest + RT11_DI_STATUS])) || + ((RT11_DS_SIZE - src) < entrysz)) + break; + + for (i = RT11_DI_FNAME1;i < entrysz; i++) + data->buf[dest + i] = data->buf[src + i]; + + dest += entrysz; + src += entrysz; + } + + /* + * Make sure the last entry contains the end-of-segment marker. + */ + data->buf[dest + RT11_DI_STATUS] = htole16(RT11_E_EOS); + } + + rt11WriteDirSegment(mount, file->unit, file->segment); +} + +/*++ + * i n f o + * + * Display information about the internal structure of a single file system. + * + * Inputs: + * + * mount - pointer to the mounted file system descriptor + * unit - partition number + * + * Outputs: + * + * None + * + * Returns: + * + * None + * + --*/ +static void info( + struct mountedFS *mount, + uint8_t unit +) +{ + struct RT11data *data = &mount->rt11data; + uint16_t entrysz, startblk, dsseg = 1; + char temp[32]; + + printf("%s%o:\n\n", mount->name, unit); + + do { + uint16_t off = RT11_DH_SIZE; + + if (rt11ReadDirSegment(mount, unit, dsseg) == 0) + return; + + printf("\nDirectory Segment %2d:\n\n", dsseg); + printf(" File Type Date Status Class Length Disk Region\n\n"); + + entrysz = RT11_DI_SIZE + (le16toh(data->buf[RT11_DH_EXTRA]) >> 1); + startblk = le16toh(data->buf[RT11_DH_START]); + + /* + * Loop until we see and end-of-segment marker or there is no room for + * another directory entry. + */ + while (!RT11EOS(le16toh(data->buf[off + RT11_DI_STATUS])) && + ((RT11_DS_SIZE - off) >= entrysz)) { + uint16_t status = le16toh(data->buf[off + RT11_DI_STATUS]); + + /* + * Tentative and empty files are reported as "UNUSED" + */ + if ((status & (RT11_E_TENT | RT11_E_MPTY)) == 0) { + r50Asc(le16toh(data->buf[off + RT11_DI_FNAME1]), &temp[0]); + r50Asc(le16toh(data->buf[off + RT11_DI_FNAME2]), &temp[3]); + temp[6] = '.'; + r50Asc(le16toh(data->buf[off + RT11_DI_FTYPE]), &temp[7]); + temp[10] = ' '; + temp[11] = ' '; + rt11Date(le16toh(data->buf[off + RT11_DI_CREATE]), &temp[12]); + + printf("%s %07o %s %5d %5d-%5d\n", + temp, status, + (status & RT11_E_PRE) != 0 ? "PRE" : " ", + le16toh(data->buf[off + RT11_DI_LENGTH]), + startblk, + startblk + le16toh(data->buf[off + RT11_DI_LENGTH]) - 1); + } else { + printf("< UNUSED > %5d %5d-%5d\n", + le16toh(data->buf[off + RT11_DI_LENGTH]), + startblk, + le16toh(data->buf[off + RT11_DI_LENGTH]) == 0 ? startblk : + startblk + le16toh(data->buf[off + RT11_DI_LENGTH]) - 1); + } + startblk += le16toh(data->buf[off + RT11_DI_LENGTH]); + + off += entrysz; + } + + dsseg = le16toh(data->buf[RT11_DH_NEXT]); + } while (dsseg != 0); +} + +/*++ + * p a r t i t i o n T y p e + * + * Determine the type of the partition whose home block is in the mount + * point specific buffer. + * + * Inputs: + * + * mount - pointer to a mounted file system descriptor + * unit - partition number + * + * Outputs: + * + * ... + * + * Returns: + * + * partition type: + * + * RT11_NOPART - Not a valid RT11 partition + * RT11_SINGLE - 1 partition supported per disk + * RT11_MULTI - Multiple partitions supported per disk + * + --*/ +static int partitionType( + struct mountedFS *mount, + uint8_t unit +) +{ + struct RT11data *data = &mount->rt11data; + + if (strncmp((char *)&data->buf[RT11_HB_SYSID], RT11_SYSID, strlen(RT11_SYSID)) == 0) { + uint16_t type = le16toh(data->buf[RT11_HB_SYSVER]); + + if (unit == 0) { + if ((type == RT11_SYSVER_V3A) || (type == RT11_SYSVER_V04)) + return RT11_SINGLE; + } + + if (type == RT11_SYSVER_V05) + return RT11_MULTI; + } + + if (strncmp((char *)&data->buf[RT11_HB_SYSID], RT11_VMSSYSID, strlen(RT11_VMSSYSID)) == 0) + return RT11_SINGLE; + + return RT11_NOPART; +} + +/*++ + * v a l i d a t e + * + * Verify that a partition contains a valid RT-11 filesystem. First verify + * that the ID fields are correct, then walk the directory structure to + * make sure that we are looking at a valid RT-11 partition. + * + * Inputs: + * + * mount - pointer to a mounted file system descriptor + * unit - partition number + * maxblk - return maximum block # of the partition here + * first - return first directory segment block # here + * + * Outputs: + * + * The mount point specific buffer will be overwritten. + * + * Returns: + * + * partition type: + * + * RT11_NOPART - Not a valid RT11 partition + * RT11_SINGLE - 1 partition supported per disk + * RT11_MULTI - Multiple partitions supported per disk + * + --*/ +static int validate( + struct mountedFS *mount, + uint8_t unit, + uint16_t *maxblk, + uint16_t *first +) +{ + struct RT11data *data = &mount->rt11data; + int type = RT11_SINGLE; + uint16_t entrysz, position, dsseg = 1; + uint16_t seg_count, seg_highest, highest = 0; + uint8_t seen[RT11_DS_MAX + 1]; + + if (rt11ReadBlock(mount, unit, RT11_HOME, NULL) == 0) + return RT11_NOPART; + + if (!SWISSET('f')) { + if ((type = partitionType(mount, unit)) == RT11_NOPART) + return RT11_NOPART; + + *first = le16toh(data->buf[RT11_HB_FIRST]); + } else *first = RT11_DSSTART; + + memset(seen, 0, sizeof(seen)); + + /* + * Now walk the directory to validate it's integrity. + */ + do { + uint16_t off = RT11_DH_SIZE; + + if (rt11ReadDirSegment(mount, unit, dsseg) == 0) + return RT11_NOPART; + + /* + * Make sure we only look at each directory segment once - we want to + * avoid looping up with invalid input. + */ + if (seen[dsseg]++ != 0) + return RT11_NOPART; + + entrysz = RT11_DI_SIZE + (le16toh(data->buf[RT11_DH_EXTRA]) >> 1); + position = le16toh(data->buf[RT11_DH_START]); + + if (dsseg == 1) { + seg_count = le16toh(data->buf[RT11_DH_COUNT]); + seg_highest = le16toh(data->buf[RT11_DH_HIGHEST]); + if ((seg_highest > RT11_DS_MAX) || (seg_highest > seg_count)) + return RT11_NOPART; + } + + if (seg_count != le16toh(data->buf[RT11_DH_COUNT])) + return RT11_NOPART; + + /* + * Loop until we see and end-of-segment marker or there is no room for + * another directory entry. + */ + while (!RT11EOS(le16toh(data->buf[off + RT11_DI_STATUS])) && + ((RT11_DS_SIZE - off) >= entrysz)) { + uint16_t status = le16toh(data->buf[off + RT11_DI_STATUS]); + + position += le16toh(data->buf[off + RT11_DI_LENGTH]); + + if ((status & RT11_E_MPTY) != 0) + break; + + /* + * Within each directory segment the base address should never + * decrease. + */ + if (((position + le16toh(data->buf[off + RT11_DI_LENGTH])) & 0xFFFF) < position) + return RT11_NOPART; + + off += entrysz; + } + if (position > highest) + highest = position; + + dsseg = le16toh(data->buf[RT11_DH_NEXT]); + + if (dsseg > seg_highest) + return RT11_NOPART; + } while (dsseg != 0); + + *maxblk = highest - 1; + return type; +} + +/*++ + * r t 1 1 R e a d B y t e s + * + * Read a sequence of bytes from an open file. + * + * Inputs: + * + * file - pointer to an open file descriptor + * buf - pointer to a buffer to receive the data + * len - # of bytes of data to read + * + * Outputs: + * + * The buffer will be overwritten by up to "len" bytes + * + * Returns: + * + * # of bytes read from the file (may be less than "len"), 0 if EOF + * + --*/ +int rt11ReadBytes( + struct rt11OpenFile *file, + char *buf, + int len +) +{ + struct mountedFS *mount = file->mount; + int count = 0; + + if (file->current == 0) { + file->current = le16toh(file->start); + + if (rt11ReadBlock(mount, file->unit, file->current, file->buffer) == 0) + return 0; + + file->count = 0; + file->last = le16toh(file->start) + le16toh(file->length) - 1; + } + + while (len) { + if (file->count == RT11_BLOCKSIZE) { + if (file->current == file->last) + break; + + file->current++; + + if (rt11ReadBlock(mount, file->unit, file->current, file->buffer) == 0) + return 0; + + file->count = 0; + } + *buf++ = file->buffer[file->count++]; + len--; + count++; + } + return count; +} + +/*++ + * r t 1 1 W r i t e B y t e s + * + * Write a sequence of bytes to an open file. + * + * Inputs: + * + * file - pointer to an open file descriptor + * buf - pointer to a buffer to receive the data + * len - # of bytes of data to read + * + * Outputs: + * + * None + * + * Returns: + * + * # of bytes written to the file (may be less than "len"), 0 if error + * + --*/ +int rt11WriteBytes( + struct rt11OpenFile *file, + char *buf, + int len +) +{ + int count = 0; + + if (file->current == 0) { + memset(file->buffer, 0, RT11_BLOCKSIZE); + + file->current = le16toh(file->start); + file->count = 0; + file->last = le16toh(file->start) + le16toh(file->length) - 1; + } + + while (len) { + if (file->count == RT11_BLOCKSIZE) { + if (file->current == file->last) + break; + + if (rt11WriteBlock(file->mount, file->unit, file->current, file->buffer) == 0) + return 0; + + memset(file->buffer, 0, RT11_BLOCKSIZE); + + file->current++; + file->count = 0; + } + file->buffer[file->count++] = *buf++; + len--; + count++; + } + return count; +} + +/*++ + * r t 1 1 U n g e t B y t e + * + * Move back the internal buffer pointer by 1 byte so that the previous byte + * will be read again. This is only used in ASCII mode when we read a ^Z + * character indicating EOF. + * + * Inputs: + * + * file - pointer to open file descriptor + * + * Outputs: + * + * None + * + * Returns: + * + * None + * + --*/ +static void rt11UngetByte( + struct rt11OpenFile *file +) +{ + if (file->count != 0) + file->count--;} + +/*++ + * r t 1 1 M o u n t + * + * Verify that the open container file contains 1 or more RT-11 file systems. + * + * Inputs: + * + * mount - pointer to a mounted file system descriptor + * (not in the mounted file system list) + * + * Outputs: + * + * None + * + * Returns: + * + * 1 if 1 or more valid RT-11 file systems, 0 otherwise + * + --*/ +static int rt11Mount( + struct mountedFS *mount +) +{ + struct RT11data *data = &mount->rt11data; + struct stat stat; + + data->sectorsz = 0; + + /* + * Check for device type override. + */ + if (SWISSET('t')) { + if (strcmp("rx01", SWGETVAL('t')) == 0) { + mount->skip = RT11_RX0xNSECT * RT11_RX01SS; + data->sectorsz = RT11_RX01SS; + } + if (strcmp("rx02", SWGETVAL('t')) == 0) { + mount->skip = RT11_RX0xNSECT * RT11_RX02SS; + data->sectorsz = RT11_RX02SS; + } + + if (data->sectorsz == 0) + fprintf(stderr, + "mount: Ignoring unknown disk type \"%s\"\n", SWGETVAL('t')); + } + + memset(&data->valid, 0, sizeof(data->valid)); + + if (fstat(fileno(mount->container), &stat) == 0) { + uint16_t i, count, lastsz, validcount = 0;; + + data->blocks = stat.st_size / RT11_BLOCKSIZE; + + count = data->blocks / RT11_MAXPARTSZ; + lastsz = data->blocks % RT11_MAXPARTSZ; + + if (lastsz >= RT11_MINPARTSZ) + count++; + + /* + * Check each file system for validity. + */ + for (i = 0; i < count; i++) { + int type; + + /* + * Assume valid and maximal size + */ + data->valid[i / 16] |= 1 << (i % 16); + data->maxblk[i] = RT11_MAXPARTSZ - 1; + + type = validate(mount, i, &data->maxblk[i], &data->first[i]); + + if (type != RT11_NOPART) + validcount++; + else data->valid[i / 16] &= ~(1 << (i % 16)); + + if (type == RT11_SINGLE) + break; + } + + data->filesystems = validcount; + + if (validcount != 0) { + if (!quiet) + printf("%s: successfully mounted (%d partition%s)\n\n", + mount->name, validcount, validcount == 1 ? "" : "s"); + + for (i = 0; (i < 256) && (validcount != 0); i++) + if (RT11_PARTITIONVALID(data, i)) { + uint16_t entrysz, freeblks = 0, dsseg = 1; + uint16_t highest = 0; + + do { + uint16_t off = RT11_DH_SIZE; + + if (rt11ReadDirSegment(mount, i, dsseg) == 0) + return 0; + + if (highest == 0) + highest = le16toh(data->buf[RT11_DH_HIGHEST]); + + entrysz = RT11_DI_SIZE + (le16toh(data->buf[RT11_DH_EXTRA]) >> 1); + + while (!RT11EOS(le16toh(data->buf[off + RT11_DI_STATUS])) && + ((RT11_DS_SIZE - off) >= entrysz)) { + uint16_t status = le16toh(data->buf[off + RT11_DI_STATUS]); + + if ((status & RT11_E_MPTY) != 0) + freeblks += le16toh(data->buf[off + RT11_DI_LENGTH]); + + off += entrysz; + } + dsseg = le16toh(data->buf[RT11_DH_NEXT]); + } while (dsseg != 0); + + if (!quiet) { + char vers[4], *version = NULL; + uint16_t cnt, extra; + + cnt = le16toh(data->buf[RT11_DH_COUNT]); + extra = le16toh(data->buf[RT11_DH_EXTRA]); + + if (rt11ReadBlock(mount, i, RT11_HOME, NULL) == 0) + return 0; + + /* + * Special handling of volumes created by non-RT-11 systenms + * (e.g. VMS Exchange). + */ + if (strncmp((char *)&data->buf[RT11_HB_SYSID], RT11_VMSSYSID, strlen(RT11_VMSSYSID)) == 0) { + r50Asc(le16toh(data->buf[RT11_HB_SYSVER]), vers); + version = vers; + } else { + switch (le16toh(data->buf[RT11_HB_SYSVER])) { + case RT11_SYSVER_V3A: + version = "V3A"; + break; + + case RT11_SYSVER_V04: + version = "V04"; + break; + + case RT11_SYSVER_V05: + version = "V05"; + break; + } + } + + printf("%s%o:\n", mount->name, i); + if (version != NULL) + printf(" Version: %s, System ID: %12s\n", + version, (char *)&data->buf[RT11_HB_SYSID]); + printf(" Total blocks: %5d, Free blocks: %5d\n" + " Directory segments: %2d (Highest in use: %d)\n" + " Extra bytes/directory entry: %d\n", + data->maxblk[i] + 1, freeblks, cnt, highest, extra); + if (data->sectorsz != 0) + printf(" Sector size: %d\n", data->sectorsz); + } + validcount--; + } + return 1; + } + } + return 0; +} + +/*++ + * r t 1 1 U m o u n t + * + * Unmount the RT-11 file system(s), releasing any storage allocated. + * + * Inputs: + * + * mount - pointer to the mounted file system descriptor + * + * Outputs: + * + * None + * + * Returns: + * + * None + * + --*/ +static void rt11Umount( + struct mountedFS *UNUSED(mount) +) +{ +} + +/*++ + * r t 1 1 S i z e + * + * Return the size of an RT-11 container file. + * + * Inputs: + * + * None + * + * Outputs: + * + * None + * + * Returns: + * + * Size of the container file in bytes. + * + --*/ +static size_t rt11Size(void) +{ + size_t size = (RT11_MAXPARTSZ - 1) * RT11_BLOCKSIZE; + + if (SWISSET('t')) { + int i = 0; + char *type = SWGETVAL('t'); + + while (rt11DiskSize[i].name != NULL) { + if (strcmp(rt11DiskSize[i].name, type) == 0) { + size = rt11DiskSize[i].size; + break; + } + i++; + } + if (size == ((RT11_MAXPARTSZ - 1) * RT11_BLOCKSIZE)) + fprintf(stderr, + "newfs: Invalid device type \"%s\", using default\n", type); + } + return size; +} + +/*++ + * r t 1 1 N e w f s + * + * Create an empty RT11 file system. + * + * Inputs: + * + * mount - pointer to a mounted file system descriptor + * (not in the mounted file system list) + * size - the size (in bytes) of the file system + * + * Outputs: + * + * None + * + * Returns: + * + * 1 if the file system was successfully created, 0 otherwise + * + --*/ +static int rt11Newfs( + struct mountedFS *mount, + size_t size +) +{ + struct RT11data *data = &mount->rt11data; + int i; + uint16_t checksum = 0, extra = 0;; + + /* + * Check for device type override. + */ + if (SWISSET('t')) { + if (strcmp("rx01", SWGETVAL('t')) == 0) { + mount->skip = RT11_RX0xNSECT * RT11_RX01SS; + data->sectorsz = RT11_RX01SS; + } + if (strcmp("rx02", SWGETVAL('t')) == 0) { + mount->skip = RT11_RX0xNSECT * RT11_RX02SS; + data->sectorsz = RT11_RX02SS; + } + } + + /* + * Check for extra bytes on each directory entry, rounded up to nearest + * even value. + */ + if (SWISSET('e')) { + char *endptr; + + extra = strtoul(SWGETVAL('e'), &endptr, 10); + extra = (extra + 1) & ~1; + + if ((extra > 63) || (*endptr != '\0')) { + fprintf(stderr, "newfs: bad -e switch value \"%s\" - ignored\n", + SWGETVAL('e')); + extra = 0; + } + } + + /* + * Remove possible first track + */ + size = (size - mount->skip) / RT11_BLOCKSIZE; + // size = ((size * RT11_BLOCKSIZE) - mount->skip) / RT11_BLOCKSIZE; + + /* + * Mark partition 0 as valid + */ + memset(data->valid, 0, sizeof(data->valid)); + data->valid[0] = 1; + data->maxblk[0] = size; + data->first[0] = RT11_DSSTART; + data->filesystems = 1; + + /* + * Build and write the Home Block. + */ + memset(&data->buf[0], 0, RT11_BLOCKSIZE); + + data->buf[RT11_HB_PCS] = htole16(1); + data->buf[RT11_HB_FIRST] = htole16(RT11_DSSTART); + data->buf[RT11_HB_SYSVER] = htole16(RT11_SYSVER_V05); + + memcpy((char *)&data->buf[RT11_HB_VOLID], RT11_VOLID, strlen(RT11_VOLID)); + memcpy((char *)&data->buf[RT11_HB_OWNER], RT11_OWNER, strlen(RT11_OWNER)); + memcpy((char *)&data->buf[RT11_HB_SYSID], RT11_SYSID, strlen(RT11_SYSID)); + + for (i = 0; i < 255; i++) + checksum += le16toh(data->buf[i]); + + data->buf[RT11_HB_CHKSUM] = htole16(checksum); + + if (rt11WriteBlock(mount, 0, RT11_HOME, NULL) == 0) + return 0; + + /* + * Build the maximum # of directory segments + */ + for (i = 1; i <= RT11_DS_MAX; i++) { + memset(data->buf, 0, sizeof(data->buf)); + + data->buf[RT11_DH_COUNT] = htole16(RT11_DS_MAX); + data->buf[RT11_DH_HIGHEST] = htole16(1); + data->buf[RT11_DH_EXTRA] = htole16(extra); + data->buf[RT11_DH_START] = htole16(RT11_DSSTART + (2 * RT11_DS_MAX)); + + if (i == 1) { + data->buf[RT11_DH_SIZE + RT11_DI_STATUS] = htole16(RT11_E_MPTY); + data->buf[RT11_DH_SIZE + RT11_DI_LENGTH] = + htole16(size - (RT11_DSSTART + (2 * RT11_DS_MAX))); + data->buf[RT11_DH_SIZE + RT11_DI_SIZE + RT11_DI_STATUS] = + htole16(RT11_E_EOS); + } else data->buf[RT11_DH_SIZE + RT11_DI_STATUS] = htole16(RT11_E_EOS); + + if (rt11WriteDirSegment(mount, 0, i) == 0) + return 0; + } + return 1; +} + +/*++ + * r t 1 1 I n f o + * + * Display information about the internal structure of the RT-11 file + * system(s). + * + * Inputs: + * + * mount - pointer to the mounted file system descriptor + * unit - partition number + * present - partition number present + * + * Outputs: + * + * None + * + * Returns: + * + * None + * + --*/ +static void rt11Info( + struct mountedFS *mount, + uint8_t unit, + uint8_t present +) +{ + struct RT11data *data = &mount->rt11data; + + if (present) { + if (RT11_PARTITIONVALID(data, unit)) + info(mount, unit); + } else { + uint16_t i, count = data->filesystems; + + /* + * Display information about all valid partitions + */ + for (i = 0; (i < 256) && (count != 0); i++) + if (RT11_PARTITIONVALID(data, i)) { + info(mount, i); + count--; + } + } +} + +/*++ + * r t 1 1 D i r + * + * Produce a full or brief directory listing. + * + * Inputs: + * + * mount - pointer to the mounted file system descriptor + * unit - partition number + * fname - pointer to filename string + * + * Outputs: + * + * None + * + * Returns: + * + * None + * + --*/ +static void rt11Dir( + struct mountedFS *mount, + uint8_t unit, + char *fname +) +{ + struct RT11data *data = &mount->rt11data; + struct rt11FileSpec spec; + + if (rt11ParseFilespec(fname, &spec, RT11_M_NONAME) == 0) { + fprintf(stderr, "dir: syntax error in file spec \"%s\"\n", fname); + return; + } + + if (RT11_PARTITIONVALID(data, unit)) { + uint16_t entrysz, dsseg = 1; + regex_t reg; + + if ((spec.flags & (RT11_WC_NAME | RT11_WC_TYPE)) != 0) + if (rt11BuildRegex(&spec, ®) == 0) { + fprintf(stderr, "dir: regcomp() failed\n"); + return; + } + + do { + uint16_t off = RT11_DH_SIZE; + + if (rt11ReadDirSegment(mount, unit, dsseg) == 0) { + if ((spec.flags & (RT11_WC_NAME | RT11_WC_TYPE)) != 0) + regfree(®); + return; + } + + entrysz = RT11_DI_SIZE + (le16toh(data->buf[RT11_DH_EXTRA]) >> 1); + + /* + * Loop until we see and end-of-segment marker or there is no room for + * another directory entry. + */ + while (!RT11EOS(le16toh(data->buf[off + RT11_DI_STATUS])) && + ((RT11_DS_SIZE - off) >= entrysz)) { + uint16_t status = le16toh(data->buf[off + RT11_DI_STATUS]); + + if ((status & (RT11_E_TENT | RT11_E_MPTY)) == 0) { + uint16_t fname1 = le16toh(data->buf[off + RT11_DI_FNAME1]); + uint16_t fname2 = le16toh(data->buf[off + RT11_DI_FNAME2]); + uint16_t ftype = le16toh(data->buf[off + RT11_DI_FTYPE]); + + if ((spec.flags & (RT11_WC_NAME | RT11_WC_TYPE)) == 0) { + if ((fname1 != spec.name[0]) || + (fname2 != spec.name[1]) || + (ftype != spec.type)) + goto nomatch; + } else { + if (rt11MatchRegex(®, fname1, fname2, ftype) == 0) + goto nomatch; + } + + rt11DisplayDir(&data->buf[off], SWISSET('f')); + } + + nomatch: + off += entrysz; + } + + dsseg = le16toh(data->buf[RT11_DH_NEXT]); + } while (dsseg != 0); + + if ((spec.flags & (RT11_WC_NAME | RT11_WC_TYPE)) != 0) + regfree(®); + } +} + +/*++ + * r t 1 1 O p e n F i l e R + * + * Open an RT-11 file for reading. + * + * Inputs: + * + * mount - pointer to the mounted file system descriptor + * unit - partition number + * fname - pointer to filename string + * + * Outputs: + * + * None + * + * Returns: + * + * Pointer to open file descriptor, NULL if open fails + * + --*/ +static void *rt11OpenFileR( + struct mountedFS *mount, + uint8_t unit, + char *fname +) +{ + struct rt11OpenFile *file; + struct rt11FileSpec spec; + + if (rt11ParseFilespec(fname, &spec, RT11_M_NONE) == 0) { + fprintf(stderr, "Failed to parse filename \"%s\"\n", fname); + return NULL; + } + + if ((file = malloc(sizeof(struct rt11OpenFile))) != NULL) { + memset(file, 0, sizeof(struct rt11OpenFile)); + + if (rt11LookupFile(mount, unit, &spec, file) != 0) { + /* + * Allocate local buffer space for the file. + */ + if ((file->buffer = malloc(mount->blocksz)) == NULL) { + free(file); + return NULL; + } + file->mode = M_RD; + } else { + free(file); + return NULL; + } + } + return file; +} + +/*++ + * r t 1 1 O p e n F i l e W + * + * Open an RT-11 file for writing. + * + * Inputs: + * + * mount - pointer to the mounted file system descriptor + * unit - partition number + * fname - pointer to filename string + * size - estimated file size (in bytes) + * 0 means allocate as much space as possible + * + * Outputs: + * + * %None + * + * Returns: + * + * Pointer to open file descriptor, NULL if open fails + * + --*/ +void *rt11OpenFileW( + struct mountedFS *mount, + uint8_t unit, + char *fname, + off_t size +) +{ + struct rt11OpenFile *file; + struct rt11FileSpec spec; + uint16_t blocks = 0; + + if (size != 0) + blocks = (size + (RT11_BLOCKSIZE - 1)) / RT11_BLOCKSIZE; + + if (rt11ParseFilespec(fname, &spec, RT11_M_NONE) == 0) { + fprintf(stderr, "Failed to parse filename \"%s\"\n", fname); + return NULL; + } + + if ((file = malloc(sizeof(struct rt11OpenFile))) != NULL) { + memset(file, 0, sizeof(struct rt11OpenFile)); + + if (rt11LookupFile(mount, unit, &spec, file) == 0) { + /* + * Allocate local buffer space for the file. + */ + if ((file->buffer = malloc(mount->blocksz)) != NULL) { + if (rt11CreateFile(mount, unit, &spec, file, blocks) == 0) { + ERROR("Failed to create file \"%s\"\n", fname); + free(file->buffer); + free(file); + return NULL; + } else file->mode = M_WR; + } else { + ERROR("Buffer allocation failure for \"%s\"\n", fname); + free(file); + return NULL; + } + } else { + ERROR("File \"%s\" already exists\n", fname); + free(file); + return NULL; + } + } else ERROR("Memory allocation failure\n"); + return file; +} + +/*++ + * r t 1 1 F i l e S i z e + * + * Return an estimate of the size of a currently open file. This routine + * bases the file size on the number of blocks allocated to the file and + * may over-report the actual size of the file. + * + * Inputs: + * + * filep - pointer to open file descriptor + * + * Outputs: + * + * None + * + * Returns: + * + * Estimate of the file size + * + --*/ +static off_t rt11FileSize( + void *filep +) +{ + struct rt11OpenFile *file = filep; + + return le16toh(file->length) * RT11_BLOCKSIZE; +} + +/*++ + * r t 1 1 D e l e t e F i l e + * + * Delete a file from an RT-11 file system. + * + * Inputs: + * + * filep - pointer to open file descriptor + * fname - pointer to filename string + * + * Outputs: + * + * The mount point specific buffer will be modified. + * + * Returns: + * + * None + * + --*/ +static void rt11DeleteFile( + void *filep, + char *UNUSED(fname) +) +{ + struct rt11OpenFile *file = filep; + struct mountedFS *mount = file->mount; + struct RT11data *data = &mount->rt11data; + + if (rt11ReadDirSegment(mount, file->unit, file->segment) == 0) + return; + + data->buf[file->offset + RT11_DI_STATUS] = le16toh(RT11_E_MPTY); + + rt11MergeEmptyRegions(mount, file->offset); + + if (rt11WriteDirSegment(mount, file->unit, file->segment) == 0) + return; + + rt11CloseFile(file); +} + +/*++ + * r t 1 1 C l o s e F i l e + * + * Close an open RT-11 file. + * + * Inputs: + * + * filep - pointer to open file descriptor + * + * Outputs: + * + * None + * + * Returns: + * + * None + * + --*/ +static void rt11CloseFile( + void *filep +) +{ + struct rt11OpenFile *file = filep; + + if (file->mode == M_WR) { + if (SWISSET('a') && + !SWISSET('p') && + (file->count != RT11_BLOCKSIZE)) { + char ch = '\032'; + + rt11WriteBytes(file, &ch, 1); + } + + if (file->current == 0) + file->current = le16toh(file->start); + + /* + * Flush the current buffer + */ + rt11WriteBlock(file->mount, file->unit, file->current, file->buffer); + rt11UpdateFile(file->mount, file->unit, file); + } + + if (file != NULL) { + if (file->buffer != NULL) + free(file->buffer); + free(file); + } +} + +/*++ + * r t 1 1 R e a d F i l e + * + * Read data from an RT-11 file to a supplied buffer. + * + * Inputs: + * + * filep - pointer to open file descriptor + * buf - pointer to buffer + * buflen - length of the supplied buffer + * + * Outputs: + * + * None + * + * Returns: + * + * # of bytes of data read, 0 means EOF or error + * + --*/ +static size_t rt11ReadFile( + void *filep, + void *buf, + size_t buflen +) +{ + struct rt11OpenFile *file = filep; + char *bufr = buf; + + if (SWISSET('a')) { + char ch; + size_t count = 0; + + /* + * Read a full or partial line from the open file. + */ + while ((buflen != 0) && (rt11ReadBytes(file, &ch, 1) == 1)) { + if (ch == '\032') { + /* + * ^Z indicating EOF + */ + rt11UngetByte(file); + break; + } + bufr[count++] = ch; + buflen--; + if (ch == '\n') + break; + } + return count; + } + + return rt11ReadBytes(file, bufr, buflen); +} + +/*++ + * r t 1 1 W r i t e F i l e + * + * Write data to an RT-11 file from a supplied buffer. + * + * Inputs: + * + * filep - pointer to open file descriptor + * buf - pointer to buffer + * buflen - length of the supplied buffer + * + * Outputs: + * + * None + * + * Returns: + * + * # of bytes of data written, 0 means EOF or error + * + --*/ +static size_t rt11WriteFile( + void *filep, + void *buf, + size_t buflen +) +{ + struct rt11OpenFile *file = filep; + char *bufw = buf; + + return rt11WriteBytes(file, bufw, buflen); +} + +/*++ + * r t 1 1 F S + * + * Descriptor for accessing RT-11 file systems. + * + --*/ +struct FSdef rt11FS = { + NULL, + "rt11", + "rt11 PDP-11 RT-11 file system\n", + FS_UNITVALID, + RT11_BLOCKSIZE, + rt11Mount, + rt11Umount, + rt11Size, + rt11Newfs, + NULL, + rt11Info, + rt11Dir, + rt11OpenFileR, + rt11OpenFileW, + rt11FileSize, + rt11CloseFile, + rt11ReadFile, + rt11WriteFile, + rt11DeleteFile, + NULL, /* No tape support functions */ + NULL, + NULL, + NULL +}; diff --git a/converters/fsio/rt11.h b/converters/fsio/rt11.h new file mode 100644 index 0000000..378485a --- /dev/null +++ b/converters/fsio/rt11.h @@ -0,0 +1,306 @@ +/* + * Copyright (C) 2018 John Forecast. All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY JOHN FORECAST "AS IS" AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#ifndef __RT11_H__ +#define __RT11_H__ + +/* + * General disk layout: + * + * Block + * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + * 0 | Reserved | + * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + * 1 | Home Block (Reserved) | + * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + * 2 | Reserved | + * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + * 3 | Reserved | + * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + * 4 | Reserved | + * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + * 5 | Reserved | + * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + * 6 | Directory Segment 1 | + * 7 | | + * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + * 10 | Directory Segment 2 | + * 11 | | + * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + * | ... | + * | ... | + * | ... | + * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + * x | Directory Segment n | + * x+1 | | + * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + * Files | ... | + * | ... | + * | ... | + * | ... | + * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + * End of Volume + * + * Home Block: + * + * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + * 0000 | Bad block replacement table | + * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + * 0202 | Unused | + * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + * 0204 | INITIALIZE/RESTORE data area | + * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + * 0252 | BUP Information area | + * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + * 0274 | Unused | + * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + * 0700 | Reserved for Digital - 000000 | + * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + * 0702 | Reserved for Digital - 000000 | + * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + * 0704 | Unused | + * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + * 0722 | Pack cluster size - 000001 | + * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + * 0724 | Block # of first directory segment - 000006 | + * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + * 0726 | System version - Radix-50 "V3A"? | + * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + * 0730 | Volume identification - "RT11A " | + * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + * 0744 | Owner name - " " | + * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + * 0760 | System identification - "DECRT11A " | + * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + * 0776 | Checksum | + * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + * + * Directory Segment Header: + * + * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + * | Total # of directory segments (1 - 31) | + * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + * | Next logical directory segment # | + * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + * | Highest directory segment in use | + * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + * | Extra bytes per directory entry | + * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + * | Block # for start of this segment | + * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + * + * Directory Entry: + * + * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + * | Status Word | + * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + * | Radix-50 File Name (chars 1 - 3) | + * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + * | Radix-50 File Name (chars 4 - 6) | + * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + * | Radix-50 File Type (1 - 3 chars) | + * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + * | Job # | Channel # | + * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + * | Creation Date | + * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + * | Optional Extra Words | + * | ... | + * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + * + * Status Word: + * + * 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 + * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + * | | | | | | | | | | | | | | | | | + * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + * | | | | | | | + * | | | | | | - Prefix block indicator + * | | | | | - Tentative file + * | | | | - Empty area + * | | | - Permanent file + * | | - End of segment marker + * | - Protected from .WRITE requests + * - Protected permanent file + * + * Date Word: + * + * 15 14 13 10 9 5 4 0 + * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + * | | | | | + * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + * | | | | | | | | + * +---+ +-----------+ +---------------+ +---------------+ + * | | | | + * | | | Year - 1972 + * | | | - 32 x Age + * | | - Day (1 - 31) + * | - Month (1 - 12) + * - Age (0 - 3) + */ + +#define RT11_HOME 1 /* Home block is always 1 */ +#define RT11_DSSTART 6 /* Start of directory segs */ +#define RT11_BLOCKSIZE 512 /* Size of a data block on disk */ +#define RT11_RX01SS 128 /* Sector size of RX01 floppy */ +#define RT11_RX02SS 256 /* Sector size of RX02 floppy */ +#define RT11_RX0xNSECT 26 /* Sectors/track on RX01/RX02 */ + +#define RT11_SYSVER_V3A 36521 +#define RT11_SYSVER_V04 36434 +#define RT11_SYSVER_V05 36435 + +#define RT11_NOPART 0 /* Not a valid partition */ +#define RT11_SINGLE 1 /* Single partition per disk */ +#define RT11_MULTI 2 /* Multiple partitions per disk */ + +#define RT11_VOLID "RT11A " +#define RT11_OWNER " " +#define RT11_SYSID "DECRT11A " +#define RT11_VMSSYSID "DECVMSEXCHNG" /* VMS exchange created volume */ + +/* + * Partition sizes. The last block of a maximum sized partition (32MB) is + * unused. The minimum size is based on a file system having 1 directory + * segment and 1 data block! Is this reasonable? + */ +#define RT11_MAXPARTSZ 0200000 /* Max partition size */ +#define RT11_MINPARTSZ 0000010 /* Min partition size */ + +#define RT11_RK05SZ 4800 /* Blocks on an RK05 drive */ +#define RT11_RL01SZ 10240 /* Blocks on an RL01 drive */ +#define RT11_RL02SZ 20480 /* Blocks on an RL02 drive */ +#define RT11_RX0xSZ 2002 /* Sectors on an RX01/RX02 */ + +#define RT11_HB_BBLOCK 0000 /* Bad block replacement tbl */ +#define RT11_HB_RESTORE 0102 /* INIT/RESTORE data area */ +#define RT11_HB_BUP 0125 /* BUP info area */ +#define RT11_HB_PCS 0351 /* Pack cluster size */ +#define RT11_HB_FIRST 0352 /* First directory segment */ +#define RT11_HB_SYSVER 0353 /* System version */ +#define RT11_HB_VOLID 0354 /* Volume identification */ +#define RT11_HB_OWNER 0362 /* Owner name */ +#define RT11_HB_SYSID 0370 /* System identification */ +#define RT11_HB_CHKSUM 0377 /* Checksum */ + +#define RT11_DH_COUNT 0000 /* # of directory segments */ +#define RT11_DH_NEXT 0001 /* Next logical segment # */ +#define RT11_DH_HIGHEST 0002 /* Highest segment # in use */ +#define RT11_DH_EXTRA 0003 /* Extra bytes/dir. entry */ +#define RT11_DH_START 0004 /* Block # for segment start */ +#define RT11_DH_SIZE 0005 /* Size of header */ + +#define RT11_DS_SIZE 512 /* Directory segment size */ +#define RT11_DS_DISPACE (RT11_DS_SIZE - RT11_DH_SIZE) +#define RT11_DS_MAX 31 /* Max # of directory segments */ + +#define RT11_DI_STATUS 0000 /* Status word */ +#define RT11_DI_FNAME1 0001 /* File name (chars 1 - 3) */ +#define RT11_DI_FNAME2 0002 /* File name (chars 4 - 6) */ +#define RT11_DI_FTYPE 0003 /* File type (1 - 3 chars) */ +#define RT11_DI_LENGTH 0004 /* Total file length */ +#define RT11_DI_JOB_CHN 0005 /* Channel # */ +#define RT11_DI_CREATE 0006 /* Date of creation */ +#define RT11_DI_SIZE 0007 /* Default entry size */ + +#define RT11_E_PRE 000020 /* Prefix block indicator */ +#define RT11_E_TENT 000400 /* Tentative file */ +#define RT11_E_MPTY 001000 /* Empty area */ +#define RT11_E_PERM 002000 /* Permanent file */ +#define RT11_E_EOS 004000 /* End of segment marker */ +#define RT11_E_READ 040000 /* Protected from .WRITE */ +#define RT11_E_PROT 100000 /* Protected permanent file */ + +#define RT11EOS(v) (((v) & RT11_E_EOS) != 0) + +#define RT11_DW_YEAR 0000037 /* Year */ +#define RT11_DW_DAY 0001740 /* Day */ +#define RT11_DW_MONTH 0036000 /* Month */ +#define RT11_DW_AGE 0140000 /* Age */ + +/* + * Structure to describe a filename. Asterisks may be used as wild card + * characters for the 2 components of a filename; name and type. + */ +struct rt11FileSpec { + uint8_t flags; /* Wild card indicators */ + uint16_t name[2]; /* File name (RAD50) */ + uint16_t type; /* File type (RAD50) */ + char fname[6]; /* File name (ASCII) */ + char ftype[3]; /* File type (ASCII) */ +}; +#define RT11_WC_NAME 0001 /* Wild card in name */ +#define RT11_WC_TYPE 0002 /* Wild card in type */ + +#define RT11_M_NONE 0000 /* Wild cards not allowed */ +#define RT11_M_ALLOW 0001 /* Wild cards allowed */ +#define RT11_M_NONAME 0002 /* Wild cards allowed */ + /* If no filename + extension */ + /* present, default to *.* */ +/* + * Structure to define an open file. This is an RT-11 directory entry along + * with sufficient information to be able to write the directory entry back + * to disk. Some of the directory information can be created on-the-fly so + * will not be stored here. + */ +struct rt11OpenFile { + uint16_t status; /* File status */ + uint16_t name[2]; /* File name */ + uint16_t type; /* File type */ + uint16_t length; /* Blocks written */ + uint16_t creation; /* Creation date */ + /* End of directory entry */ + uint8_t segment; /* Directory segment # */ + uint16_t offset; /* Directory offset */ + /* Start of read/write info */ + enum openMode mode; /* Open mode (read/write) */ + struct mountedFS *mount; /* Mounted file system descriptor */ + uint8_t unit; /* Partition number */ + char *buffer; /* Private buffer for file I/O */ + uint16_t current; /* Current working block # */ + uint16_t last; /* Last usable block */ + uint16_t count; /* Bytes used in current block */ + uint16_t start; /* Starting block # */ +}; + +/* + * RT-11 specific data area. + */ +struct RT11data { + unsigned int blocks; /* Size of container */ + unsigned int sectorsz; /* Interleave sector size */ + /* 0 if no interleave */ + uint16_t filesystems; /* Max # of filesystems */ + uint16_t valid[16]; /* Valid partitions */ + uint16_t maxblk[256]; /* Max block address */ + uint16_t first[256]; /* First directory block */ + uint16_t buf[512]; /* Disk buffer - enough for a */ + /* directory segment */ +}; +#define RT11_PARTITIONVALID(d, u) ((d->valid[u / 16] & (1 << (u % 16))) != 0) + +#endif diff --git a/converters/fsio/tape.c b/converters/fsio/tape.c new file mode 100644 index 0000000..59f0b8a --- /dev/null +++ b/converters/fsio/tape.c @@ -0,0 +1,816 @@ +/* + * Copyright (C) 2018 John Forecast. All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY JOHN FORECAST "AS IS" AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/* + * Support routines for reading/writing SIMH tape container files. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "fsio.h" + +/*++ + * t a p e V e r i f y + * + * Verify that the container format is valid, leaving the tape positioned + * at beginning-of-tape. + * + * Inputs: + * + * container - pointer open container file + * eot - return end-of-tape info here, NUL if not needed + * + * Outputs: + * + * None + * + * Returns: + * + * 1 if format is valid, 0 otherwise + * + --*/ +int tapeVerify( + FILE *container, + off_t *eot +) +{ + int errorCount = 0, tmSeen = 0; + uint32_t meta, header, bc; + off_t position; + struct stat stat; + + /* + * Determine the size of the file. + */ + fstat(fileno(container), &stat); + + for (;;) { + position = ftello(container); + + /* + * If we are positioned at the end-of-file, there is a tape mark or + * end-of-media marker missing. Treat it as though one is present. + */ + if (position == stat.st_size) + break; + + if (fread(&meta, sizeof(meta), 1, container) != 1) + return 0; + + bc = le32toh(meta); + + switch (bc) { + case ST_TM: + if (++tmSeen <= 1) + break; + /* Treat second TM in a row as end of medium */ + /* FALLTHROUGH */ + + case ST_EOM: + if (fseeko(container, -sizeof(meta), SEEK_CUR) != 0) + return 0; + + if (errorCount) + printf("mount: Tape contains error records\n"); + goto done; + + case ST_GAP: + break; + + default: + /* + * Record descriptor + */ + tmSeen = 0; + + header = bc; + if ((bc & ST_ERROR) != 0) + errorCount++; + if ((bc & ST_MBZ) != 0) + return 0; + + bc = RECLEN(bc & ST_LENGTH); + + /* + * Check if we are seeking ouside 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 0; + + if (fseeko(container, bc, SEEK_CUR) != 0) + return 0; + if (fread(&meta, sizeof(meta), 1, container) != 1) + return 0; + + bc = le32toh(meta); + + if (header != bc) + return 0; + } + } + done: + if (eot != NULL) + *eot = ftello(container); + + /* + * Position at beginning-of-tape. + */ + if (fseeko(container, 0, SEEK_SET) == 0) + return 1; + return 0; +} + +/*++ + * t a p e G e t P o s i t i o n + * + * Get the current position of the tape. + * + * Inputs: + * + * container - pointer open container file + * + * Outputs: + * + * None + * + * Returns: + * + * Current position of the tape + * + --*/ +off_t tapeGetPosition( + FILE *container +) +{ + return ftello(container); +} + +/*++ + * t a p e S e t P o s i t i o n + * + * Position the tape to a position previously obtained by tapeGetPosition(). + * + * Inputs: + * + * container - pointer open container file + * pos - requested position + * + * Outputs: + * + * None + * + * Returns: + * + * 1 if successfully positioned, 0 if error + * + --*/ +int tapeSetPosition( + FILE *container, + off_t pos +) +{ + return fseeko(container, pos, SEEK_SET) == 0 ? 1 : 0; +} + +/*++ + * t a p e S k i p R e c o r d F + * + * Skip over the next record in the forward direction. + * + * Inputs: + * + * container - pointer open container file + * + * Outputs: + * + * None + * + * Returns: + * + * ST_FAIL - error accessing container file + * ST_EOM - end of media detected + * ST_TM - tape mark detected + * Other - record length (including error flag) + * + --*/ +uint32_t tapeSkipRecordF( + FILE *container +) +{ + return tapeReadRecordLength(container); +} + +/*++ + * t a p e S k i p R e c o r d R + * + * Skip over the next record in the reverse direction. + * + * Inputs: + * + * container - pointer open container file + * + * Outputs: + * + * None + * + * Returns: + * + * ST_FAIL - error accessing container file + * ST_EOM - end of media detected + * ST_TM - tape mark detected + * Other - record length (including error flag) + * + --*/ +uint32_t tapeSkipRecordR( + FILE *container +) +{ + return tapeReadRecordLengthReverse(container); +} + +/*++ + * t a p e P e e k R e c o r d L e n g t h + * + * Get the length of the next record on the tape without actually reading + * the data or changing the current position of the tape. + * + * Inputs: + * + * container - pointer open container file + * + * Outputs: + * + * None + * + * Returns: + * + * ST_FAIL - error accessing the container file + * ST_EOM - end of media detected + * ST_TM - tape mark detected + * Other - record length (including error flag) + * + --*/ +uint32_t tapePeekRecordLength( + FILE *container +) +{ + off_t pos = ftello(container); + uint32_t meta; + + if ((fread(&meta, sizeof(meta), 1, container) != 1) || + (fseeko(container, pos, SEEK_SET) != 0)) + return ST_FAIL; + + return le32toh(meta); +} + +/*++ + * t a p e R e a d R e c o r d + * + * 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, + * losing data. + * + * Inputs: + * + * container - pointer open container file + * buf - pointer to the buffer to receive the data + * len - length of the buffer + * + * Outputs: + * + * None + * + * Returns: + * + * ST_FAIL - error accessing the container file + * ST_EOM - end of media 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_t tapeReadRecord( + FILE *container, + void *buf, + int len +) +{ + off_t pos = ftello(container); + uint32_t meta, bc, erflag, length; + + if (fread(&meta, sizeof(meta), 1, container) != 1) + return ST_FAIL; + + bc = le32toh(meta); + + switch (bc) { + case ST_EOM: + case ST_TM: + return bc; + + default: + erflag = bc & ST_ERROR; + bc &= ST_LENGTH; + + length = (uint32_t)len; + if (bc < length) + length = bc; + + if (fread(buf, sizeof(uint8_t), length, container) != length) + return ST_FAIL; + + /* + * Now position the file after this record. + */ + pos += RECLEN(bc) + (2 * sizeof(meta)); + if (fseeko(container, pos, SEEK_SET) != 0) + return ST_FAIL; + + return erflag | length; + } + return ST_FAIL; +} + +/*++ + * t a p e R e a d R e c o r d L e n g t h + * + * Get the length of the next record on the tape without actually reading + * the data. The tape will be positioned at the start of the next record. + * + * Inputs: + * + * container - pointer open container file + * + * Outputs: + * + * None + * + * Returns: + * + * ST_FAIL - error accessing container file + * ST_EOM - end of media detected + * ST_TM - tape mark detected + * Other - record length (including error flag) + * + --*/ +uint32_t tapeReadRecordLength( + FILE *container +) +{ + off_t pos = ftello(container); + uint32_t meta, bc, erflag; + + if (fread(&meta, sizeof(meta), 1, container) != 1) + return ST_FAIL; + + bc = le32toh(meta); + + 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 (fseeko(container, pos, SEEK_SET) != 0) + return ST_FAIL; + + return erflag | bc; + } + return ST_FAIL; +} + +/*++ + * t a p e R e a d R e c o r d L e n g t h R e v e r s e + * + * Get the length of the previous record on the tape without actually reading + * the data. The tape will be positioned at the start of the previous record. + * + * Inputs: + * + * container - pointer open container file + * + * Outputs: + * + * None + * + * Returns: + * + * ST_FAIL - error accessing container file + * ST_EOM - end of media detected + * ST_TM - tape mark detected + * Other - record length (including error flag) + * + --*/ +uint32_t tapeReadRecordLengthReverse( + FILE *container +) +{ + uint32_t meta, bc, erflag; + off_t delta; + + if ((fseeko(container, -sizeof(meta), SEEK_CUR) != 0) || + (fread(&meta, sizeof(meta), 1, container) != 1)) + return ST_FAIL; + + bc = le32toh(meta); + + switch (bc) { + case ST_EOM: + case ST_TM: + if (fseeko(container, -sizeof(meta), SEEK_CUR) != 0) + return ST_FAIL; + return bc; + + default: + erflag = bc & ST_ERROR; + bc &= ST_LENGTH; + + /* + * Now position the file before this record. + */ + delta = RECLEN(bc) + (2 * sizeof(meta)); + if (fseeko(container, -delta, SEEK_CUR) != 0) + return ST_FAIL; + + return erflag | bc; + } + return ST_FAIL; +} + +/*++ + * t a p e W r i t e R e c o r d + * + * Write a record to the tape at it's current position leaving the tape + * positioned after the newly written record. + * + * Inputs: + * + * container - pointer open container file + * buf - pointer to the record to be written + * len - length of the record + * + * Outputs: + * + * None + * + * Returns: + * + * 1 if record was successfully written, 0 otherwise + * + --*/ +int tapeWriteRecord( + FILE *container, + void *buf, + int len +) +{ + uint32_t meta = htole16(len); + int datalen = (len + 1) & ~1; + + if ((fwrite(&meta, sizeof(meta), 1, container) != 1) || + (fwrite(buf, datalen, 1, container) != 1) || + (fwrite(&meta, sizeof(meta), 1, container) != 1)) + return 0; + + return 1; +} + +/*++ + * t a p e W r i t e E O M + * + * Write an end-of-media record to the tape at it's current position and, + * optionally, backup the tape to before the newly written record. + * + * Inputs: + * + * container - pointer open container file + * backup - if 1, position the tape before the new record + * + * Outputs: + * + * None + * + * Returns: + * + * 1 if EOM record successfully written, 0 otherwise + * + --*/ +int tapeWriteEOM( + FILE *container, + int backup +) +{ + uint32_t eom = htole32(ST_EOM); + + if (fwrite(&eom, sizeof(eom), 1, container) != 1) + return 0; + + if (backup) + if (fseeko(container, -sizeof(eom), SEEK_CUR) != 0) + return 0; + + return 1; +} + +/*++ + * t a p e W r i t e T M + * + * Write a tape mark record to the tape at it's current position. + * + * Inputs: + * + * container - pointer open container file + * + * Outputs: + * + * None + * + * Returns: + * + * 1 if EOM record successfully written, 0 otherwise + * + --*/ +int tapeWriteTM( + FILE *container +) +{ + uint32_t tm = htole32(ST_TM); + + if (fwrite(&tm, sizeof(tm), 1, container) != 1) + return 0; + + return 1; +} + +/*++ + * t a p e E O M + * + * Position the tape to the end of media so that a subsequent write will + * append a file to the tape. + * + * Inputs: + * + * container - pointer open container file + * eot - pointer to end-of-tape position, NULL if + * not available + * + * Outputs: + * + * None + * + * Returns: + * + * 1 if tape successfully positioned, 0 otherwise + * + --*/ +int tapeEOM( + FILE *container, + off_t *eot +) +{ + int whence = eot == NULL ? SEEK_END : SEEK_SET; + off_t pos = eot == NULL ? 0 : *eot; + uint32_t bc1, bc2; + + /* + * Move to the end of the tape and then look backwards to see how it is + * terminated. + */ + if (fseeko(container, pos, whence) == 0) { + if (ftello(container) == 0) { + /* + * Empty file, we are correctly positioned. + */ + return 1; + } + + if ((bc1 = tapeReadRecordLengthReverse(container)) == ST_FAIL) + return 0; + + if ((bc1 == ST_EOM) || (bc1 == ST_TM)) { + if (ftello(container) == 0) { + /* + * Only ST_EOM or ST_TM present, we are correctly positioned. + */ + return 1; + } + + if ((bc2 = tapeReadRecordLengthReverse(container)) == ST_FAIL) + return 0; + + if (bc2 == ST_TM) { + if (ftello(container) == 0) { + /* + * Only ST_TM followed by ST_TM or ST_EOM, we are correctly + * positioned + */ + return 1; + } + + /* + * ST_TM followed by ST_TM or ST_EOM with at least one data block + * present, skip over the initial ST_TM. + */ + if (fseeko(container, sizeof(uint32_t), SEEK_CUR) == 0) + return 1; + } else { + /* + * Only a single ST_TM at the end of the container file. This + * indicates that there is a missing ST_TM or ST_EOM. Position + * the tape at the logical end-of-tape so that any subsequent file + * write will fix the problem. + */ + if (fseeko(container, pos, whence) == 0) + return 1; + } + } + } + return 0; +} + +/*++ + * t a p e R e w i n d + * + * Rewind the tape. + * + * Inputs: + * + * container - pointer open container file + * + * Outputs: + * + * None + * + * Returns: + * + * None + * + --*/ +void tapeRewind( + FILE *container +) +{ + fseeko(container, 0, SEEK_SET); +} + +/*++ + * t a p e S k i p F o r w a r d + * + * Skip forward over a number of files. If end-of-media is reached, the skip + * operation will terminate early. + * + * Inputs: + * + * container - pointer open container file + * count - # of files to skip + * + * Outputs: + * + * None + * + * Returns: + * + * 1 if skip was successful, 0 otherwise + * + --*/ +int tapeSkipForward( + FILE *container, + unsigned long count +) +{ + unsigned long i; + uint32_t bc; + + for (i = 0; i < count; i++) { + /* + * Peek at the next record. If it's a tape mark or end-of-media there + * are not more files to skip. + */ + switch (tapePeekRecordLength(container)) { + case ST_FAIL: + return 0; + + case ST_TM: + case ST_EOM: + return 1; + } + /* + * Skip forward over 1 file. + */ + do { + switch (bc = tapeReadRecordLength(container)) { + case ST_FAIL: + return 0; + + case ST_EOM: + if (fseeko(container, -sizeof(uint32_t), SEEK_CUR) != 0) + return 0; + break; + } + } while ((bc != ST_TM) && (bc != ST_EOM)); + } + return 1; +} + +/*++ + * t a p e S k i p R e v e r s e + * + * Skip backwards over a number of files. If beginning-of-tape is reached, + * the skip operation will terminate early. + * + * Inputs: + * + * container - pointer open container file + * count - # of files to skip + * + * Outputs: + * + * None + * + * Returns: + * + * 1 if skip was successful, 0 otherwise + * + --*/ +int tapeSkipReverse( + FILE *container, + unsigned long count +) +{ + unsigned long i; + uint32_t bc; + + for (i = 0; i < count; i++) { + /* + * If we are at beginning-of-tape, there are no more files to skip. + */ + if (ftello(container) == 0) + return 1; + + /* + * If we are not at the beginning of tape, the previous record should + * be a tape mark. + */ + if (tapeReadRecordLengthReverse(container) != ST_TM) + return 0; + + /* + * Now skip over the remainder of the file. + */ + do { + if ((bc = tapeReadRecordLengthReverse(container)) == ST_FAIL) + return 0; + } while ((bc != ST_TM) && (ftello(container) != 0)); + + /* + * Skip over the tape mark since it marks the end of the previous file. + */ + if (bc == ST_TM) { + if (fseeko(container, sizeof(uint32_t), SEEK_CUR) != 0) + return 0; + } + } + return 1; +} + diff --git a/converters/fsio/tape.h b/converters/fsio/tape.h new file mode 100644 index 0000000..a3544f6 --- /dev/null +++ b/converters/fsio/tape.h @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2018 John Forecast. All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY JOHN FORECAST "AS IS" AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#ifndef __TAPE_H__ +#define __TAPE_H__ + +/* + * Metadata markers + */ +#define ST_EOM 0xFFFFFFFF /* end of medium */ +#define ST_GAP 0xFFFFFFFE /* erase gap */ +#define ST_TM 0x00000000 /* tape mark */ + +/* + * Special code for reporting errors. If actually read from a container + * file, this would be considered an error. + */ +#define ST_FAIL 0xFEFEFEFE + +/* + * 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) + +extern int tapeVerify(FILE *, off_t *); +extern off_t tapeGetPosition(FILE *); +extern int tapeSetPosition(FILE *, off_t); +extern uint32_t tapeSkipRecordF(FILE *); +extern uint32_t tapeSkipRecordR(FILE *); +extern uint32_t tapePeekRecordLength(FILE *); +extern uint32_t tapeReadRecord(FILE *, void *, int); +extern uint32_t tapeReadRecordLength(FILE *); +extern uint32_t tapeReadRecordLengthReverse(FILE *); +extern int tapeWriteRecord(FILE *, void *, int); +extern int tapeWriteEOM(FILE *, int); +extern int tapeWriteTM(FILE *); +extern int tapeEOM(FILE *, off_t *); +extern void tapeRewind(FILE *); +extern int tapeSkipForward(FILE *, unsigned long); +extern int tapeSkipReverse(FILE *, unsigned long); + +#endif diff --git a/converters/mksimtape/Makefile b/converters/mksimtape/Makefile new file mode 100644 index 0000000..583e594 --- /dev/null +++ b/converters/mksimtape/Makefile @@ -0,0 +1,20 @@ + # all of these can be over-ridden on the "make" command line if they don't suit your environment. +TOOL=mksimtape +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/mksimtape/mksimtape.c b/converters/mksimtape/mksimtape.c new file mode 100644 index 0000000..79905b0 --- /dev/null +++ b/converters/mksimtape/mksimtape.c @@ -0,0 +1,332 @@ +/* + * File: mksimtape.c + * + * Make a SIMH tape image + * + * Bob Eager March 2016 + * + */ + +/* + * History: + * + * 1.0 Initial version. + * + */ + +#include +#include +#include +#include +#include +#include + +#define VERSION 1 /* Major version number */ +#define EDIT 0 /* Edit number within major version */ + +#define FALSE 0 +#define TRUE 1 + +#define DEFBLKSIZE 10240 + +/* General type definitions */ + +typedef int BOOL,*PBOOL; +typedef char CHAR,*PCHAR; +typedef int INT,*PINT; +typedef unsigned char UCHAR,*PUCHAR; +typedef unsigned int UINT,*PUINT; +typedef void VOID,*PVOID; + +#if 0 +typedef short SHORT, *PSHORT; +typedef unsigned short USHORT, *PUSHORT; +typedef long LONG,*PLONG; +typedef unsigned long ULONG,*PULONG; +#endif + +/* Forward references */ + +static INT blksize(INT); +static VOID error(PCHAR, ...); +static INT little_endian(VOID); +static VOID putusage(VOID); +static BOOL tape_mark(VOID); +static BOOL write_file(PCHAR, INT); + +/* Local storage */ + +static PCHAR progname; /* Name of program, as a string */ + +/* Help text */ + +static const PCHAR helpinfo[] = { +"%s: make SIMH tape image", +"Synopsis: %s file ...", +" ", +"The tape image is written to standard output.", +" ", +"A block size of %2$d is assumed for each file. A different block size", +"may be specified by adding it to the end of the filename, separated by a", +"colon; e.g.:", +" stand:512", +"" +}; + + +/* + * Parse arguments and handle options. + * + */ + +INT main(INT argc, PCHAR argv[]) +{ BOOL rc = TRUE; + INT i, bs; + PCHAR p; + + progname = strrchr(argv[0], '/'); + if(progname == (PCHAR) NULL) progname = argv[0]; else progname++; + + if(argc == 1) { + error("at least one file must be specified", argv[0]); + putusage(); + exit(EXIT_FAILURE); + } + + for(i = 1; i < argc; i++) { + bs = DEFBLKSIZE; + p = strchr(argv[i], ':'); + if(p != (PCHAR) NULL) { + *p = '\0'; + bs = strtol(++p, (CHAR **) NULL, 10); + if(bs == 0) { + error("block size for file '%s' is invalid", + argv[i]); + rc = FALSE; + break; + } + } + write_file(argv[i], bs); + tape_mark(); + } + + tape_mark(); + + return(rc == TRUE ? EXIT_SUCCESS : EXIT_FAILURE); +} + + +/* + * Write a file to the 'tape' on stdout. + * + * Inputs: + * file name of file to be written + * bs blocksize to be used + * + * Outputs: + * TRUE written OK + * FALSE write (or other) error + * + * Blocks are rounded up to and even number of bytes, padded with a + * zero byte. + * Each block is preceded and followed by a 4 byte count, the actual + * block data size, in little endian format. + * + */ + +static BOOL write_file(PCHAR file, INT bs) +{ BOOL rc = TRUE; + INT n; + INT obs; + INT le_bs; + UCHAR sbuf[4]; + FILE *fp; + PCHAR buf; + + obs = (bs+1) & ~1;/* Round up output block size to make even */ + le_bs = blksize(bs); /* Little endian true block size */ + sbuf[0] = (le_bs & 0x000000ff); + sbuf[1] = (le_bs & 0x0000ff00) >> 8; + sbuf[2] = (le_bs & 0x00ff0000) >> 16; + sbuf[3] = (le_bs & 0xff000000) >> 24; + fprintf(stderr, "Writing file %s with block size %d %s\n", + file, obs, bs == obs ? "": "(rounded up)"); + + fp = fopen(file, "r"); + if(fp == (FILE *) NULL) { + error("cannot open file '%s'", file); + return(FALSE); + } + + buf = (PCHAR) malloc(obs); + if(buf == (PCHAR) NULL) { + error("cannot allocate memory for buffer"); + return(FALSE); + } + + for(;;) { + n = fread(buf, 1, bs, fp); + if(n < bs) { + if(!feof(fp)) { + error("error reading file '%s'", file); + rc = FALSE; + break; + } + if(n == 0) break; /* No partial buffer */ + memset(&buf[n], '\0', bs-n); /* Zero fill */ + } + if(bs != obs) buf[obs-1] = '\0'; /* Pad */ + n = fwrite(sbuf, 1, 4, stdout); /* Record header */ + if(n != 4) { + error("Error writing to tape image"); + rc = FALSE; + break; + } + n = fwrite(buf, 1, obs, stdout);/* Record data */ + if(n < obs) { + error("Error writing to tape image"); + rc = FALSE; + break; + } + n = fwrite(sbuf, 1, 4, stdout); /* Record trailer */ + if(n != 4) { + error("Error writing to tape image"); + rc = FALSE; + break; + } + } + + free((PCHAR) buf); + fclose(fp); + return(rc); +} + + +/* + * Write a tape mark to the tape image. This consists of four + * consecutive zero bytes. + * + * Inputs: + * none + * + * Outputs: + * TRUE written OK + * FALSE write (or other) error + * + */ + +static BOOL tape_mark(VOID) +{ INT n; + UCHAR sbuf[4] = { 0,0,0,0 }; + + n = fwrite(sbuf, 1, 4, stdout); + if(n != 4) { + error("Error writing to tape image"); + return(FALSE); + } + + return(TRUE); +} + + +/* + * Return a 4 byte block size, in little endian format. + * The input value is never negative. + * + * Inputs: + * mbs block size in machine format + * + * Outputs: + * lbs block size in little endian format + * + */ + +static INT blksize(INT mbs) +{ UINT result = 0; + + if(little_endian()) return(mbs); + + result |= (mbs & 0x000000ff) << 24; + result |= (mbs & 0x0000ff00) << 8; + result |= (mbs & 0x00ff0000) >> 8; + result |= (mbs & 0xff000000) >> 24; + return(result); +} + + +/* + * Check whether we are on a big endian or little endian machine. + * + * Inputs: + * none + * + * Outputs: + * 0 Big endian + * 1 Little endian + * + */ + +static INT little_endian(VOID) +{ UINT i = 1; + PCHAR c = (PCHAR) &i; + + return (INT) *c; +} + + +/* + * Print message on standard error in printf style, + * accompanied by program name. + * + * Inputs: + * mes message to be printed + * ... optional arguments + * + * Outputs: + * none + * + */ + +static VOID error(PCHAR mes, ...) +{ va_list ap; + + fprintf(stderr, "%s: ", progname); + + va_start(ap, mes); + vfprintf(stderr, mes, ap); + va_end(ap); + + fputc('\n', stderr); +} + + +/* + * Output program usage information. + * + * Inputs: + * none + * + * Outputs: + * none + * + */ + +static VOID putusage(VOID) +{ PCHAR *p = (PCHAR *) helpinfo; + PCHAR q; + + for(;;) { + q = *p++; + if(*q == '\0') break; + + fprintf(stderr, q, progname, DEFBLKSIZE); + fputc('\n', stderr); + } + fprintf(stderr, "\nThis is version %d.%d\n", VERSION, EDIT); +} + +/* + * End of file: mksimtape.c + * + */ + diff --git a/converters/mksimtape/mksimtape.txt b/converters/mksimtape/mksimtape.txt new file mode 100644 index 0000000..d4e60bb --- /dev/null +++ b/converters/mksimtape/mksimtape.txt @@ -0,0 +1,28 @@ +mksimtape +--------- + +This program generates a SIMH-compatible tape image file, from one or +more input files. It should be easily compiled using any reasonable C +compiler, for example: + + cc -o mksimtape mksimtape.c + +Once compiled, it is run by giving it the names of the files to be +written to the image, in the same order that they should be written. The +tape image is written to standard output, so a typical use might be +this: + + mksimtape file1 file2 file3 > tape.out + +Each 'file' on the tape is written with a block size of 10240 bytes, +unless otherwise specified. To use a particular block size for a file, +append it to the end of the filename, separated by a colon, viz.: + + mksimtape file1:512 file2 file3 > tape.out + +This would write 'file1' with a block size of 512 bytes; 'file2' and +'file3' would still be written with a block size of 10240 bytes. + +Bob Eager +bob@eager.cx + diff --git a/converters/tar2mt/tar2mt.c b/converters/tar2mt/tar2mt.c index 2edce66..64be3e9 100644 --- a/converters/tar2mt/tar2mt.c +++ b/converters/tar2mt/tar2mt.c @@ -44,7 +44,7 @@ if (argc < 2) { fprintf (stderr, "blocksize defaults to 8192\n"); exit (0); } -if ((argc >= 3) && ((strcmp("-b", argv[1])) || (strcmp("--blocksize", argv[1])))) { +if ((argc >= 3) && ((strcmp("-b", argv[1]) == 0) || (strcmp("--blocksize", argv[1]) == 0))) { if (atoi (argv[2]) <= 0) { fprintf (stderr, "Invalid blocksize: %s\n", argv[2]); exit (0); diff --git a/crossassemblers/Makefile b/crossassemblers/Makefile index 18cf775..5d70b70 100644 --- a/crossassemblers/Makefile +++ b/crossassemblers/Makefile @@ -5,29 +5,17 @@ BIN=/usr/local/bin INSTALL=install CC=gcc +SUBDIRS=hpasm macro1 macro11 macro7 macro8x + .PHONY: all clean install uninstall # Omitted: macro11: needs more complicated Makefiles. all: - cd hpasm && $(MAKE) CFLAGS="$(CFLAGS)" BIN="$(BIN)" INSTALL="$(INSTALL)" CC="$(CC)" - cd macro1 && $(MAKE) CFLAGS="$(CFLAGS)" BIN="$(BIN)" INSTALL="$(INSTALL)" CC="$(CC)" - cd macro7 && $(MAKE) CFLAGS="$(CFLAGS)" BIN="$(BIN)" INSTALL="$(INSTALL)" CC="$(CC)" - cd macro8x && $(MAKE) CFLAGS="$(CFLAGS)" BIN="$(BIN)" INSTALL="$(INSTALL)" CC="$(CC)" + for subdir in $(SUBDIRS); do \ + $(MAKE) -C $$subdir CFLAGS="$(CFLAGS)" BIN="$(BIN)" INSTALL="$(INSTALL)" CC="$(CC)"; \ + done -clean: - cd hpasm && $(MAKE) CFLAGS="$(CFLAGS)" BIN="$(BIN)" INSTALL="$(INSTALL)" CC="$(CC)" clean - cd macro1 && $(MAKE) CFLAGS="$(CFLAGS)" BIN="$(BIN)" INSTALL="$(INSTALL)" CC="$(CC)" clean - cd macro7 && $(MAKE) CFLAGS="$(CFLAGS)" BIN="$(BIN)" INSTALL="$(INSTALL)" CC="$(CC)" clean - cd macro8x && $(MAKE) CFLAGS="$(CFLAGS)" BIN="$(BIN)" INSTALL="$(INSTALL)" CC="$(CC)" clean - -install: - cd hpasm && $(MAKE) CFLAGS="$(CFLAGS)" BIN="$(BIN)" INSTALL="$(INSTALL)" CC="$(CC)" install - cd macro1 && $(MAKE) CFLAGS="$(CFLAGS)" BIN="$(BIN)" INSTALL="$(INSTALL)" CC="$(CC)" install - cd macro7 && $(MAKE) CFLAGS="$(CFLAGS)" BIN="$(BIN)" INSTALL="$(INSTALL)" CC="$(CC)" install - cd macro8x && $(MAKE) CFLAGS="$(CFLAGS)" BIN="$(BIN)" INSTALL="$(INSTALL)" CC="$(CC)" install - -uninstall: - cd hpasm && $(MAKE) CFLAGS="$(CFLAGS)" BIN="$(BIN)" INSTALL="$(INSTALL)" CC="$(CC)" uninstall - cd macro1 && $(MAKE) CFLAGS="$(CFLAGS)" BIN="$(BIN)" INSTALL="$(INSTALL)" CC="$(CC)" uninstall - cd macro7 && $(MAKE) CFLAGS="$(CFLAGS)" BIN="$(BIN)" INSTALL="$(INSTALL)" CC="$(CC)" uninstall - cd macro8x && $(MAKE) CFLAGS="$(CFLAGS)" BIN="$(BIN)" INSTALL="$(INSTALL)" CC="$(CC)" uninstall +clean install uninstall: + for subdir in $(SUBDIRS); do \ + $(MAKE) -C $$subdir CFLAGS="$(CFLAGS)" BIN="$(BIN)" INSTALL="$(INSTALL)" CC="$(CC)" $@; \ + done diff --git a/crossassemblers/macro11/.gitignore b/crossassemblers/macro11/.gitignore new file mode 100644 index 0000000..6c9e6c9 --- /dev/null +++ b/crossassemblers/macro11/.gitignore @@ -0,0 +1,11 @@ +git-info.h +*.d +*.o +dumpobj +macro11 +tests/*.lst +tests/*.obj +tests/*.objd +x/* +tests/2.11BSD/l11 +tests/2.11BSD-*.mac diff --git a/crossassemblers/macro11/.gitlab-ci.yml b/crossassemblers/macro11/.gitlab-ci.yml new file mode 100644 index 0000000..0ead23d --- /dev/null +++ b/crossassemblers/macro11/.gitlab-ci.yml @@ -0,0 +1,32 @@ +# Minimized from https://about.gitlab.com/2016/10/12/automated-debian-package-build-with-gitlab-ci/ +# https://github.com/gitlabhq/gitlabhq/blob/master/vendor/gitlab-ci-yml/C%2B%2B.gitlab-ci.yml + +# use the official gcc image, based on debian +# can use verions as well, like gcc:5.2 +# see https://hub.docker.com/_/gcc/ +#image: gcc + +before_script: + - apt-get update + +# Defines stages which are to be executed +stages: + - build-gcc + - build-clang + +# Stage "build" +run-build-gcc: + stage: build-gcc + script: + - apt-get install -y gcc + - make + - cd tests && ./RunTests + +# Stage "build-clang" +run-build-clang: + stage: build-clang + script: + - apt-get install -y clang + - CC=clang make + - cd tests && ./RunTests + diff --git a/crossassemblers/macro11/.indent.pro b/crossassemblers/macro11/.indent.pro new file mode 100755 index 0000000..ebdb836 --- /dev/null +++ b/crossassemblers/macro11/.indent.pro @@ -0,0 +1,45 @@ +--blank-lines-after-commas +--blank-lines-after-declarations +--blank-lines-after-procedures +--braces-on-if-line +--braces-on-struct-decl-line +--break-before-boolean-operator +--break-function-decl-args +--break-function-decl-args-end +--case-indentation0 +--comment-indentation40 +--continuation-indentation8 +--continue-at-parentheses +--cuddle-do-while +--cuddle-else +--declaration-indentation16 +--dont-break-procedure-type +--dont-format-comments +--indent-level4 +--honour-newlines +--leave-optional-blank-lines +--line-comments-indentation0 +--line-length112 +--no-blank-lines-before-block-comments +--no-space-after-function-call-names +--no-tabs +--paren-indentation4 +--space-special-semicolon +--tab-size8 +-T ADDR_MODE +-T ARG +-T BUFFER +-T EX_TREE +-T FILE +-T MACRO +-T MACRO_STREAM +-T MLB +-T MLBENT +-T STACK +-T STREAM +-T SECTION +-T SYMBOL +-T SYMBOL_ITER +-T SYMBOL_TABLE +-T TEXT_COMPLEX +-T TEXT_RLD diff --git a/crossassemblers/macro11/.travis.yml b/crossassemblers/macro11/.travis.yml new file mode 100644 index 0000000..70150df --- /dev/null +++ b/crossassemblers/macro11/.travis.yml @@ -0,0 +1,12 @@ +language: c +dist: bionic +os: + - linux + - osx +compiler: + - gcc + - clang +script: + - make + - cd tests + - ./RunTests diff --git a/crossassemblers/macro11/CHANGES b/crossassemblers/macro11/CHANGES new file mode 100644 index 0000000..0cc8d0b --- /dev/null +++ b/crossassemblers/macro11/CHANGES @@ -0,0 +1,137 @@ +07.07.2022: Rhialto + version 0.8: + - Improve parsing of symbols, e.g. `4..` is not a symbol. + - More careful file format checking of .MLB files, so that you can + use either RSX or RT format .MLB files, and autodetection works. + - Replace the floating point parser by a version which is based on + integer math only, because the Apple M1 doesn't have the required + long double support in its compiler. + 13 changes from Paul Koning that add some features which help to + assemble RSTS/E sources: + - Add default extensions to .include, .library + - Fix .psect without argument, add (ignored) pseudo ops .mdelete, + .cross, .nocross + - Allow TRAP without argument, or with non-literal argument + - Fix .library pseudo + - Always list lines that have an error + - Fix macro calls with omitted argument that has a default value, + followed by additional (not omitted) arguments. + - Implement .ENABLE MCL + - Treat unexpected .IF argument as true, not false. This appears to + be what the reference assembler does, at least it is necessary for + certain RSTS source files to assemble without error. + - Allow formal name of .IRP and .IRPC to be enclosed in < >. + - Just like .RAD50, .IDENT accepts an argument that can contain not + just delimited string characters but also values in < >. + - Allow expression (with constant value) in .RADIX + - Bugfixes in macro expansion, incl duplicate named arguments + - Fix references to blank section: Its name is empty, not ". BLK." + +05.01.2022: Rhialto + version 0.7.2: + - Improved error messages for bad addressing modes + (due to gitlab issue #10) + - Undefined symbols are now listed in the symbol table. + - The weird way the unary "operator" % works in expressions is + now implemented. + The docs on page 3-8/9 aren't very precise but testing showed + that any expression with a register in it can be used as a + register, and (if used to define a symbol) is listed in the + symbol table as such. %3+1 is <%3>+1 is R4. + - Fixed registers being used in .word or immediate operands: + no longer generated as being relocatable expressions. + - 'make tests' works more portably. + +16.05.2021: Rhialto + version 0.7.1: + - Fixed immediate source operand of LDEXP, LD[IL][FD] + which is an integer, unlike several other FPP instructions. + (gitlab issue #7) + - Start each pass with .DSABL LSB (gitlab issue #8) + +19.03.2021: Rhialto + version 0.7: + - Fixed the end-of-line check for MARK, EMT, TRAP. + - Left/right shift operator _; disabled when the -yus option is used. + Contributed by Stephen Casner. + - Added some missing instructions: LDCFD, CSM, TSTSET, WRTLCK. + +13.02.2021: Rhialto + version 0.6: + - Fixed rounding issues with floating point literals. + - Fixed FPP instructions which take fp immediate operands. + - Fixed bounds check on FPP AC0-3 operands. + - Fix listing to show a % before values that represent a register + number. + - Added checks for junk text following correct code. This revealed + some small other issues, now fixed. + - Added CIS instructions; as an extension, for the Inline variants + you can specify the descriptor addresses etc as arguments to + the instruction (much like an implied .word). + +25.04.2020: Rhialto + version 0.5: + - Fixed bug with checking addressing mode for JSR and bugs + with .REPT 0, unneeded relocation on pc-relative mode. + - Very simple .LIST and .NLIST implementation. + - Add ^pl and ^ph expressions from 2.11BSD's m11. + - Object-ified macro libraries with an eye to support the .sml + files from 2.11BSD's m11. But since the given file does not + just contain .MACROs (it even contains conditionals) I'm not + sure how it is supposed to work. + - Added 2.11BSD/m11 as test files. To make this reasonable, a + few small features they use have been recognized and ignored. + - Add -rsx and -rt11 to switch object file format (from Kevin Handy) + - Add obj2bin from https://github.com/AK6DN/obj2bin.git + +09.11.2015: Rhialto + version 0.4: + - Fixed various bugs. The most notable was extensive use- + after-free in the expression tree, which crashed on NetBSD but + apparently not on other systems. + - Lots of changes to make this MACRO11 more like the MACRO11 of + RSX-11M+ 4.6. I used Kermit-11 source files for comparison. + "The Manual" I'm refering to is + AA-KX10A-TC_PDP-11_MACRO-11_Reference_Manual_May88.pdf at + www.bitsavers.org/pdf/dec/pdp11/rsx11/RSX11Mplus_V4.x/4a/ and + I use an installed system to double-check. + +----------- Joerg Hoppe's entries ------------------ + +19.4.2009: JH + version 0.3 + - bugfix: Illegal labels and illegal opcodes are processed as + "implied .WORD" directives. + Expression errors in "do_word()" did not process any input character, + so parser did go into an endless loop. + - Switchable syntax extensions with -yxx options: + symbol len can be adjusted with "-ysl" command line option. + "-yus" option allows underscore "_" char in symbols. + This was needed to process code generated by my favorite disassembler. + - command line help added (-h option) + +17.4.2009: JH + version 0.3 + - ".INCLUDE" re-enabled + - refactoring: big 6000+ lines "macro11.c" split into 10 modules. + +15.4.2009: JH + Begin rework by Joerg Hoppe (j_hoppe@t-online.de) + All my changes are marked with "/*JH: .. */" comments + + +----------- Richard Krebiehls entries ------------------ + + +15-July-2001 + version 0.2 + removed references to snprintf from dumpobj.c and + mlb.c for portability + fixed a type cast warning in dumpobj.c compare_gsdlines + Removed strcasecmp from macro11.c for portability + Removed references to wnewmem.c from makefile (isn't needed) + makefile more compatible with non-gnu make and compiler + main prints version 0.2 + +14-July-2001 + First release, version 0.1. diff --git a/crossassemblers/macro11/license b/crossassemblers/macro11/LICENSE similarity index 100% rename from crossassemblers/macro11/license rename to crossassemblers/macro11/LICENSE diff --git a/crossassemblers/macro11/Makefile b/crossassemblers/macro11/Makefile new file mode 100644 index 0000000..5f7df01 --- /dev/null +++ b/crossassemblers/macro11/Makefile @@ -0,0 +1,87 @@ +##### +# +# Makefile for macro11 and dumpobj +# + +WARNS ?= -Wall -Wshadow -Wextra -pedantic -Woverflow -Wstrict-overflow +OBJFORMAT ?= -DDEFAULT_OBJECTFORMAT_RT11=0 +#SANITIZE ?= -fsanitize=address -fsanitize=undefined -fsanitize-recover=all -fno-omit-frame-pointer +DEBUG ?= -ggdb $(SANITIZE) +OPT ?= -O3 +CFLAGS ?= -std=gnu99 $(WARNS) $(DEBUG) $(OPT) $(OBJFORMAT) + +MACRO11_SRCS = macro11.c \ + assemble.c assemble_globals.c assemble_aux.c \ + extree.c listing.c macros.c parse.c rept_irpc.c symbols.c \ + mlb2.c mlb-rsx.c mlb-rt11.c object.c stream2.c util.c rad50.c + +MACRO11_OBJS = $(MACRO11_SRCS:.c=.o) + +DUMPOBJ_SRCS = dumpobj.c rad50.c + +DUMPOBJ_OBJS = $(DUMPOBJ_SRCS:.c=.o) + +ALL_SRCS = $(MACRO11_SRCS) $(DUMPOBJ_SRCS) + +all: macro11 dumpobj + +tags: macro11 dumpobj + ctags *.c *.h + +macro11: git-info.h $(MACRO11_OBJS) Makefile + $(CC) $(CFLAGS) -o macro11 $(MACRO11_OBJS) -lm + +dumpobj: $(DUMPOBJ_OBJS) Makefile + $(CC) $(CFLAGS) -o dumpobj $(DUMPOBJ_OBJS) + +$(MACRO11_OBJS): Makefile +$(DUMPOBJ_OBJS): Makefile + +git-info.h: + ./make-git-info + +# Bootstrap dependency on the git header file, which otherwise +# gets generated too late. +macro11.o: git-info.h +macro11.c: git-info.h + +clean: + -rm -f $(MACRO11_OBJS) $(DUMPOBJ_OBJS) macro11 dumpobj + -rm -f *.d + -rm -f git-info.h + +# Since the only tests we have so far are for crashes, +# just try to assemble. Later, we will need expected/actual tests. + +# Test that all options requiring a value bail out if it's not present. +argtests: macro11 + @ for OPT in -e -d -m -p -o -l -ysl ; do \ + ./macro11 foo.mac $$OPT 2> /dev/null; \ + if [ $$? = 1 ]; then echo PASS; else echo FAIL; fi; \ + echo " $$OPT missing value"; \ + ./macro11 foo.mac $$OPT -v 2> /dev/null; \ + if [ $$? = 1 ]; then echo PASS; else echo FAIL; fi; \ + echo " $$OPT fol. by option"; \ + done + @ ./macro11 foo.mac $$OPT -x -v 2> /dev/null; \ + if [ $$? = 1 ]; then echo PASS; else echo FAIL; fi; \ + echo " -x must be the last option" + +LSAN_OPTIONS=suppressions=../macro11.supp + +tests: macro11 argtests + cd tests && env LSAN_OPTIONS="${LSAN_OPTIONS}" ./RunTests + +# Automatic dependency generation + +ifneq ($(MAKECMDGOALS),clean) +-include $(ALL_SRCS:.c=.d) +endif + +# Make .d files as side effect of compiling .c to .o +%.d %.o: %.c + $(CC) $(CFLAGS) -c -o $*.o $< + @set -e; rm -f $*.d; \ + $(CC) -MM $(CPPFLAGS) $< > $@.$$$$; \ + sed 's,\($*\)\.o[ :]*,\1.o \1.d : ,g' < $@.$$$$ > $*.d; \ + rm -f $@.$$$$ diff --git a/crossassemblers/macro11/README b/crossassemblers/macro11/README new file mode 100644 index 0000000..f4be345 --- /dev/null +++ b/crossassemblers/macro11/README @@ -0,0 +1,187 @@ +A MACRO-11 assembler for the PDP-11 in portable C source code. + +Copyright (c) 2001, Richard Krehbiel +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + +o Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +o Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +o Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS +OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR +TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH +DAMAGE. + +modified 2009 by Joerg Hoppe, +modified 2015-2017,2020,2021 by Olaf 'Rhialto' Seibert. + +Files: + macro11.c Command line parsing and driving the passes. + assemble.c Handles all of the opcodes and directives. + assemble_aux.c Helper functions mostly called from assemble(). + assemble_globals.c Global variables for assemble(). + extree.c Expression tree: memory management and evaluation. + listing.c Listing generation. + macros.c Define macros and make them available as stream2s. + parse.c Basic parsing for numbers, labels, strings, expressions + rept_irpc.c stream2 subclass for processing .REPT and .IREPT. + object.c Functions for writing RSX-11 compatible .OBJ files. + mlb-rsx.c Classes (!) for reading RSX-11 macro libraries. + mlb-rt11.c Classes (!) for reading RT-11 macro libraries. + mlb2.c Glue functions for macro libraries in general. + stream2.c Functions for managing input streams and buffers. + rad50.c Functions for converting text to and from RAD50. + util.c A few general utility fuctions. + + macro11.h, object.h, mlb.h, stream2.h, rad50.h, util.h + types and symbols exported from the associated sources. + + dumpobj.c A program I wrote to examine the output from + RT-11's MACRO.SAV program and compare it with my + own output. + + Makefile A GNU makefile for Linux; simple enough, it + should be convertible to any Unix. + Contains automatic dependency generation. + + macro11.dsp, dumpobj.dsp, macro11.dsw + Visual Studio 6 projects. Out of date. + + README This file + + LICENSE The copyright notice and license + + CHANGES A list of changes from previous versions + + TODO A list of things that may need fixing + + +Notes: + +Sorry, I am a believer in 4 column hardware tabs, for a number of +reasons, mostly regarding editing convenience. (I did untabify this +README file though.) + +The bulk of Richard's development was done in Microsoft Visual Studio 6, +but currently Olaf maintains it using a Unix (NetBSD) system. I build +with gcc and a lot of warning options, and from time to time check with +a Linux system which has a different gcc version, and clang. The Visual +Studio project files are out of date. + +Richard used the MACRO11 from RT-11 as reference, but I use the one +from RSX-11M+. It turns out there are some small file format +differences. I used the dumpobj command to compare the output of this +macro11 with the reference version when assembling Kermit-11 source +files, and currently there seem to be no significant differences. + +The macro11 command line: + +macro11 [options...] files... + +Options: + -v Prints program version. + + -e opt .ENABL option. Implemented options are AMA, GBL, + and also .LIST options ME, BEX, and + MD, though the status of listing control is + presently very poor. + + -d opt .DSABL option; same options as -e. + + -m macname Gives a macro library name. + Up to 32 macro libraries may be specified, one per + -m option. + The current object file format (default, or from + -rt11 or -rsx before this option) is used first to + read the file; if this fails, the other format is + tried. + Note: unlike MACRO.SAV, SYSMAC.SML is not + automatically included; you must name it. + + -p macpath For any .MCALL directive, macro11 will + first search -m macro libraries, then it will + search the MCALL path for a file named .MAC + to locate the body of the macro. The MCALL path + is an environment variable containing directory + names separated by delimiters (":" for Unix-style + targets; ";" for Windows). The -p command line + options appends a directory name to the MCALL + path. + + -I incpath For any .INCLUDE directive, macro11 will + search the INCLUDE path for a file named + to include. The INCLUDE path works like the MCALL + path. If not specified at all, the default is the + current directory. If contains a drive + and/or directory in RSX/RT-11 form + (DEV:[DIR]FILE.EXT) then the search is also tried + without DEV: and without DEV:[DIR]. + + -o objname Gives the name of the object file. No extension + is assumed; if you want .OBJ you have to say it. + With no -o option, no object file is generated. + + -l lstname Gives the name of a listing file. The name "-" + may be given to write the listing to stdout. No + extension is assumed; if you want .LST you have to + say it. With no -l option, no listing file is + written. + + -x Tells macro11 not to assemble anything, but rather + to simply extract all the macros in all the -m + macro libraries into individual .MAC files in the + current directory. This should be the last option + given, as none following will be processed. + This also works for extracting an object library + (.OLB) file. + + -rsx Tells macro11 to generate rsx style object files. + This is the default. + + -rt11 Tells macro11 to generate rt11 style object files. + + Various options starting with -y enable extensions: + + -ysl Allow longer symbols up to the given length. + The maximal allowed value is 64. + + -yus Allow underscore in symbol names. + + -yl1 Include the processing of the first pass in the + listing file. This may be useful for finding + phase errors. + + files... Any number of input files. They will be assembled + as if they were concatenated together. + + +You may define the MCALL and INCLUDE environment variable prior to +invoking macro11, as a path to directories containing macros or files to +be included, respectively. + +======================================================================= + +Included in subdirectory obj2bin is a Perl script from Don North, +copied from https://github.com/AK6DN/obj2bin. The copied version may +get out of date; when in doubt check the original. + +======================================================================= diff --git a/crossassemblers/macro11/todo b/crossassemblers/macro11/TODO similarity index 94% rename from crossassemblers/macro11/todo rename to crossassemblers/macro11/TODO index d922637..9a514ec 100644 --- a/crossassemblers/macro11/todo +++ b/crossassemblers/macro11/TODO @@ -1,3 +1,10 @@ + .packed pdf page 80 + +listing format errors: ignore whitespace of input + +documentation: print supported directives + +--------------------------------------- I was not able to locate a Macro-11 language reference manual any more recent than for RT11 version *3*, so I used that plus my recollection of more modern features. It was enough to get the RT11 V5.4 kernel diff --git a/crossassemblers/macro11/assemble.c b/crossassemblers/macro11/assemble.c new file mode 100644 index 0000000..6d5bf59 --- /dev/null +++ b/crossassemblers/macro11/assemble.c @@ -0,0 +1,1878 @@ +#define ASSEMBLE__C + + +#include +#include + +#include "assemble.h" /* my own definitions */ + +#include "assemble_globals.h" +#include "assemble_aux.h" + +#include "util.h" + +#include "mlb.h" +#include "object.h" +#include "listing.h" +#include "parse.h" +#include "symbols.h" +#include "extree.h" +#include "macros.h" +#include "rept_irpc.h" + +#include "rad50.h" + + + + +#define CHECK_EOL check_eol(stack, cp) + +/* assemble a rad50 value (some number of words). */ +static unsigned * assemble_rad50 ( + char *cp, + int max, + int *count, + STACK *stack) +{ + char *radstr; + unsigned *ret; + int i, len, wcnt; + + /* + * Allocate storage sufficient for the rest of + * the line. + */ + radstr = memcheck(malloc(strlen(cp))); + len = 0; + + do { + cp = skipwhite(cp); + if (*cp == '<') { + EX_TREE *value; + /* A byte value */ + value = parse_unary_expr(cp, 0); + cp = value->cp; + if (value->type != EX_LIT) { + report(stack->top, "expression must be constant\n"); + radstr[len++] = 0; + } else if (value->data.lit >= 050) { + report(stack->top, "invalid character value %o\n", value->data.lit); + radstr[len++] = 0; + } else { + radstr[len++] = value->data.lit; + } + free_tree(value); + } else { + char quote = *cp++; + + while (*cp && *cp != '\n' && *cp != quote) { + int ch = ascii2rad50(*cp++); + + if (ch == -1) { + report(stack->top, "invalid character '%c'\n", cp[-1]); + radstr[len++] = 0; + } else { + radstr[len++] = ch; + } + + } + cp++; /* Skip closing quote */ + } + + cp = skipwhite(cp); + } while (!EOL(*cp)); + + wcnt = (len + 2) / 3; + /* Return at most "max" words, if specified */ + if (max && max < wcnt) + wcnt = max; + if (count != NULL) + *count = wcnt; + + /* Allocate space for actual or max words, whichever is larger */ + ret = memcheck (malloc (((wcnt < max) ? max : wcnt) * sizeof (int))); + for (i = 0; i < wcnt; i++) { + int word = packrad50word(radstr + i * 3, len - (i * 3)); + ret[i] = word; + } + /* If max is specified, zero fill */ + for (; i < max; i++) + ret[i] = 0; + + free(radstr); + return ret; +} + +/* assemble - read a line from the input stack, assemble it. */ + +/* This function is way way too large, because I just coded most of + the operation code and pseudo-op handling right in line. */ + +static int assemble( + STACK *stack, + TEXT_RLD *tr) +{ + char *cp; /* Parse character pointer */ + char *opcp; /* Points to operation mnemonic text */ + char *ncp; /* "next" cp */ + char *label; /* A label */ + char *line; /* The whole line */ + SYMBOL *op; /* The operation SYMBOL */ + int local; /* Whether a label is a local label or + not */ + + line = stack_getline(stack); + if (line == NULL) + return -1; /* Return code for EOF. */ + + if (!enabl_lc) { /* If lower case disabled, */ + upcase(line); /* turn it into upper case. */ + } + + cp = line; + + /* Frankly, I don't need to keep "line." But I found it quite + handy during debugging, to see what the whole operation was, + when I'm down to parsing the second operand and things aren't + going right. */ + + stmtno++; /* Increment statement number */ + + list_source(stack->top, line); /* List source */ + + if (suppressed) { + /* Assembly is suppressed by unsatisfied conditional. Look + for ending and enabling statements. */ + + op = get_op(cp, &cp); /* Look at operation code */ + + /* FIXME: this code will blindly look into .REM commentary and + find operation codes. Incidentally, so will read_body(). + + That doesn't really matter, though, since the original also + did that (line 72 ends the suppressed conditional block): + + 69 .if NE,0 + 70 .rem & + 71 junk + 72 .endc +A 73 000144 000000G 000000G more junk +A 74 000150 000000G 000000G 000000G line that ends the comments with & + 000156 000000G 000000G 000000C +O 75 .endc + */ + + if (op == NULL) + return 1; /* Not found. Don't care. */ + if (op->section->type != SECTION_PSEUDO) + return 1; /* Not a pseudo-op. */ + switch (op->value) { + case P_IF: + case P_IFDF: + suppressed++; /* Nested. Suppressed. */ + break; + case P_IFTF: + if (suppressed == 1) /* Reduce suppression from 1 to 0. */ + suppressed = 0; + break; + case P_IFF: + if (suppressed == 1) { /* Can reduce suppression from 1 to 0. */ + if (!conds[last_cond].ok) + suppressed = 0; + } + break; + case P_IFT: + if (suppressed == 1) { /* Can reduce suppression from 1 to 0. */ + if (conds[last_cond].ok) + suppressed = 0; + } + break; + case P_ENDC: + suppressed--; /* Un-nested. */ + if (suppressed == 0) + pop_cond(last_cond - 1); /* Re-enabled. */ + break; + } + return 1; + } + + /* The line may begin with "label:[:]" */ + + /* PSEUDO P_IIF jumps here. */ + reassemble: + opcp = cp; + if ((label = get_symbol(cp, &ncp, &local)) != NULL) { + int flag = SYMBOLFLAG_PERMANENT | SYMBOLFLAG_DEFINITION | local; + SYMBOL *sym; + + ncp = skipwhite(ncp); + if (*ncp == ':') { /* Colon, for symbol definition? */ + ncp++; + /* maybe it's a global definition */ + if (*ncp == ':') { + flag |= SYMBOLFLAG_GLOBAL; /* Yes, include global flag */ + ncp++; + } + + sym = add_sym(label, DOT, flag, current_pc->section, &symbol_st); + cp = ncp; + + if (sym == NULL) + report(stack->top, "Illegal symbol definition %s\n", label); + + free(label); + + /* See if local symbol block should be incremented */ + if (!enabl_lsb && !local) { + lsb = get_next_lsb(); + } + + cp = skipwhite(ncp); + opcp = cp; + label = get_symbol(cp, &ncp, NULL); /* Now, get what follows */ + } + } + + cp = skipwhite(cp); + + if (EOL(*cp)) + return 1; /* It's commentary. All done. */ + + if (label) { /* Something looks like a label. */ + /* detect assignment */ + + ncp = skipwhite(ncp); /* The pointer to the text that + follows the symbol */ + + if (*ncp == '=') { + unsigned flags; + EX_TREE *value; + SYMBOL *sym; + + cp = ncp; + + /* Symbol assignment. */ + + flags = SYMBOLFLAG_DEFINITION | local; + cp++; + if (*cp == '=') { + flags |= SYMBOLFLAG_GLOBAL; /* Global definition */ + cp++; + } + if (*cp == ':') { + flags |= SYMBOLFLAG_PERMANENT; + cp++; + } + + cp = skipwhite(cp); + + value = parse_expr(cp, 0); + cp = value->cp; + + /* Special code: if the symbol is the program counter, + this is harder. */ + + if (strcmp(label, ".") == 0) { + if (current_pc->section->flags & PSECT_REL) { + SYMBOL *symb; + unsigned offset; + + /* Express the given expression as a symbol and an + offset. The symbol must not be global, the + section must = current. */ + + if (!express_sym_offset(value, &symb, &offset)) { + report(stack->top, "Illegal ORG (for relocatable section)\n"); + } else if (SYM_IS_IMPORTED(symb)) { + report(stack->top, "Can't ORG to external location\n"); + } else if (symb->flags & SYMBOLFLAG_UNDEFINED) { + report(stack->top, "Can't ORG to undefined sym\n"); + } else if (symb->section != current_pc->section) { + report(stack->top, "Can't ORG to alternate section (use PSECT)\n"); + } else { + DOT = symb->value + offset; + list_value(stack->top, DOT); + change_dot(tr, 0); + } + } else { + /* If the current section is absolute, the value + must be a literal */ + if (value->type != EX_LIT) { + report(stack->top, "Can't ORG to non-absolute location\n"); + free_tree(value); + free(label); + return 0; + } + DOT = value->data.lit; + list_value(stack->top, DOT); + change_dot(tr, 0); + } + free_tree(value); + free(label); + return CHECK_EOL; + } + + /* regular symbols */ + if (value->type == EX_LIT) { + sym = add_sym(label, value->data.lit, flags, &absolute_section, &symbol_st); + } else if (value->type == EX_SYM || value->type == EX_TEMP_SYM) { + sym = add_sym(label, value->data.symbol->value, flags, value->data.symbol->section, &symbol_st); + } else { + report(stack->top, "Complex expression cannot be assigned to a symbol\n"); + + if (!pass) { + /* This may work better in pass 2 - something in + RT-11 monitor needs the symbol to apear to be + defined even if I can't resolve its value. */ + sym = add_sym(label, 0, SYMBOLFLAG_UNDEFINED, &absolute_section, &symbol_st); + } else + sym = NULL; + } + + if (sym != NULL) + list_value(stack->top, sym->value); + + free_tree(value); + free(label); + + return sym != NULL && CHECK_EOL; + } + + /* Try to resolve macro */ + +do_mcalled_macro: + op = lookup_sym(label, ¯o_st); + if (op /*&& op->stmtno < stmtno*/) { + STREAM *macstr; + + free(label); + + list_location(stack->top, DOT); + + macstr = expandmacro(stack->top, (MACRO *) op, ncp); + if (macstr == NULL) { + /* Error in expanding the macro, stop now. */ + return 0; + } + + stack_push(stack, macstr); /* Push macro expansion + onto input stream */ + + return 1; + } + + /* Try to resolve instruction or pseudo */ + op = lookup_sym(label, &system_st); + if (op) { + cp = ncp; + + free(label); /* Don't need this hanging around anymore */ + + switch (op->section->type) { + case SECTION_PSEUDO: + switch (op->value) { + case P_PAGE: + case P_PRINT: + case P_SBTTL: + case P_CROSS: + case P_NOCROSS: + return 1; /* Accepted, ignored. (An obvious + need: get assembly listing + controls working fully. ) */ + case P_LIST: + if (pass > 0) { + cp = skipwhite(cp); + if (EOL(*cp)) + list_level++; + } + return 1; + case P_NLIST: + if (pass > 0) { + cp = skipwhite(cp); + if (EOL(*cp)) + list_level--; + } + return 1; + + case P_IDENT: + if (ident) /* An existing ident? */ + free(ident); /* Discard it. */ + + ident = assemble_rad50 (cp, 2, NULL, stack); + return 1; + + case P_RADIX: + { + int old_radix = radix; + EX_TREE *value; + int ok = 1; + + cp = skipwhite(cp); + if (EOL(*cp)) { + /* If no argument, assume 8 */ + radix = 8; + return 1; + } + /* Parse the argument in decimal radix */ + radix = 10; + value = parse_expr(cp, 0); + cp = value->cp; + + if (value->type != EX_LIT) { + report(stack->top, "Argument to .RADIX must be constant\n"); + radix = old_radix; + ok = 0; + } else { + radix = value->data.lit; + list_value(stack->top, radix); + if (radix != 8 && radix != 10 && + radix != 2 && radix != 16) { + radix = old_radix; + report(stack->top, "Argument to .RADIX must be 2, 8, 10, or 16\n"); + ok = 0; + } + } + free_tree(value); + return ok && CHECK_EOL; + } + + case P_FLT4: + case P_FLT2: + { + int ok = 1; + + while (ok && !EOL(*cp)) { + unsigned flt[4]; + + if (parse_float(cp, &cp, (op->value == P_FLT4 ? 4 : 2), flt)) { + /* All is well */ + } else { + report(stack->top, "Bad floating point format\n"); + ok = 0; /* Don't try to parse the rest of the line */ + flt[0] = flt[1] /* Store zeroes */ + = flt[2] + = flt[3] = 0; + } + /* Store the word values */ + store_word(stack->top, tr, 2, flt[0]); + store_word(stack->top, tr, 2, flt[1]); + if (op->value == P_FLT4) { + store_word(stack->top, tr, 2, flt[2]); + store_word(stack->top, tr, 2, flt[3]); + } + cp = skipdelim(cp); + } + return ok && CHECK_EOL; + } + + case P_ERROR: + report(stack->top, "%.*s\n", strcspn(cp, "\n"), cp); + return 0; + + case P_SAVE: + if (sect_sp >= SECT_STACK_SIZE - 1) { + report(stack->top, "Too many saved sections for .SAVE\n"); + return 0; + } + sect_sp++; + sect_stack[sect_sp] = current_pc->section; + dot_stack[sect_sp] = DOT; + return CHECK_EOL; + + case P_RESTORE: + if (sect_sp < 0) { + report(stack->top, "No saved section for .RESTORE\n"); + return 0; + } else { + go_section(tr, sect_stack[sect_sp]); + DOT = dot_stack[sect_sp]; + list_location(stack->top, DOT); + if (!enabl_lsb) { + lsb = get_next_lsb(); + } + sect_sp--; + } + return CHECK_EOL; + + case P_NARG: + { + STREAM *str; + MACRO_STREAM *mstr; + int islocal; + + label = get_symbol(cp, &cp, &islocal); + + if (label == NULL) { + report(stack->top, "Bad .NARG syntax\n"); + return 0; + } + + /* Walk up the stream stack to find the + topmost macro stream */ + for (str = stack->top; str != NULL && str->vtbl != ¯o_stream_vtbl; + str = str->next) ; + + if (!str) { + report(str, ".NARG not within macro expansion\n"); + free(label); + return 0; + } + + mstr = (MACRO_STREAM *) str; + + add_sym(label, mstr->nargs, SYMBOLFLAG_DEFINITION | islocal, &absolute_section, + &symbol_st); + free(label); + list_value(stack->top, mstr->nargs); + return CHECK_EOL; + } + + case P_NCHR: + { + char *string; + int islocal; + + label = get_symbol(cp, &cp, &islocal); + + if (label == NULL) { + report(stack->top, "Bad .NCHR syntax\n"); + return 0; + } + + cp = skipdelim(cp); + + string = getstring(cp, &cp); + + add_sym(label, strlen(string), SYMBOLFLAG_DEFINITION | islocal, &absolute_section, + &symbol_st); + free(label); + free(string); + return CHECK_EOL; + } + + case P_NTYPE: + { + ADDR_MODE mode; + int islocal; + char *error; + + label = get_symbol(cp, &cp, &islocal); + if (label == NULL) { + report(stack->top, "Bad .NTYPE syntax\n"); + return 0; + } + + cp = skipdelim(cp); + + if (!get_mode(cp, &cp, &mode, &error)) { + report(stack->top, + "Bad .NTYPE addressing mode (%s)\n", error); + free(label); + return 0; + } + + add_sym(label, mode.type, SYMBOLFLAG_DEFINITION | islocal, &absolute_section, &symbol_st); + free_addr_mode(&mode); + free(label); + + return CHECK_EOL; + } + + case P_INCLUDE: + { + char *name = getstring_fn(cp, &cp); + STREAM *incl; + char hitfile[FILENAME_MAX]; + + if (name == NULL) { + report(stack->top, "Bad .INCLUDE file name\n"); + return 0; + } + + name = defext (name, "MAC"); + my_searchenv(name, "INCLUDE", hitfile, sizeof(hitfile)); + + if (hitfile[0] == '\0') { + report(stack->top, "Unable to find .INCLUDE file \"%s\"\n", name); + free(name); + return 0; + } + + free(name); + + incl = new_file_stream(hitfile); + if (incl == NULL) { + report(stack->top, "Unable to open .INCLUDE file \"%s\"\n", hitfile); + return 0; + } + + stack_push(stack, incl); + + return CHECK_EOL; + } + + case P_REM: + { + char quote[4]; + + /* Read and discard lines until one with a + closing quote */ + + cp = skipwhite(cp); + quote[0] = *cp++; + quote[1] = '\n'; + quote[2] = 0; + + for (;;) { + cp += strcspn(cp, quote); + if (*cp == quote[0]) + break; /* Found closing quote */ + cp = stack_getline(stack); /* Read next input line */ + if (cp == NULL) + break; /* EOF */ + } + } + return 1; + + case P_IRP: + { + STREAM *str = expand_irp(stack, cp); + + if (str) + stack_push(stack, str); + return str != NULL; + } + + case P_IRPC: + { + STREAM *str = expand_irpc(stack, cp); + + if (str) + stack_push(stack, str); + return str != NULL; + } + + case P_LIBRARY: + { + char hitfile[FILENAME_MAX]; + char *name = getstring_fn(cp, &cp); + + name = defext (name, "MLB"); + my_searchenv(name, "MCALL", hitfile, sizeof(hitfile)); + + if (hitfile[0]) { + mlbs[nr_mlbs] = mlb_open(hitfile, 0); + if (mlbs[nr_mlbs] == NULL) { + report(stack->top, "Unable to register macro library \"%s\"\n", hitfile); + } else { + nr_mlbs++; + } + } else { + report(stack->top, "Unable to locate macro library \"%s\"\n", name); + } + free(name); + } + return CHECK_EOL; + + case P_MCALL: + { + for (;;) { + cp = skipdelim(cp); + + if (EOL(*cp)) + return 1; + + /* (lib)macro syntax. Ignore (lib) for now. */ + if (*cp == '(') { + char *close = strchr(cp + 1, ')'); + + if (close != NULL) { + char *libname = cp + 1; + (void)libname; + *close = '\0'; + cp = close + 1; + } + } + + label = get_symbol(cp, &cp, NULL); + if (!label) { + report(stack->top, "Illegal .MCALL format\n"); + return 0; + } + + /* See if that macro's already defined */ + if (lookup_sym(label, ¯o_st)) { + free(label); /* Macro already + registered. No + prob. */ + cp = skipdelim(cp); + continue; + } + + /* Do the actual macro library search */ + if (!do_mcall (label, stack)) + report(stack->top, "MACRO %s not found\n", label); + + free(label); + } + /* NOTREACHED */ + } + return 1; + + case P_MACRO: + { + MACRO *mac = defmacro(cp, stack, CALLED_NORMAL); + + return mac != NULL; + } + + case P_MDELETE: + return 1; /* TODO: or should it just be a NOP? */ + + case P_MEXIT: + { + STREAM *macstr; + + /* Pop a stream from the input. */ + /* It must be the first stream, and it must be */ + /* a macro, rept, irp, or irpc. */ + macstr = stack->top; + if (macstr->vtbl != ¯o_stream_vtbl && macstr->vtbl != &rept_stream_vtbl + && macstr->vtbl != &irp_stream_vtbl && macstr->vtbl != &irpc_stream_vtbl) { + report(stack->top, ".MEXIT not within a macro\n"); + return 0; + } + + /* and finally, pop the macro */ + stack_pop(stack); + + return CHECK_EOL; + } + + case P_REPT: + { + STREAM *reptstr = expand_rept(stack, cp); + + if (reptstr) + stack_push(stack, reptstr); + return reptstr != NULL; + } + + case P_ENABL: + /* FIXME - add all the rest of the options. */ + while (!EOL(*cp)) { + label = get_symbol(cp, &cp, NULL); + if (strcmp(label, "AMA") == 0) + enabl_ama = 1; + else if (strcmp(label, "LSB") == 0) { + enabl_lsb = 1; + lsb = get_next_lsb(); + } else if (strcmp(label, "GBL") == 0) { + enabl_gbl = 1; + } else if (strcmp(label, "LC") == 0) { + enabl_lc = 1; + } else if (strcmp(label, "LCM") == 0) { + enabl_lcm = 1; + } else if (strcmp(label, "MCL") == 0) { + enabl_mcl = 1; + } + free(label); + cp = skipdelim(cp); + } + return 1; + + case P_DSABL: + /* FIXME Ditto as for .ENABL */ + while (!EOL(*cp)) { + label = get_symbol(cp, &cp, NULL); + if (strcmp(label, "AMA") == 0) + enabl_ama = 0; + else if (strcmp(label, "LSB") == 0) { + lsb = get_next_lsb(); + enabl_lsb = 0; + } else if (strcmp(label, "GBL") == 0) { + enabl_gbl = 0; + } else if (strcmp(label, "LC") == 0) { + enabl_lc = 0; + } else if (strcmp(label, "LCM") == 0) { + enabl_lcm = 0; + } else if (strcmp(label, "MCL") == 0) { + enabl_mcl = 0; + } + free(label); + cp = skipdelim(cp); + } + return 1; + + case P_LIMIT: + store_limits(stack->top, tr); + return CHECK_EOL; + + case P_TITLE: + /* accquire module name */ + if (module_name != NULL) { + free(module_name); + } + module_name = get_symbol(cp, &cp, NULL); + return 1; + + case P_END: + /* Accquire transfer address */ + cp = skipwhite(cp); + if (!EOL(*cp)) { + if (xfer_address) + free_tree(xfer_address); + xfer_address = parse_expr(cp, 0); + cp = xfer_address->cp; + } + return CHECK_EOL; + + case P_IFDF: + opcp = skipwhite(opcp); + cp = opcp + 3; /* Point cp at the "DF" or + "NDF" part */ + /* FALLS THROUGH */ + case P_IIF: + case P_IF: + { + EX_TREE *value; + int ok = FALSE; + + label = get_symbol(cp, &cp, NULL); /* Get condition */ + cp = skipdelim(cp); + + if (!label) { + report(stack->top, "Missing .(I)IF condition\n"); + } else if (strcmp(label, "DF") == 0) { + value = parse_expr(cp, EVALUATE_DEFINEDNESS); + cp = value->cp; + ok = eval_defined(value); + free_tree(value); + } else if (strcmp(label, "NDF") == 0) { + value = parse_expr(cp, EVALUATE_DEFINEDNESS); + cp = value->cp; + ok = eval_undefined(value); + free_tree(value); + } else if (strcmp(label, "B") == 0 || + strcmp(label, "NB") == 0) { + /* + * Page 6-46 footnote 1 says + * "A macro argument (a form of symbolic argument) + * is enclosed within angle brackets or delimited + * by the circumflex construction, as described in + * section 7.3. For example, + * + * ^/124/" + * but we don't enforce that here (yet) by using + * simply getstring(). + */ + cp = skipwhite(cp); + if (EOL(*cp)) { + ok = 1; + } else { + char *thing, *end; + + thing = getstring(cp, &cp); + end = skipwhite(thing); + ok = (*end == 0); + free(thing); + } + if (label[0] == 'N') { + ok = !ok; + } + } else if (strcmp(label, "IDN") == 0 || + strcmp(label, "DIF") == 0) { + char *thing1, + *thing2; + + thing1 = getstring(cp, &cp); + cp = skipdelim(cp); + if (!EOL(*cp)) + thing2 = getstring(cp, &cp); + else + thing2 = memcheck(strdup("")); + + if (!enabl_lcm) { + upcase(thing1); + upcase(thing2); + } + + ok = (strcmp(thing1, thing2) == 0); + if (label[0] == 'D') { + ok = !ok; + } + free(thing1); + free(thing2); + } else if (strcmp(label, "P1") == 0) { + ok = (pass == 0); + } else if (strcmp(label, "P2") == 0) { + ok = (pass == 1); + } else { + int sword; + unsigned uword; + EX_TREE *tvalue = parse_expr(cp, 0); + + cp = tvalue->cp; + + if (tvalue->type != EX_LIT) { + report(stack->top, "Bad .IF expression\n"); + list_value(stack->top, 0); + free_tree(tvalue); + ok = TRUE; /* Pick something. */ + } else { + unsigned word; + + /* Convert to signed and unsigned words */ + sword = tvalue->data.lit & 0x7fff; + + /* FIXME I don't know if the following + is portable enough. */ + if (tvalue->data.lit & 0x8000) + sword |= ~0x7FFF; /* Render negative */ + + /* Reduce unsigned value to 16 bits */ + uword = tvalue->data.lit & 0xffff; + + if (strcmp(label, "EQ") == 0 || strcmp(label, "Z") == 0) + ok = (uword == 0), word = uword; + else if (strcmp(label, "NE") == 0 || strcmp(label, "NZ") == 0) + ok = (uword != 0), word = uword; + else if (strcmp(label, "GT") == 0 || strcmp(label, "G") == 0) + ok = (sword > 0), word = sword; + else if (strcmp(label, "GE") == 0) + ok = (sword >= 0), word = sword; + else if (strcmp(label, "LT") == 0 || strcmp(label, "L") == 0) + ok = (sword < 0), word = sword; + else if (strcmp(label, "LE") == 0) + ok = (sword <= 0), word = sword; + else + ok = 0, word = 0; + + list_value(stack->top, word); + + free_tree(tvalue); + } + } + + free(label); + + if (op->value == P_IIF) { + stmtno++; /* the second half is a + separate statement */ + if (ok) { + /* The "immediate if" */ + /* Only slightly tricky. */ + cp = skipdelim(cp); + goto reassemble; + } + return 1; /* Ignore rest of line if + condition is false */ + } + + push_cond(ok, stack->top); + + if (!ok) + suppressed++; /* Assembly + suppressed + until .ENDC */ + } + return CHECK_EOL; + + case P_IFF: + if (last_cond < 0) { + report(stack->top, "No conditional block active\n"); + return 0; + } + if (conds[last_cond].ok) /* Suppress if last cond + is true */ + suppressed++; + return CHECK_EOL; + + case P_IFT: + if (last_cond < 0) { + report(stack->top, "No conditional block active\n"); + return 0; + } + if (!conds[last_cond].ok) /* Suppress if last cond + is false */ + suppressed++; + return CHECK_EOL; + + case P_IFTF: + if (last_cond < 0) { + report(stack->top, "No conditional block active\n"); + return 0; + } + return CHECK_EOL; /* Don't suppress. */ + + case P_ENDC: + if (last_cond < 0) { + report(stack->top, "No conditional block active\n"); + return 0; + } + + pop_cond(last_cond - 1); + return CHECK_EOL; + + case P_ENDM: + report(stack->top, "No macro definition block active\n"); + return 0; + + case P_ENDR: + report(stack->top, "No repeat block active\n"); + return 0; + + case P_EVEN: + cp = skipwhite(cp); + if (!EOL(*cp)) { + report(stack->top, ".EVEN must not have an argument\n"); + } + if (DOT & 1) { + list_word(stack->top, DOT, 0, 1, ""); + DOT++; + change_dot(tr, 0); + } + return 1; + + case P_ODD: + if (!EOL(*cp)) { + report(stack->top, ".ODD must not have an argument\n"); + } + if (!(DOT & 1)) { + list_word(stack->top, DOT, 0, 1, ""); + DOT++; + change_dot(tr, 0); + } + return 1; + + case P_ASECT: + if (!enabl_lsb) { + lsb = get_next_lsb(); + } + go_section(tr, &absolute_section); + list_location(stack->top, DOT); + return CHECK_EOL; + + case P_CSECT: + case P_PSECT: + { + SYMBOL *sectsym; + SECTION *sect; + unsigned int old_flags = ~0u; + + label = get_symbol(cp, &cp, NULL); + if (label == NULL) { + sect = &blank_section; + } + else { + sectsym = lookup_sym(label, §ion_st); + if (sectsym) { + sect = sectsym->section; + free(label); + old_flags = sect->flags; + } else { + sect = new_section(); + sect->label = label; + sect->flags = 0; + sect->pc = 0; + sect->size = 0; + sect->type = SECTION_USER; + sections[sector++] = sect; + sectsym = add_sym(label, 0, SYMBOLFLAG_DEFINITION, sect, §ion_st); + + /* page 6-41 table 6-5 */ + if (op->value == P_PSECT) { + sect->flags |= PSECT_REL; + } else { + sect->flags |= PSECT_REL | PSECT_COM | PSECT_GBL; + } + } + } + + cp = skipdelim(cp); + if (!EOL(*cp)) { + while (cp = skipdelim(cp), !EOL(*cp)) { + /* Parse section options */ + label = get_symbol(cp, &cp, NULL); + if (strcmp(label, "ABS") == 0) { + sect->flags &= ~PSECT_REL; /* Not relative */ + sect->flags |= PSECT_COM; /* implies common */ + } else if (strcmp(label, "REL") == 0) { + sect->flags |= PSECT_REL; /* Is relative */ + } else if (strcmp(label, "SAV") == 0) { + sect->flags |= PSECT_SAV; /* Is root */ + } else if (strcmp(label, "NOSAV") == 0) { + sect->flags &= ~PSECT_SAV; /* Is not root */ + } else if (strcmp(label, "OVR") == 0) { + sect->flags |= PSECT_COM; /* Is common */ + } else if (strcmp(label, "CON") == 0) { + sect->flags &= ~PSECT_COM; /* Concatenated */ + } else if (strcmp(label, "RW") == 0) { + sect->flags &= ~PSECT_RO; /* Not read-only */ + } else if (strcmp(label, "RO") == 0) { + sect->flags |= PSECT_RO; /* Is read-only */ + } else if (strcmp(label, "I") == 0) { + sect->flags &= ~PSECT_DATA; /* Not data */ + } else if (strcmp(label, "D") == 0) { + sect->flags |= PSECT_DATA; /* data */ + } else if (strcmp(label, "GBL") == 0) { + sect->flags |= PSECT_GBL; /* Global */ + } else if (strcmp(label, "LCL") == 0) { + sect->flags &= ~PSECT_GBL; /* Local */ + } else { + report(stack->top, "Unknown flag %s given to .PSECT directive\n", label); + free(label); + return 0; + } + + free(label); + } + /* If a section is declared a second time, and flags + * are given, then they must be identical to the + * first time. + * See page 6-38 of AA-KX10A-TC_PDP-11_MACRO-11_Reference_Manual_May88.pdf . + */ + if (old_flags != ~0u && sect->flags != old_flags) { + /* The manual also says that any different + * flags are ignored, and an error issued. + * Apparently, that isn't true. + * Kermit seems to do this in k11cmd.mac: + * .psect $pdata ; line 16 + * .psect $pdata ,ro,d,lcl,rel,con + * ; k11mac.mac, first pass only + * .psect $PDATA ,D ; line 1083 + * and ends up with + * $PDATA 001074 003 (RO,D,LCL,REL,CON) + */ + + /* + sect->flags = old_flags; + report(stack->top, "Program section flags not identical\n"); + */ + } + } + + if (!enabl_lsb) { + lsb = get_next_lsb(); + } + go_section(tr, sect); + list_location(stack->top, DOT); + + return CHECK_EOL; + } /* end PSECT code */ + break; + + case P_WEAK: + case P_GLOBL: + { + SYMBOL *sym; + int islocal = 0; + + while (!EOL(*cp)) { + /* Loop and make definitions for + comma-separated symbols */ + label = get_symbol(cp, &ncp, &islocal); + if (label == NULL) { + report(stack->top, "Illegal .GLOBL/.WEAK syntax\n"); + return 0; + } + + if (islocal) { + report(stack->top, "Local label used in .GLOBL/.WEAK\n"); + return 0; + } + + sym = lookup_sym(label, &symbol_st); + if (sym) { + sym->flags |= SYMBOLFLAG_GLOBAL | (op->value == P_WEAK ? SYMBOLFLAG_WEAK : 0); + } else + sym = add_sym(label, 0, + SYMBOLFLAG_GLOBAL | (op->value == P_WEAK ? SYMBOLFLAG_WEAK : 0), + &absolute_section, &symbol_st); + + free(label); + cp = skipdelim(ncp); + } + } + return CHECK_EOL; + + case P_WORD: + { + /* .WORD might be followed by nothing, which + is an implicit .WORD 0 */ + if (EOL(*cp)) { + if (DOT & 1) { + report(stack->top, ".WORD on odd boundary\n"); + DOT++; /* Fix it, too */ + } + store_word(stack->top, tr, 2, 0); + return 1; + } else + return do_word(stack, tr, cp, 2); + } + + case P_BYTE: + if (EOL(*cp)) { + /* Blank .BYTE. Same as .BYTE 0 */ + store_word(stack->top, tr, 1, 0); + return 1; + } else + return do_word(stack, tr, cp, 1); + + case P_BLKW: + case P_BLKB: + { + EX_TREE *value; + int ok = 1; + + cp = skipwhite(cp); + if (EOL(*cp)) { + /* If no argument, assume 1. Documented but + * discouraged. Par 6.5.3, page 6-32. */ + /* warning(stack->top, "Argument to .BLKB/.BLKW should be present; 1 assumed\n"); */ + value = new_ex_lit(1); + } else { + value = parse_expr(cp, 0); + cp = value->cp; + } + + if (value->type != EX_LIT) { + report(stack->top, "Argument to .BLKB/.BLKW must be constant\n"); + ok = 0; + } else { + list_value(stack->top, DOT); + DOT += value->data.lit * (op->value == P_BLKW ? 2 : 1); + change_dot(tr, 0); + } + free_tree(value); + return ok && CHECK_EOL; + } + + case P_ASCIZ: + case P_ASCII: + { + do { + cp = skipwhite(cp); + if (*cp == '<') { + EX_TREE *value; + /* A byte value */ + value = parse_unary_expr(cp, 0); + cp = value->cp; + store_value(stack, tr, 1, value); + free_tree(value); + } else { + char quote = *cp++; + + while (*cp && *cp != '\n' && *cp != quote) + store_word(stack->top, tr, 1, *cp++); + cp++; /* Skip closing quote */ + } + + cp = skipwhite(cp); + } while (!EOL(*cp)); + + if (op->value == P_ASCIZ) { + store_word(stack->top, tr, 1, 0); + } + + return 1; + } + + case P_RAD50: + if (DOT & 1) { + report(stack->top, ".RAD50 on odd boundary\n"); + DOT++; /* Fix it */ + } + { + int i, count; + unsigned *rad50; + + /* Now assemble the argument */ + rad50 = assemble_rad50 (cp, 0, &count, stack); + for (i = 0; i < count; i++) { + store_word (stack->top, tr, 2, rad50[i]); + } + free (rad50); + } + return 1; + + default: + report(stack->top, "Unimplemented directive %s\n", op->label); + return 0; + } /* end switch (PSEUDO operation) */ + + case SECTION_INSTRUCTION: + { + /* The PC must always be even. */ + if (DOT & 1) { + report(stack->top, "Instruction on odd address\n"); + DOT++; /* ...and fix it... */ + } + + switch (op->flags & OC_MASK) { + case OC_NONE: + /* No operands. */ + store_word(stack->top, tr, 2, op->value); + return CHECK_EOL; + + case OC_MARK: + /* MARK, EMT, TRAP */ { + EX_TREE *value; + + cp = skipwhite(cp); + if (EOL (*cp)) { + /* Default argument is 0 */ + store_word (stack->top, tr, 2, op->value); + } + else { + if (*cp == '#') + cp++; /* Allow the hash, but + don't require it */ + value = parse_expr(cp, 0); + cp = value->cp; + if (value->type != EX_LIT) { + if (op->value == I_MARK) { + report(stack->top, "Instruction requires " "simple literal operand\n"); + store_word(stack->top, tr, 2, op->value); + } + else { + /* EMT, TRAP: handle as two bytes */ + store_value (stack, tr, 1, value); + store_word (stack->top, tr, 1, op->value >> 8); + } + } else { + unsigned v = value->data.lit; + unsigned int max = (op->value == I_MARK)? 077 : 0377; + + if (v > max) { + report(stack->top, "Literal operand too large (%d. > %d.)\n", value->data.lit, max); + v = max; + } + store_word(stack->top, tr, 2, op->value | v); + } + free_tree(value); + } + } + return CHECK_EOL; + + case OC_1GEN: + /* One general addressing mode */ { + ADDR_MODE mode; + unsigned word; + char *error; + + if (!get_mode(cp, &cp, &mode, &error)) { + report(stack->top, + "Invalid addressing mode (%s)\n", error); + return 0; + } + + if (op->value == I_JMP && (mode.type & 070) == 0) { + report(stack->top, "JMP Rn is illegal\n"); + /* But encode it anyway... */ + } + + /* Build instruction word */ + word = op->value | mode.type; + store_word(stack->top, tr, 2, word); + mode_extension(tr, &mode, stack->top); + } + return CHECK_EOL; + + case OC_2GEN: + /* Two general addressing modes */ { + ADDR_MODE left, + right; + unsigned word; + char *error; + + if (!get_mode(cp, &cp, &left, &error)) { + report(stack->top, + "Invalid addressing mode (1st operand: %s)\n", + error); + return 0; + } + + cp = skipwhite(cp); + if (*cp++ != ',') { + report(stack->top, "Invalid syntax (comma expected)\n"); + free_addr_mode(&left); + return 0; + } + + if (!get_mode(cp, &cp, &right, &error)) { + report(stack->top, + "Invalid addressing mode (2nd operand: %s)\n", + error); + free_addr_mode(&left); + return 0; + } + + /* Build instruction word */ + word = op->value | left.type << 6 | right.type; + store_word(stack->top, tr, 2, word); + mode_extension(tr, &left, stack->top); + mode_extension(tr, &right, stack->top); + } + return CHECK_EOL; + + case OC_BR: + /* branches */ { + EX_TREE *value; + unsigned offset; + + value = parse_expr(cp, 0); + cp = value->cp; + + /* Relative PSECT or absolute? */ + if (current_pc->section->flags & PSECT_REL) { + SYMBOL *sym = NULL; + + /* Can't branch unless I can + calculate the offset. */ + + /* You know, I *could* branch + between sections if I feed the + linker a complex relocation + expression to calculate the + offset. But I won't. */ + + if (!express_sym_offset(value, &sym, &offset) + || sym->section != current_pc->section) { + report(stack->top, "Bad branch target (%s)\n", + sym ? "not same section" + : "can't express offset"); + store_word(stack->top, tr, 2, op->value); + free_tree(value); + return 0; + } + + /* Compute the branch offset and + check for addressability */ + offset += sym->value; + offset -= DOT + 2; + } else { + if (value->type != EX_LIT) { + report(stack->top, "Bad branch target (not literal; ABS section)\n"); + store_word(stack->top, tr, 2, op->value); + free_tree(value); + return 0; + } + + offset = value->data.lit - (DOT + 2); + } + + if (!check_branch(stack, offset, -256, 255)) + offset = 0; + + /* Emit the branch code */ + offset &= 0777; /* Reduce to 9 bits */ + offset >>= 1; /* Shift to become + word offset */ + + store_word(stack->top, tr, 2, op->value | offset); + + free_tree(value); + } + return CHECK_EOL; + + case OC_SOB: + { + EX_TREE *value; + unsigned reg; + unsigned offset; + + value = parse_expr(cp, 0); + cp = value->cp; + + reg = get_register(value); + free_tree(value); + if (reg == NO_REG) { + report(stack->top, "Invalid addressing mode (register expected)\n"); + return 0; + } + + cp = skipwhite(cp); + if (*cp++ != ',') { + report(stack->top, "Invalid syntax (comma expected)\n"); + return 0; + } + + value = parse_expr(cp, 0); + cp = value->cp; + + /* Relative PSECT or absolute? */ + if (current_pc->section->flags & PSECT_REL) { + SYMBOL *sym; + + if (!express_sym_offset(value, &sym, &offset)) { + report(stack->top, "Bad branch target (can't express offset)\n"); + free_tree(value); + return 0; + } + /* Must be same section */ + if (sym->section != current_pc->section) { + report(stack->top, "Bad branch target (different section)\n"); + free_tree(value); + offset = 0; + } else { + /* Calculate byte offset */ + offset += DOT + 2; + offset -= sym->value; + } + } else { + if (value->type != EX_LIT) { + report(stack->top, "Bad branch target (not a literal)\n"); + offset = 0; + } else { + offset = DOT + 2 - value->data.lit; + } + } + + if (!check_branch(stack, offset, 0, 126)) + offset = 0; + + offset &= 0177; /* Reduce to 7 bits */ + offset >>= 1; /* Shift to become word offset */ + store_word(stack->top, tr, 2, op->value | offset | (reg << 6)); + + free_tree(value); + } + return CHECK_EOL; + + case OC_ASH: + /* First op is gen, second is register. */ { + ADDR_MODE mode; + EX_TREE *value; + unsigned reg; + unsigned word; + char *error; + + if (!get_mode(cp, &cp, &mode, &error)) { + report(stack->top, + "Invalid addressing mode (1st operand: %s)\n", + error); + return 0; + } + + cp = skipwhite(cp); + if (*cp++ != ',') { + report(stack->top, "Invalid syntax (comma expected)\n"); + free_addr_mode(&mode); + return 0; + } + value = parse_expr(cp, 0); + cp = value->cp; + + reg = get_register(value); + if (reg == NO_REG) { + report(stack->top, "Invalid addressing mode (2nd operand: register expected)\n"); + free_tree(value); + free_addr_mode(&mode); + return 0; + } + + /* Instruction word */ + word = op->value | mode.type | (reg << 6); + store_word(stack->top, tr, 2, word); + mode_extension(tr, &mode, stack->top); + free_tree(value); + } + return CHECK_EOL; + + case OC_JSR: + /* For JSR and XOR, first op is register, second is gen. */ { + ADDR_MODE mode; + EX_TREE *value; + unsigned reg; + unsigned word; + char *error; + + value = parse_expr(cp, 0); + cp = value->cp; + + reg = get_register(value); + if (reg == NO_REG) { + report(stack->top, "Invalid addressing mode (1st operand: register exected)\n"); + free_tree(value); + return 0; + } + + cp = skipwhite(cp); + if (*cp++ != ',') { + report(stack->top, "Invalid syntax (comma expected)\n"); + return 0; + } + + if (!get_mode(cp, &cp, &mode, &error)) { + report(stack->top, + "Invalid addressing mode (2nd operand: %s)\n", + error); + free_tree(value); + return 0; + } + + if (op->value == I_JSR && (mode.type & 070) == 0) { + report(stack->top, "JSR Rn,Rm is illegal\n"); + /* But encode it anyway... */ + } + + word = op->value | mode.type | (reg << 6); + store_word(stack->top, tr, 2, word); + mode_extension(tr, &mode, stack->top); + free_tree(value); + } + return CHECK_EOL; + + case OC_1REG: + /* One register (RTS,FADD,FSUB,FMUL,FDIV,SPL) */ { + EX_TREE *value; + unsigned reg; + + value = parse_expr(cp, 0); + cp = value->cp; + reg = get_register(value); + if (reg == NO_REG) { + report(stack->top, "Invalid addressing mode (register expected)\n"); + reg = 0; + } + + store_word(stack->top, tr, 2, op->value | reg); + free_tree(value); + } + return CHECK_EOL; + +#if 0 +/* + * Although it is arguable that the FPP TSTF/TSTD instruction has 1 + * operand which is a floating point source, the PDP11 Architecture + * Handbook describes it as a destination, and MACRO11 V05.05 doesn't + * allow a FP literal argument. + */ + case OC_FPP_FSRC: + /* One fp immediate or a general addressing mode */ { + ADDR_MODE mode; + unsigned word; + + if (!get_fp_src_mode(cp, &cp, &mode)) { + report(stack->top, "Illegal addressing mode\n"); + return 0; + } + + /* Build instruction word */ + word = op->value | mode.type; + store_word(stack->top, tr, 2, word); + mode_extension(tr, &mode, stack->top); + } + return CHECK_EOL; +#endif + + case OC_FPP_SRCAC: + case OC_FPP_FSRCAC: + /* One gen and one reg 0-3 */ { + ADDR_MODE mode; + EX_TREE *value; + unsigned reg; + unsigned word; + char *error; + + if ((op->flags & OC_MASK) == OC_FPP_FSRCAC) { + if (!get_fp_src_mode(cp, &cp, &mode, &error)) { + report(stack->top, + "Invalid addressing mode (1st operand, fsrc: %s)\n", + error); + return 0; + } + } else { + if (!get_mode(cp, &cp, &mode, &error)) { + report(stack->top, + "Invalid addressing mode (1st operand: %s)\n", + error); + return 0; + } + } + + cp = skipwhite(cp); + if (*cp++ != ',') { + report(stack->top, "Invalid syntax (comma expected)\n"); + free_addr_mode(&mode); + return 0; + } + + value = parse_expr(cp, 0); + cp = value->cp; + + reg = get_register(value); + if (reg == NO_REG || reg > 3) { + report(stack->top, "Invalid destination fp register\n"); + reg = 0; + } + + /* + * We could check here that the general mode + * is not AC6 or AC7, but the original Macro11 + * doesn't do that either. + */ + word = op->value | mode.type | (reg << 6); + store_word(stack->top, tr, 2, word); + mode_extension(tr, &mode, stack->top); + free_tree(value); + } + return CHECK_EOL; + + case OC_FPP_ACFDST: + /* One reg 0-3 and one fdst */ { + ADDR_MODE mode; + EX_TREE *value; + unsigned reg; + unsigned word; + char *error; + + value = parse_expr(cp, 0); + cp = value->cp; + + reg = get_register(value); + if (reg == NO_REG || reg > 3) { + report(stack->top, "Invalid source fp register\n"); + reg = 0; + } + + cp = skipwhite(cp); + if (*cp++ != ',') { + report(stack->top, "Invalid syntax (comma expected)\n"); + free_tree(value); + return 0; + } + + if (!get_mode(cp, &cp, &mode, &error)) { + report(stack->top, + "Invalid addressing mode (2nd operand: %s)\n", + error); + free_tree(value); + return 0; + } + + /* + * We could check here that the general mode + * is not AC6 or AC7, but the original Macro11 + * doesn't do that either. + * + * For some (mostly STore instructions) the + * destination isn't a FDST but a plain DST. + */ + word = op->value | mode.type | (reg << 6); + store_word(stack->top, tr, 2, word); + mode_extension(tr, &mode, stack->top); + free_tree(value); + } + return CHECK_EOL; + + { int nwords; + EX_TREE *expr[4]; + case OC_CIS2: + /* Either no operands or 2 (mostly) address operand words + * (extension) */ + nwords = 2; + goto cis_common; + case OC_CIS3: + /* Either no operands or 3 (mostly) address operand words + * (extension) */ + nwords = 3; + goto cis_common; + case OC_CIS4: + /* Either no operands or 4 (mostly) address operand words + * (extension) */ + nwords = 4; + cis_common: + if (!EOL(*cp)) { + for (int i = 0; i < nwords; i++) { + if (i > 0) { + cp = skipwhite(cp); + if (*cp++ != ',') { + report(stack->top, "Invalid syntax (operand %d: comma expected)\n", i+1); + cp--; + } + } + EX_TREE *ex = parse_expr(cp, 0); + if (!expr_ok(ex)) { + report(stack->top, "Invalid expression (operand %d)\n", i+1); + } + cp = ex->cp; + expr[i] = ex; + } + } else { + expr[0] = NULL; + } + + store_word(stack->top, tr, 2, op->value); + + if (expr[0]) { + for (int i = 0; i < nwords; i++) { + store_value(stack, tr, 2, expr[i]); + } + } + } + return CHECK_EOL; + + default: + report(stack->top, "Unimplemented instruction format\n"); + return 0; + } /* end(handle an instruction) */ + } + break; + } /* end switch(section type) */ + } /* end if (op is a symbol) */ + } + + /* If .ENABL MCL is in effect, try to define the symbol as a + * library macro if it is not a defined symbol. */ + if (enabl_mcl) { + if (lookup_sym(label, &symbol_st) == NULL) { + if (do_mcall (label, stack)) { + goto do_mcalled_macro; + } + } + } + + /* Only thing left is an implied .WORD directive */ + /*JH: fall through in case of illegal opcode, illegal label! */ + free(label); + + return do_word(stack, tr, cp, 2); +} + +int get_next_lsb( + void) +{ + if (lsb_used) { + lsb_used = 0; + if (enabl_debug && lstfile) { + fprintf(lstfile, "get_next_lsb: lsb: %d becomes %d (= next_lsb)\n", lsb, next_lsb); + } + return next_lsb++; + } else { + if (enabl_debug && lstfile) { + fprintf(lstfile, "get_next_lsb: lsb: stays %d\n", lsb); + } + return lsb; + } +} + +/* assemble_stack assembles the input stack. It returns the error + count. */ + +int assemble_stack( + STACK *stack, + TEXT_RLD *tr) +{ + int res; + int errcount = 0; + + while ((res = assemble(stack, tr)) >= 0) { + list_flush(); + if (res == 0) + errcount++; /* Count an error */ + } + + return errcount; +} diff --git a/crossassemblers/macro11/assemble.h b/crossassemblers/macro11/assemble.h new file mode 100644 index 0000000..369a007 --- /dev/null +++ b/crossassemblers/macro11/assemble.h @@ -0,0 +1,19 @@ + +#ifndef ASSEMBLE__H +#define ASSEMBLE__H + + +#include "stream2.h" +#include "object.h" + + + +#define DOT (current_pc->value) /* Handy reference to the current location */ + +int assemble_stack( + STACK *stack, + TEXT_RLD *tr); +int get_next_lsb( + void); + +#endif diff --git a/crossassemblers/macro11/assemble_aux.c b/crossassemblers/macro11/assemble_aux.c new file mode 100644 index 0000000..b00fb3a --- /dev/null +++ b/crossassemblers/macro11/assemble_aux.c @@ -0,0 +1,833 @@ + +/* + Smaller operators for assemble +*/ + +#include +#include +#include + +#include "util.h" + +#include "assemble_aux.h" /* my own definitions */ + +#include "assemble_globals.h" +#include "macros.h" +#include "assemble.h" +#include "listing.h" +#include "symbols.h" +#include "parse.h" + + +/* Allocate a new section */ + +SECTION *new_section( + void) +{ + SECTION *sect = memcheck(malloc(sizeof(SECTION))); + + sect->flags = 0; + sect->size = 0; + sect->pc = 0; + sect->type = 0; + sect->sector = 0; + sect->label = NULL; + return sect; +} + + + +/* This is called by places that are about to store some code, or + which want to manually update DOT. */ + +void change_dot( + TEXT_RLD *tr, + int size) +{ + if (size > 0) { + if (last_dot_section != current_pc->section) { + text_define_location(tr, current_pc->section->label, ¤t_pc->value); + last_dot_section = current_pc->section; + last_dot_addr = current_pc->value; + } + if (last_dot_addr != current_pc->value) { + text_modify_location(tr, ¤t_pc->value); + last_dot_addr = current_pc->value; + } + + /* Update for next time */ + last_dot_addr += size; + } + + if (DOT + size > current_pc->section->size) + current_pc->section->size = DOT + size; +} + +/* store_word stores a word to the object file and lists it to the + listing file */ + +int store_word( + STREAM *str, + TEXT_RLD *tr, + int size, + unsigned word) +{ + change_dot(tr, size); + list_word(str, DOT, word, size, ""); + return text_word(tr, &DOT, size, word); +} + +/* store_word stores a word to the object file and lists it to the + listing file */ + +static int store_displaced_word( + STREAM *str, + TEXT_RLD *tr, + int size, + unsigned word) +{ + change_dot(tr, size); + list_word(str, DOT, word, size, "'"); + return text_displaced_word(tr, &DOT, size, word); +} + +static int store_global_displaced_offset_word( + STREAM *str, + TEXT_RLD *tr, + int size, + unsigned word, + char *global) +{ + change_dot(tr, size); + list_word(str, DOT, word, size, "G"); + return text_global_displaced_offset_word(tr, &DOT, size, word, global); +} + +static int store_global_offset_word( + STREAM *str, + TEXT_RLD *tr, + int size, + unsigned word, + char *global) +{ + change_dot(tr, size); + list_word(str, DOT, word, size, "G"); + return text_global_offset_word(tr, &DOT, size, word, global); +} + +static int store_internal_word( + STREAM *str, + TEXT_RLD *tr, + int size, + unsigned word) +{ + change_dot(tr, size); + list_word(str, DOT, word, size, "'"); + return text_internal_word(tr, &DOT, size, word); +} + +static int store_psect_displaced_offset_word( + STREAM *str, + TEXT_RLD *tr, + int size, + unsigned word, + char *name) +{ + change_dot(tr, size); + list_word(str, DOT, word, size, "'"); + return text_psect_displaced_offset_word(tr, &DOT, size, word, name); +} + +static int store_psect_offset_word( + STREAM *str, + TEXT_RLD *tr, + int size, + unsigned word, + char *name) +{ + change_dot(tr, size); + list_word(str, DOT, word, size, "'"); + return text_psect_offset_word(tr, &DOT, size, word, name); +} + +int store_limits( + STREAM *str, + TEXT_RLD *tr) +{ + change_dot(tr, 4); + list_word(str, DOT, 0, 2, ""); + list_word(str, DOT + 2, 0, 2, ""); + return text_limits(tr, &DOT); +} + + +/* free_addr_mode frees the storage consumed by an addr_mode */ + +void free_addr_mode( + ADDR_MODE *mode) +{ + if (mode->offset) + free_tree(mode->offset); + mode->offset = NULL; +} + +/* Get the register indicated by the expression */ + +unsigned get_register( + EX_TREE *expr) +{ + unsigned reg; + + if (expr->type == EX_LIT && expr->data.lit <= 7) { + reg = expr->data.lit; + return reg; + } + + if (expr->type == EX_SYM && expr->data.symbol->section->type == SECTION_REGISTER) { + reg = expr->data.symbol->value; + return reg; + } + + return NO_REG; +} + + +/* + implicit_gbl is a self-recursive routine that adds undefined symbols + to the "implicit globals" symbol table, or alternatively adds the + symbol as an UNDEFINED symbol. +*/ + +void implicit_gbl( + EX_TREE *value) +{ + if (pass || !value) + return; /* Only do this in first pass */ + + switch (num_subtrees(value)) { + case 0: + switch (value->type) { + case EX_UNDEFINED_SYM: + { + if (!(value->data.symbol->flags & SYMBOLFLAG_LOCAL)) { + /* Unless it's a local symbol, */ + if (enabl_gbl) { + /* either make the undefined symbol into an + implicit global */ + add_sym(value->data.symbol->label, 0, SYMBOLFLAG_GLOBAL, + &absolute_section, &implicit_st); + } else { + /* or add it to the undefined symbol table, + purely for listing purposes. + It also works to add it to symbol_st, + all code is carefully made for that. */ +#define ADD_UNDEFINED_SYMBOLS_TO_MAIN_SYMBOL_TABLE 0 +#if ADD_UNDEFINED_SYMBOLS_TO_MAIN_SYMBOL_TABLE + add_sym(value->data.symbol->label, 0, SYMBOLFLAG_UNDEFINED, + &absolute_section, &symbol_st); +#else + add_sym(value->data.symbol->label, 0, SYMBOLFLAG_UNDEFINED, + &absolute_section, &undefined_st); +#endif + } + } + } + break; + case EX_LIT: + case EX_SYM: + case EX_TEMP_SYM: // Impossible on this pass + return; + default: + break; + } + break; + case 2: + implicit_gbl(value->data.child.right); + /* FALLS THROUGH */ + case 1: + implicit_gbl(value->data.child.left); + break; + } +} + +/* Done between the first and second passes */ +/* Migrates the symbols from the "implicit" table into the main table. */ + +void migrate_implicit( + void) +{ + SYMBOL_ITER iter; + SYMBOL *isym, + *sym; + + for (isym = first_sym(&implicit_st, &iter); isym != NULL; isym = next_sym(&implicit_st, &iter)) { + sym = lookup_sym(isym->label, &symbol_st); + if (sym) { + continue; // It's already in there. Great. + } + isym->flags |= SYMBOLFLAG_IMPLICIT_GLOBAL; + sym = add_sym(isym->label, isym->value, isym->flags, isym->section, &symbol_st); + // Just one other thing - migrate the stmtno + sym->stmtno = isym->stmtno; + } +} + +/* Done between second pass and listing */ +/* Migrates the symbols from the "undefined" table into the main table. */ + +void migrate_undefined( + void) +{ + SYMBOL_ITER iter; + SYMBOL *isym, + *sym; + + for (isym = first_sym(&undefined_st, &iter); isym != NULL; isym = next_sym(&undefined_st, &iter)) { + sym = lookup_sym(isym->label, &symbol_st); + if (sym) { + continue; /* It's already in there. Great. */ + } + isym->flags |= SYMBOLFLAG_UNDEFINED; /* Just in case */ + sym = add_sym(isym->label, isym->value, isym->flags, isym->section, &symbol_st); + /* Just one other thing - migrate the stmtno */ + sym->stmtno = isym->stmtno; + } +} + +int express_sym_offset( + EX_TREE *value, + SYMBOL **sym, + unsigned *offset) +{ + implicit_gbl(value); /* Translate tree's undefined syms + into global syms */ + + /* Internally relocatable symbols will have been summed down into + EX_TEMP_SYM's. */ + + if (value->type == EX_SYM || value->type == EX_TEMP_SYM) { + *sym = value->data.symbol; + *offset = 0; + return 1; + } + + /* What remains is external symbols. */ + + if (value->type == EX_ADD) { + EX_TREE *left = value->data.child.left; + EX_TREE *right = value->data.child.right; + + if ((left->type != EX_SYM && left->type != EX_UNDEFINED_SYM) || right->type != EX_LIT) + return 0; /* Failed. */ + *sym = left->data.symbol; + *offset = right->data.lit; + return 1; + } + + if (value->type == EX_SUB) { + EX_TREE *left = value->data.child.left; + EX_TREE *right = value->data.child.right; + + if ((left->type != EX_SYM && left->type != EX_UNDEFINED_SYM) || right->type != EX_LIT) + return 0; /* Failed. */ + *sym = left->data.symbol; + *offset = (unsigned) -(int) (right->data.lit); + return 1; + } + + return 0; +} + +/* + Translate an EX_TREE into a TEXT_COMPLEX suitable for encoding + into the object file. */ + +int complex_tree( + TEXT_COMPLEX *tx, + EX_TREE *tree) +{ + switch (tree->type) { + case EX_LIT: + text_complex_lit(tx, tree->data.lit); + return 1; + + case EX_TEMP_SYM: + case EX_SYM: + { + SYMBOL *sym = tree->data.symbol; + + /* This check may not be needed; so far it made no difference. */ + if (sym->flags & SYMBOLFLAG_UNDEFINED) { + return 0; + } + + if (SYM_IS_IMPORTED(sym)) { + text_complex_global(tx, sym->label); + } else { + text_complex_psect(tx, sym->section->sector, sym->value); + } + } + return 1; + + case EX_COM: + if (!complex_tree(tx, tree->data.child.left)) + return 0; + text_complex_com(tx); + return 1; + + case EX_NEG: + if (!complex_tree(tx, tree->data.child.left)) + return 0; + text_complex_neg(tx); + return 1; + + case EX_ADD: + if (!complex_tree(tx, tree->data.child.left)) + return 0; + if (!complex_tree(tx, tree->data.child.right)) + return 0; + text_complex_add(tx); + return 1; + + case EX_SUB: + if (!complex_tree(tx, tree->data.child.left)) + return 0; + if (!complex_tree(tx, tree->data.child.right)) + return 0; + text_complex_sub(tx); + return 1; + + case EX_MUL: + if (!complex_tree(tx, tree->data.child.left)) + return 0; + if (!complex_tree(tx, tree->data.child.right)) + return 0; + text_complex_mul(tx); + return 1; + + case EX_DIV: + if (!complex_tree(tx, tree->data.child.left)) + return 0; + if (!complex_tree(tx, tree->data.child.right)) + return 0; + text_complex_div(tx); + return 1; + + case EX_AND: + if (!complex_tree(tx, tree->data.child.left)) + return 0; + if (!complex_tree(tx, tree->data.child.right)) + return 0; + text_complex_and(tx); + return 1; + + case EX_OR: + if (!complex_tree(tx, tree->data.child.left)) + return 0; + if (!complex_tree(tx, tree->data.child.right)) + return 0; + text_complex_or(tx); + return 1; + + default: + return 0; + } +} + +/* store a word which is represented by a complex expression. */ + +static void store_complex( + STREAM *refstr, + TEXT_RLD *tr, + int size, + EX_TREE *value) +{ + TEXT_COMPLEX tx; + + change_dot(tr, size); /* About to store - update DOT */ + + implicit_gbl(value); /* Turn undefined symbols into globals */ + + text_complex_begin(&tx); /* Open complex expression */ + + if (!complex_tree(&tx, value)) { /* Translate */ + report(refstr, "Invalid expression (complex relocation)\n"); + store_word(refstr, tr, size, 0); + } else { + list_word(refstr, DOT, 0, size, "C"); + text_complex_commit(tr, &DOT, size, &tx, 0); + } +} + +/* store_complex_displaced is the same as store_complex but uses the + "displaced" RLD code */ + +static void store_complex_displaced( + STREAM *refstr, + TEXT_RLD *tr, + int size, + EX_TREE *value) +{ + TEXT_COMPLEX tx; + + change_dot(tr, size); + + implicit_gbl(value); /* Turn undefined symbols into globals */ + + text_complex_begin(&tx); + + if (!complex_tree(&tx, value)) { + report(refstr, "Invalid expression (complex displaced relocation)\n"); + store_word(refstr, tr, size, 0); + } else { + list_word(refstr, DOT, 0, size, "C"); + text_complex_commit_displaced(tr, &DOT, size, &tx, 0); + } +} + +/* + mode_extension - writes the extension word required by an addressing + mode */ + +void mode_extension( + TEXT_RLD *tr, + ADDR_MODE *mode, + STREAM *str) +{ + EX_TREE *value = mode->offset; + SYMBOL *sym; + unsigned offset; + + /* Also frees the mode. */ + + if (value == NULL) { + free_addr_mode(mode); + return; + } + + if (value->type == EX_LIT) { + if (mode->pcrel) { /* PC-relative? */ + if (current_pc->section->flags & PSECT_REL) { + store_displaced_word(str, tr, 2, value->data.lit); + } else { + /* I can compute this myself. */ + store_word(str, tr, 2, value->data.lit - DOT - 2); + } + } else { + store_word(str, tr, 2, value->data.lit); /* Just a + known + value. */ + } + } else if (express_sym_offset(value, &sym, &offset)) { + if (SYM_IS_IMPORTED(sym)) { + /* Reference to a global symbol. */ + /* Global symbol plus offset */ + if (mode->pcrel) + store_global_displaced_offset_word(str, tr, 2, offset, sym->label); + else + store_global_offset_word(str, tr, 2, offset, sym->label); + } else if (sym->section->type == SECTION_REGISTER) { + /* Delayed action: evaluate() excludes SECTION_REGISTER when + * turning symbols into EX_LIT. Do it here now. */ + store_word(str, tr, 2, sym->value + offset); + } else { + /* Relative to non-external symbol. */ + if (current_pc->section == sym->section) { + /* In the same section */ + if (mode->pcrel) { + /* I can compute this myself. */ + store_word(str, tr, 2, sym->value + offset - DOT - 2); + } else + store_internal_word(str, tr, 2, sym->value + offset); + } else { + /* In a different section */ + if (mode->pcrel) + store_psect_displaced_offset_word(str, tr, 2, sym->value + offset, sym->section->label); + else + store_psect_offset_word(str, tr, 2, sym->value + offset, sym->section->label); + } + } + } else { + /* Complex relocation */ + + if (mode->pcrel) + store_complex_displaced(str, tr, 2, mode->offset); + else + store_complex(str, tr, 2, mode->offset); + } + + free_addr_mode(mode); +} + +/* eval_defined - take an EX_TREE and returns TRUE if the tree + represents "defined" symbols. */ + +int eval_defined( + EX_TREE *value) +{ + switch (value->type) { + case EX_LIT: + return 1; + case EX_SYM: + return 1; + case EX_UNDEFINED_SYM: + return 0; + case EX_AND: + return eval_defined(value->data.child.left) && eval_defined(value->data.child.right); + case EX_OR: + return eval_defined(value->data.child.left) || eval_defined(value->data.child.right); + default: + return 0; + } +} + +/* eval_undefined - take an EX_TREE and returns TRUE if it represents + "undefined" symbols. */ + +int eval_undefined( + EX_TREE *value) +{ + switch (value->type) { + case EX_UNDEFINED_SYM: + return 1; + case EX_SYM: + return 0; + case EX_AND: + return eval_undefined(value->data.child.left) && eval_undefined(value->data.child.right); + case EX_OR: + return eval_undefined(value->data.child.left) || eval_undefined(value->data.child.right); + default: + return 0; + } +} + +/* push_cond - a new conditional (.IF) block has been activated. Push + its context. */ + +void push_cond( + int ok, + STREAM *str) +{ + last_cond++; + assert(last_cond < MAX_CONDS); + conds[last_cond].ok = ok; + conds[last_cond].file = memcheck(strdup(str->name)); + conds[last_cond].line = str->line; +} + +/* + pop_cond - pop stacked conditionals. */ + +void pop_cond( + int to) +{ + while (last_cond > to) { + free(conds[last_cond].file); + last_cond--; + } +} + + +/* go_section - sets current_pc to a new program section */ + +void go_section( + TEXT_RLD *tr, + SECTION *sect) +{ + (void)tr; + + if (current_pc->section == sect) + return; /* This is too easy */ + + /* save current PC value for old section */ + current_pc->section->pc = DOT; + + /* Set current section and PC value */ + current_pc->section = sect; + DOT = sect->pc; +} + +/* + store_value - used to store a value represented by an expression + tree into the object file. Used by do_word and .ASCII/.ASCIZ. +*/ + +void store_value( + STACK *stack, + TEXT_RLD *tr, + int size, + EX_TREE *value) +{ + SYMBOL *sym; + unsigned offset; + + implicit_gbl(value); /* turn undefined symbols into globals */ + + if (value->type == EX_LIT) { + store_word(stack->top, tr, size, value->data.lit); + } else if (!express_sym_offset(value, &sym, &offset)) { + store_complex(stack->top, tr, size, value); + } else { + if (SYM_IS_IMPORTED(sym)) { + store_global_offset_word(stack->top, tr, size, sym->value + offset, sym->label); + } else if (sym->section->type == SECTION_REGISTER) { + /* Delayed action: evaluate() excludes SECTION_REGISTER when + * turning symbols into EX_LIT. Do it here now. */ + store_word(stack->top, tr, size, sym->value + offset); + } else if (sym->section != current_pc->section) { + store_psect_offset_word(stack->top, tr, size, sym->value + offset, sym->section->label); + } else { + store_internal_word(stack->top, tr, size, sym->value + offset); + } + } +} + +/* do_word - used by .WORD, .BYTE, and implied .WORD. */ + +int do_word( + STACK *stack, + TEXT_RLD *tr, + char *cp, + int size) +{ + int comma; + + if (size == 2 && (DOT & 1)) { + report(stack->top, ".WORD on odd boundary\n"); + store_word(stack->top, tr, 1, 0); /* Align it */ + } + + cp = skipwhite(cp); + + do { + if (cp[0] == ',') { + /* Empty expressions count as 0 */ + store_word(stack->top, tr, size, 0); + } else { + EX_TREE *value = parse_expr(cp, 0); + + if (value->type != EX_ERR && value->cp > cp) { + store_value(stack, tr, size, value); + + cp = value->cp; + } else { + report(stack->top, "Invalid expression in .WORD\n"); + cp = ""; /* force loop to end */ + } + + free_tree(value); + } + } while (cp = skipdelim_comma(cp, &comma), !EOL(*cp)); + + if (comma) { + /* Trailing empty expressions count as 0 */ + store_word(stack->top, tr, size, 0); + } + + return 1; +} + +/* + check_branch - check branch distance. +*/ + +int check_branch( + STACK *stack, + unsigned offset, + int min, + int max) +{ + int s_offset; + + if (offset & 1) { + report(stack->top, "Bad branch target (odd address)\n"); + } + + /* Sign-extend */ + if (offset & 0100000) + s_offset = offset | ~0177777; + else + s_offset = offset & 077777; + if (s_offset > max || s_offset < min) { + char temp[16]; + + /* printf can't do signed octal. */ + my_ltoa(s_offset, temp, 8); + report(stack->top, "Branch target out of range (distance=%s)\n", temp); + return 0; + } + return 1; +} + + +/* write_globals writes out the GSD prior to the second assembly pass */ + +void write_globals( + FILE *obj) +{ + GSD gsd; + SYMBOL *sym; + SECTION *psect; + SYMBOL_ITER sym_iter; + int isect; + + if (obj == NULL) { + for (isect = 0; isect < sector; isect++) { + psect = sections[isect]; + + psect->sector = isect; /* Assign it a sector */ + psect->pc = 0; /* Reset its PC for second pass */ + } + return; /* Nothing more to do if no OBJ file. */ + } + + gsd_init(&gsd, obj); + + gsd_mod(&gsd, module_name); + + if (ident) + gsd_ident(&gsd, ident); + + /* write out each PSECT with its global stuff */ + /* Sections must be written out in the order that they + appear in the assembly file. */ + for (isect = 0; isect < sector; isect++) { + psect = sections[isect]; + + gsd_psect(&gsd, psect->label, psect->flags, psect->size); + psect->sector = isect; /* Assign it a sector */ + psect->pc = 0; /* Reset its PC for second pass */ + + sym = first_sym(&symbol_st, &sym_iter); + while (sym) { + if ((sym->flags & SYMBOLFLAG_GLOBAL) && sym->section == psect) { + gsd_global(&gsd, sym->label, + ((sym->flags & SYMBOLFLAG_DEFINITION) ? GLOBAL_DEF : 0) | + ((sym->flags & SYMBOLFLAG_WEAK) ? GLOBAL_WEAK : 0) | + ((sym->section->flags & PSECT_REL) ? GLOBAL_REL : 0) | + 0100, + /* Looks undefined, but add it in anyway */ + sym->value); + } + sym = next_sym(&symbol_st, &sym_iter); + } + } + + /* Now write out the transfer address */ + if (xfer_address->type == EX_LIT) { + gsd_xfer(&gsd, ". ABS.", xfer_address->data.lit); + } else { + SYMBOL *lsym; + unsigned offset; + + if (!express_sym_offset(xfer_address, &lsym, &offset)) { + report(NULL, "Illegal program transfer address\n"); + } else { + gsd_xfer(&gsd, lsym->section->label, lsym->value + offset); + } + } + + gsd_flush(&gsd); + + gsd_end(&gsd); +} diff --git a/crossassemblers/macro11/assemble_aux.h b/crossassemblers/macro11/assemble_aux.h new file mode 100644 index 0000000..10cea84 --- /dev/null +++ b/crossassemblers/macro11/assemble_aux.h @@ -0,0 +1,98 @@ + +#ifndef ASSEMBLE_AUX__H +#define ASSEMBLE_AUX__H + +#include "stream2.h" +#include "object.h" +#include "extree.h" + +#define NO_REG 0777 + + +typedef struct addr_mode { + unsigned type; /* The bits that represent the addressing mode */ + /* bits 0:2 are register number */ + /* bit 3 is indirect */ + /* bits 4:6 are mode, where 0=Rn, 1=(Rn)+, + 2=-(Rn), 3=offset(Rn) */ + int pcrel; /* the addressing mode is PC-relative */ + EX_TREE *offset; /* Expression giving the offset */ +} ADDR_MODE; + +#define MODE_INDIRECT 010 /* (R0), @(R0)+, @-(R0), @42(R0) */ +#define MODE_REG 000 /* R0 */ +#define MODE_AUTO_INCR 020 /* (R0)+ */ +#define MODE_AUTO_DECR 040 /* -(R0) */ +#define MODE_OFFSET 060 /* 42(R0) */ +#define MODE_PC 007 + +void push_cond( + int ok, + STREAM *str); +void pop_cond( + int to); + +int express_sym_offset( + EX_TREE *value, + SYMBOL **sym, + unsigned *offset); + +void change_dot( + TEXT_RLD *tr, + int size); + +int store_word( + STREAM *str, + TEXT_RLD *tr, + int size, + unsigned word); +int store_limits( + STREAM *str, + TEXT_RLD *tr); +void store_value( + STACK *stack, + TEXT_RLD *tr, + int size, + EX_TREE *value); + +int do_word( + STACK *stack, + TEXT_RLD *tr, + char *cp, + int size); + +SECTION *new_section( + void); +void go_section( + TEXT_RLD *tr, + SECTION *sect); + +void free_addr_mode( + ADDR_MODE *mode); + +int eval_defined( + EX_TREE *value); +int eval_undefined( + EX_TREE *value); + + +void mode_extension( + TEXT_RLD *tr, + ADDR_MODE *mode, + STREAM *str); +int check_branch( + STACK *stack, + unsigned offset, + int min, + int max); +unsigned get_register( + EX_TREE *expr); + +void write_globals( + FILE *obj); +void migrate_implicit( + void); +void migrate_undefined( + void); + +#endif diff --git a/crossassemblers/macro11/assemble_globals.c b/crossassemblers/macro11/assemble_globals.c new file mode 100644 index 0000000..83a8bb1 --- /dev/null +++ b/crossassemblers/macro11/assemble_globals.c @@ -0,0 +1,108 @@ + +#define ASSEMBLE_GLOBALS__C + + +#include "assemble_globals.h" /* own definitions */ + +#include "object.h" + + +/* GLOBAL VARIABLES */ +int pass = 0; /* The current assembly pass. 0 = first pass */ +int stmtno = 0; /* The current source line number */ +int radix = 8; /* The current input conversion radix */ + + +int lsb = 0; /* The current local symbol section identifier */ +int lsb_used = 0; /* Whether there was a local symbol using this lsb */ +int next_lsb = 0; /* The number of the next local symbol block */ +int last_macro_lsb = 0; /* The last block in which a macro + automatic label was created */ + +int last_locsym = 32768; /* The last local symbol number generated */ + + +int enabl_debug = 0; /* Whether assembler debugging is enabled */ + +int opt_enabl_ama = 0; /* May be changed by command line */ +int enabl_ama; /* When set, chooses absolute (037) versus + PC-relative */ + /* (067) addressing mode */ +int enabl_lsb = 0; /* When set, stops non-local symbol + definitions from delimiting local + symbol sections. */ + +int enabl_gbl = 1; /* Implicit definition of global symbols */ + +int enabl_lc = 1; /* If lowercase disabled, convert assembler + source to upper case. */ + +int enabl_lcm = 0; /* If lowercase disabled, .IF IDN/DIF are + case-sensitive. */ + +int enabl_mcl = 0; /* When set, unknown symbols are looked up + as if .MCALL had been done. */ + +int suppressed = 0; /* Assembly suppressed by failed conditional */ + + +MLB *mlbs[MAX_MLBS]; /* macro libraries specified on the + command line */ +int nr_mlbs = 0; /* Number of macro libraries */ + +COND conds[MAX_CONDS]; /* Stack of recent conditions */ +int last_cond; /* 0 means no stacked cond. */ + +SECTION *sect_stack[SECT_STACK_SIZE]; /* 32 saved sections */ +int dot_stack[SECT_STACK_SIZE]; /* 32 saved sections */ +int sect_sp; /* Stack pointer */ + +char *module_name = NULL; /* The module name (taken from the 'TITLE'); */ + +unsigned *ident = NULL; /* Encoded .IDENT name */ + +EX_TREE *xfer_address = NULL; /* The transfer address */ + +SYMBOL *current_pc; /* The current program counter */ + +unsigned last_dot_addr; /* Last coded PC... */ +SECTION *last_dot_section; /* ...and its program section */ + +/* The following are dummy psects for symbols which have meaning to +the assembler: */ + +SECTION register_section = { + "*REGISTERS*", SECTION_REGISTER, 0, 0, 0, 0 +}; /* the section containing the registers */ + +SECTION pseudo_section = { + "*PSEUDO*", SECTION_PSEUDO, 0, 0, 0, 0 +}; /* the section containing the + pseudo-operations */ + +SECTION instruction_section = { + "*INSTR*", SECTION_INSTRUCTION, 0, 0, 0, 0 +}; /* the section containing instructions */ + +SECTION macro_section = { + "*MACRO*", SECTION_SYSTEM, 0, 0, 0, 0 +}; /* Section for macros */ + +/* These are real psects that get written out to the object file */ + +SECTION absolute_section = { + ". ABS.", SECTION_SYSTEM, PSECT_GBL | PSECT_COM, 0, 0, 0 +}; /* The default + absolute section */ + +SECTION blank_section = { + "", SECTION_SYSTEM, PSECT_REL, 0, 0, 1 +}; /* The default relocatable section */ + +SECTION *sections[256] = { + /* Array of sections in the order they were + defined */ + &absolute_section, &blank_section, +}; + +int sector = 2; /* number of such sections */ diff --git a/crossassemblers/macro11/assemble_globals.h b/crossassemblers/macro11/assemble_globals.h new file mode 100644 index 0000000..c229852 --- /dev/null +++ b/crossassemblers/macro11/assemble_globals.h @@ -0,0 +1,94 @@ + +#ifndef ASSEMBLE_GLOBALS__H +#define ASSEMBLE_GLOBALS__H + + +#include "mlb.h" +#include "symbols.h" +#include "extree.h" + + + +#define MAX_MLBS 32 /* number of macro libraries */ + +#define MAX_CONDS 256 +typedef struct cond { + int ok; /* What the condition evaluated to */ + char *file; /* What file and line it occurred */ + int line; +} COND; + +#define SECT_STACK_SIZE 32 + +#ifndef ASSEMBLE_GLOBALS__C +/* GLOBAL VARIABLES */ +extern int pass; /* The current assembly pass. 0 = first pass */ +extern int stmtno; /* The current source line number */ +extern int radix; /* The current input conversion radix */ +extern int lsb; /* The current local symbol section identifier */ +extern int lsb_used; /* Whether there was a local symbol using this lsb */ +extern int next_lsb; /* The number of the next local symbol block */ +extern int last_macro_lsb; /* The last block in which a macro + automatic label was created */ + +extern int last_locsym; /* The last local symbol number generated */ + +extern int enabl_debug; /* Whether assembler debugging is enabled */ + +extern int opt_enabl_ama; /* May be changed by command line */ + +extern int enabl_ama; /* When set, chooses absolute (037) versus + PC-relative */ + /* (067) addressing mode */ +extern int enabl_lsb; /* When set, stops non-local symbol + definitions from delimiting local + symbol sections. */ + +extern int enabl_gbl; /* Implicit definition of global symbols */ + +extern int enabl_lc; /* If lowercase disabled, convert assembler + source to upper case. */ +extern int enabl_lcm; /* If lowercase disabled, .IF IDN/DIF are + case-sensitive. */ +extern int suppressed; /* Assembly suppressed by failed conditional */ + +extern MLB *mlbs[MAX_MLBS]; /* macro libraries specified on the command line */ +extern int nr_mlbs; /* Number of macro libraries */ + +extern int enabl_mcl; /* If MCALL of unknown symbols is enabled. */ + +extern COND conds[MAX_CONDS]; /* Stack of recent conditions */ +extern int last_cond; /* 0 means no stacked cond. */ + +extern SECTION *sect_stack[SECT_STACK_SIZE]; /* 32 saved sections */ +extern int dot_stack[SECT_STACK_SIZE]; /* 32 saved sections */ +extern int sect_sp; /* Stack pointer */ + +extern char *module_name; /* The module name (taken from the 'TITLE'); */ + +extern unsigned *ident; /* .IDENT name (encoded RAD50 value) */ + +extern EX_TREE *xfer_address; /* The transfer address */ + +extern SYMBOL *current_pc; /* The current program counter */ + +extern unsigned last_dot_addr; /* Last coded PC... */ +extern SECTION *last_dot_section; /* ...and its program section */ + +/* The following are dummy psects for symbols which have meaning to + the assembler: */ +extern SECTION register_section; +extern SECTION pseudo_section; /* the section containing the pseudo-operations */ +extern SECTION instruction_section; /* the section containing instructions */ +extern SECTION macro_section; /* Section for macros */ + +/* These are real psects that get written out to the object file */ +extern SECTION absolute_section; /* The default absolute section */ +extern SECTION blank_section; +extern SECTION *sections[256]; /* Array of sections in the order they were defined */ +extern int sector; /* number of such sections */ + +#endif + + +#endif diff --git a/crossassemblers/macro11/changes b/crossassemblers/macro11/changes deleted file mode 100644 index 9fa1e30..0000000 --- a/crossassemblers/macro11/changes +++ /dev/null @@ -1,12 +0,0 @@ -15-July-2001 - version 0.2 - removed references to snprintf from dumpobj.c and - mlb.c for portability - fixed a type cast warning in dumpobj.c compare_gsdlines - Removed strcasecmp from macro11.c for portability - Removed references to wnewmem.c from makefile (isn't needed) - makefile more compatible with non-gnu make and compiler - main prints version 0.2 - -14-July-2001 - First release, version 0.1. diff --git a/crossassemblers/macro11/depends b/crossassemblers/macro11/depends deleted file mode 100644 index e69de29..0000000 diff --git a/crossassemblers/macro11/dumpobj.c b/crossassemblers/macro11/dumpobj.c index 4efeb5f..c81aeaa 100644 --- a/crossassemblers/macro11/dumpobj.c +++ b/crossassemblers/macro11/dumpobj.c @@ -47,631 +47,745 @@ DAMAGE. #define WORD(cp) ((*(cp) & 0xff) + ((*((cp)+1) & 0xff) << 8)) -int psectid = 0; -char *psects[256]; -FILE *bin = NULL; -int badbin = 0; -int xferad = 1; +#define NPSECTS 256 -char *readrec(FILE *fp, int *len) +#ifndef DEFAULT_OBJECTFORMAT_RT11 +#define DEFAULT_OBJECTFORMAT_RT11 0 +#endif + +int psectid = 0; +char *psects[NPSECTS]; +FILE *bin = NULL; +int badbin = 0; +int xferad = 1; + +/* memcheck - crash out if a pointer (returned from malloc) is NULL. */ + +void *memcheck( + void *ptr) { - int c, i; - int chksum; - char *buf; + if (ptr == NULL) { + fprintf(stderr, "Out of memory.\n"); + exit(EXIT_FAILURE); + } - chksum = 0; - - while(c = fgetc(fp), c != EOF && c == 0) - ; - - if(c == EOF) - return NULL; - - if(c != 1) - { - fprintf(stderr, "Improperly formatted OBJ file (1)\n"); - return NULL; // Not a properly formatted file. - } - - chksum -= c; - - c = fgetc(fp); - if(c != 0) - { - fprintf(stderr, "Improperly formatted OBJ file (2)\n"); - return NULL; // Not properly formatted - } - - chksum -= c; // even though for 0 the checksum isn't changed... - - c = fgetc(fp); - if(c == EOF) - { - fprintf(stderr, "Improperly formatted OBJ file (3)\n"); - return NULL; - } - *len = c; - - chksum -= c; - - c = fgetc(fp); - if(c == EOF) - { - fprintf(stderr, "Improperly formatted OBJ file (4)\n"); - return NULL; - } - - *len += (c << 8); - - chksum -= c; - - *len -= 4; // Subtract header and length bytes from length - if(*len < 0) - { - fprintf(stderr, "Improperly formatted OBJ file (5)\n"); - return NULL; - } - - buf = malloc(*len); - if(buf == NULL) - { - fprintf(stderr, "Out of memory allocating %d bytes\n", *len); - return NULL; // Bad alloc - } - - i = fread(buf, 1, *len, fp); - if(i < *len) - { - free(buf); - fprintf(stderr, "Improperly formatted OBJ file (6)\n"); - return NULL; - } - - for(i = 0; i < *len; i++) - { - chksum -= (buf[i] & 0xff); - } - - c = fgetc(fp); - c &= 0xff; - chksum &= 0xff; - - if(c != chksum) - { - free(buf); - fprintf(stderr, "Bad record checksum, " - "calculated=%d, recorded=%d\n", chksum, c); - return NULL; - } - - return buf; + return ptr; } -void dump_bytes(char *buf, int len) +char *readrec( + FILE *fp, + int *len, + int rt11) { - int i, j; + int c, + i; + int chksum; + char *buf; - for(i = 0; i < len; i += 8) - { - printf("\t%3.3o: ", i); - for(j = i; j < len && j < i+8; j++) - { - printf("%3.3o ", buf[j] & 0xff); - } + chksum = 0; - printf("%*s", (i+8 - j) * 4, ""); + if (rt11) { + while (c = fgetc(fp), c != EOF && c == 0) ; - for(j = i; j < len && j < i+8; j++) - { - int c = buf[j] & 0xff; - if(!isprint(c)) - c = '.'; - putchar(c); - } + if (c == EOF) + return NULL; - putchar('\n'); - } + if (c != 1) { + fprintf(stderr, "Improperly formatted OBJ file (1)\n"); + return NULL; /* Not a properly formatted file. */ + } + + chksum -= c; + + c = fgetc(fp); + if (c != 0) { + fprintf(stderr, "Improperly formatted OBJ file (2)\n"); + return NULL; /* Not properly formatted */ + } + + chksum -= c; /* even though for 0 the checksum isn't changed... */ + } + + c = fgetc(fp); + if (c == EOF) { + if (rt11) { + fprintf(stderr, "Improperly formatted OBJ file (3)\n"); + } + return NULL; + } + *len = c; + + chksum -= c; + + c = fgetc(fp); + if (c == EOF) { + fprintf(stderr, "Improperly formatted OBJ file (4)\n"); + return NULL; + } + + *len += (c << 8); + + chksum -= c; + + if (rt11) { + *len -= 4; /* Subtract header and length bytes from length */ + } + + if (*len < 0) { + fprintf(stderr, "Improperly formatted OBJ file (5)\n"); + return NULL; + } + + buf = malloc(*len); + if (buf == NULL) { + fprintf(stderr, "Out of memory allocating %d bytes\n", *len); + return NULL; /* Bad alloc */ + } + + i = fread(buf, 1, *len, fp); + if (i < *len) { + free(buf); + fprintf(stderr, "Improperly formatted OBJ file (6)\n"); + return NULL; + } + + for (i = 0; i < *len; i++) + chksum -= (buf[i] & 0xff); + + if (rt11) { + c = fgetc(fp); + c &= 0xff; + chksum &= 0xff; + + if (c != chksum) { + free(buf); + fprintf(stderr, "Bad record checksum, " "calculated=$%04x, recorded=$%04x\n", chksum, c); + return NULL; + } + } else { + if (*len & 1) { + /* skip 1 byte of padding */ + c = fgetc(fp); + if (c == EOF) { + free(buf); + fprintf(stderr, "EOF where padding byte should be\n"); + return NULL; + } + + } + } + + return buf; } -void dump_words(unsigned addr, char *buf, int len) +void dump_bytes( + char *buf, + int len) { - int i, j; + int i, + j; - for(i = 0; i < len; i += 8) - { - printf("\t%6.6o: ", addr); + for (i = 0; i < len; i += 8) { + printf("\t%3.3o: ", i); + for (j = i; j < len && j < i + 8; j++) + printf("%3.3o ", buf[j] & 0xff); - for(j = i; j < len && j < i+8; j += 2) - { - if(len - j >= 2) - { - unsigned word = WORD(buf + j); - printf("%6.6o ", word); - } - else - printf("%3.3o ", buf[j] & 0xff); - } + printf("%*s", (i + 8 - j) * 4, ""); - printf("%*s", (i+8 - j) * 7 / 2, ""); + for (j = i; j < len && j < i + 8; j++) { + int c = buf[j] & 0xff; - for(j = i; j < len && j < i+8; j++) - { - int c = buf[j] & 0xff; - if(!isprint(c)) - c = '.'; - putchar(c); - } + if (!isprint(c)) + c = '.'; + putchar(c); + } - putchar('\n'); - addr += 8; - } -} - -void dump_bin(unsigned addr, char *buf, int len) -{ - int chksum; /* Checksum is negative sum of all - bytes including header and length */ - int FBR_LEAD1 = 1, FBR_LEAD2 = 0; - int i; - unsigned hdrlen = len + 6; - - for(i = 0; i < 8; i++) fputc (0, bin); - chksum = 0; - if(fputc(FBR_LEAD1, bin) == EOF) return; /* All recs begin with 1,0 */ - chksum -= FBR_LEAD1; - if(fputc(FBR_LEAD2, bin) == EOF) return; - chksum -= FBR_LEAD2; - - i = hdrlen & 0xff; /* length, lsb */ - chksum -= i; - if(fputc(i, bin) == EOF) return; - - i = (hdrlen >> 8) & 0xff; /* length, msb */ - chksum -= i; - if(fputc(i, bin) == EOF) return; - - i = addr & 0xff; /* origin, msb */ - chksum -= i; - if(fputc(i, bin) == EOF) return; - - i = (addr >> 8) & 0xff; /* origin, lsb */ - chksum -= i; - if(fputc(i, bin) == EOF) return; - - if ((len == 0) || (buf == NULL)) return; /* end of tape block */ - - i = fwrite(buf, 1, len, bin); - if(i < len) return; - - while(len > 0) /* All the data bytes */ - { - chksum -= *buf++ & 0xff; - len--; - } - - chksum &= 0xff; - - fputc(chksum, bin); /* Followed by the checksum byte */ - - return; /* Worked okay. */ -} - -void trim(char *buf) -{ - char *cp; - - for(cp = buf + strlen(buf); cp > buf; cp--) - { - if(cp[-1] != ' ') - break; - } - *cp = 0; + putchar('\n'); + } } -char **all_gsds = NULL; -int nr_gsds = 0; -int gsdsize = 0; - -void add_gsdline(char *line) +void dump_words( + unsigned addr, + char *buf, + int len) { - if(nr_gsds >= gsdsize || all_gsds == NULL) - { - gsdsize += 128; - all_gsds = realloc(all_gsds, gsdsize * sizeof(char *)); - if(all_gsds == NULL) - { - fprintf(stderr, "Out of memory\n"); - exit(EXIT_FAILURE); - } - } + int i, + j; - all_gsds[nr_gsds++] = line; + for (i = 0; i < len; i += 8) { + printf("\t%6.6o: ", addr); + + for (j = i; j < len && j < i + 8; j += 2) + if (len - j >= 2) { + unsigned word = WORD(buf + j); + + printf("%6.6o ", word); + } else + printf("%3.3o ", buf[j] & 0xff); + + printf("%*s", (i + 8 - j) * 7 / 2, ""); + + for (j = i; j < len && j < i + 8; j++) { + int c = buf[j] & 0xff; + + if (!isprint(c)) + c = '.'; + putchar(c); + } + + putchar('\n'); + addr += 8; + } } -void got_gsd(char *cp, int len) +void dump_bin( + unsigned addr, + char *buf, + int len) { - int i; - char *gsdline; + int chksum; /* Checksum is negative sum of all + bytes including header and length */ + int FBR_LEAD1 = 1, + FBR_LEAD2 = 0; + int i; + unsigned hdrlen = len + 6; - for(i = 2; i < len; i += 8) - { - char name[8]; - unsigned value; - unsigned flags; + for (i = 0; i < 8; i++) + fputc(0, bin); + chksum = 0; + if (fputc(FBR_LEAD1, bin) == EOF) + return; /* All recs begin with 1,0 */ + chksum -= FBR_LEAD1; + if (fputc(FBR_LEAD2, bin) == EOF) + return; + chksum -= FBR_LEAD2; - gsdline = malloc(256); - if(gsdline == NULL) - { - fprintf(stderr, "Out of memory\n"); - exit(EXIT_FAILURE); - } + i = hdrlen & 0xff; /* length, lsb */ + chksum -= i; + if (fputc(i, bin) == EOF) + return; - unrad50(WORD(cp+i), name); - unrad50(WORD(cp+i+2), name+3); - name[6] = 0; + i = (hdrlen >> 8) & 0xff; /* length, msb */ + chksum -= i; + if (fputc(i, bin) == EOF) + return; - value = WORD(cp+i+6); - flags = cp[i+4] & 0xff; + i = addr & 0xff; /* origin, msb */ + chksum -= i; + if (fputc(i, bin) == EOF) + return; - switch(cp[i+5] & 0xff) - { - case 0: - sprintf(gsdline, - "\tMODNAME %s=%o flags=%o\n", name, value, flags); - break; - case 1: - sprintf(gsdline, - "\tCSECT %s=%o flags=%o\n", name, value, flags); - break; - case 2: - sprintf(gsdline, - "\tISD %s=%o flags=%o\n", name, value, flags); - break; - case 3: - sprintf(gsdline, - "\tXFER %s=%o flags=%o\n", name, value, flags); - xferad = value; - break; - case 4: - sprintf(gsdline, - "\tGLOBAL %s=%o %s flags=%o\n", - name, value, cp[i+4] & 8 ? "DEF" : "REF", flags); - break; - case 5: - sprintf(gsdline, - "\tPSECT %s=%o flags=%o\n", name, value, flags); - psects[psectid] = strdup(name); - trim(psects[psectid++]); - break; - case 6: - sprintf(gsdline, - "\tIDENT %s=%o flags=%o\n", name, value, flags); - break; - case 7: - sprintf(gsdline, - "\tVSECT %s=%o flags=%o\n", name, value, flags); - break; - default: - sprintf(gsdline, - "\t***Unknown GSD entry type %d flags=%o\n", - cp[i+5] & 0xff, flags); - break; - } + i = (addr >> 8) & 0xff; /* origin, lsb */ + chksum -= i; + if (fputc(i, bin) == EOF) + return; - gsdline = realloc(gsdline, strlen(gsdline)+1); - add_gsdline(gsdline); - } + if ((len == 0) || (buf == NULL)) + return; /* end of tape block */ + i = fwrite(buf, 1, len, bin); + if (i < len) + return; + + while (len > 0) { /* All the data bytes */ + chksum -= *buf++ & 0xff; + len--; + } + + chksum &= 0xff; + + fputc(chksum, bin); /* Followed by the checksum byte */ + + return; /* Worked okay. */ } -int compare_gsdlines(const void *p1, const void *p2) +void trim( + char *buf) { - const char * const *l1 = p1, * const *l2 = p2; + char *cp; - return strcmp(*l1, *l2); + for (cp = buf + strlen(buf); cp > buf; cp--) + if (cp[-1] != ' ') + break; + *cp = 0; } -void got_endgsd(char *cp, int len) +char **all_gsds = NULL; +int nr_gsds = 0; +int gsdsize = 0; + +void add_gsdline( + char *line) { - int i; + if (nr_gsds >= gsdsize || all_gsds == NULL) { + gsdsize += 128; + all_gsds = memcheck(realloc(all_gsds, gsdsize * sizeof(char *))); + } - qsort(all_gsds, nr_gsds, sizeof(char *), compare_gsdlines); - - printf("GSD:\n"); - - for(i = 0; i < nr_gsds; i++) - { - fputs(all_gsds[i], stdout); - free(all_gsds[i]); - } - - printf("ENDGSD\n"); - - free(all_gsds); + all_gsds[nr_gsds++] = line; } -unsigned last_text_addr = 0; - -void got_text(char *cp, int len) +void got_gsd( + char *cp, + int len) { - unsigned addr = WORD(cp+2); + int i; + char *gsdline; - last_text_addr = addr; + for (i = 2; i < len; i += 8) { + char name[8]; + unsigned value; + unsigned flags; - printf("TEXT ADDR=%o LEN=%o\n", last_text_addr, len-4); + gsdline = memcheck(malloc(256)); - dump_words(last_text_addr, cp+4, len-4); - - if (bin) dump_bin(last_text_addr, cp+4, len-4); + unrad50(WORD(cp + i), name); + unrad50(WORD(cp + i + 2), name + 3); + name[6] = 0; + + value = WORD(cp + i + 6); + flags = cp[i + 4] & 0xff; + + switch (cp[i + 5] & 0xff) { + case 0: + sprintf(gsdline, "\tMODNAME %s=%o flags=%o\n", name, value, flags); + break; + case 1: + sprintf(gsdline, "\tCSECT %s=%o flags=%o\n", name, value, flags); + break; + case 2: + sprintf(gsdline, "\tISD %s=%o flags=%o\n", name, value, flags); + break; + case 3: + sprintf(gsdline, "\tXFER %s=%o flags=%o\n", name, value, flags); + xferad = value; + break; + case 4: + sprintf(gsdline, "\tGLOBAL %s=%o %s%s%s %s flags=%o\n", name, value, + flags & 01 ? "WEAK " : "", + flags & 04 ? "LIB " : "", + flags & 010 ? "DEF" : "REF", + flags & 040 ? "REL" : "ABS", + flags); + break; + case 5: + sprintf(gsdline, "\tPSECT %s=%o %s%s %s %s %s %s %s flags=%o\n", name, value, + flags & 01 ? "SAV " : "", + flags & 02 ? "LIB " : "", + flags & 04 ? "OVR" : "CON", + flags & 020 ? "RO" : "RW", + flags & 040 ? "REL" : "ABS", + flags & 0100 ? "GBL" : "LCL", + flags & 0200 ? "D" : "I", + flags); + psects[psectid] = memcheck(strdup(name)); + trim(psects[psectid++]); + psectid %= NPSECTS; + break; + case 6: + sprintf(gsdline, "\tIDENT %s=%o flags=%o\n", name, value, flags); + break; + case 7: + sprintf(gsdline, "\tVSECT %s=%o flags=%o\n", name, value, flags); + break; + case 010: + sprintf(gsdline, "\tCompletion Routine Name %s=%o flags=%o\n", name, value, flags); + break; + default: + sprintf(gsdline, "\t***Unknown GSD entry type %d flags=%o\n", cp[i + 5] & 0xff, flags); + break; + } + + gsdline = memcheck(realloc(gsdline, strlen(gsdline) + 1)); + add_gsdline(gsdline); + } } -void rad50name(char *cp, char *name) +int compare_gsdlines( + const void *p1, + const void *p2) { - unrad50(WORD(cp), name); - unrad50(WORD(cp+2), name+3); - name[6] = 0; - trim(name); + const char *const *l1 = p1, + *const *l2 = p2; + + return strcmp(*l1, *l2); } -void got_rld(char *cp, int len) +void got_endgsd( + char *cp, + int len) { - int i; - printf("RLD\n"); + int i; - for(i = 2; i < len;) - { - unsigned addr; - unsigned word; - unsigned disp = cp[i+1] & 0xff; - char name[8]; - char *byte; + (void)cp; + (void)len; - addr = last_text_addr + disp - 4; + if (nr_gsds == 0) { + return; + } - byte = ""; - if(cp[i] & 0200) - byte = " byte"; + qsort(all_gsds, nr_gsds, sizeof(char *), compare_gsdlines); - switch(cp[i] & 0x7f) - { - case 01: - printf("\tInternal%s %o=%o\n", byte, addr, WORD(cp+i+2)); - i += 4; - break; - case 02: - rad50name(cp+i+2, name); - printf("\tGlobal%s %o=%s\n", byte, addr, name); - i += 6; - break; - case 03: - printf("\tInternal displaced%s %o=%o\n", byte, addr, WORD(cp+i+2)); - i += 4; - badbin = 1; - break; - case 04: - rad50name(cp+i+2, name); - printf("\tGlobal displaced%s %o=%s\n", byte, addr, name); - i += 6; - badbin = 1; - break; - case 05: - rad50name(cp+i+2, name); - word = WORD(cp+i+6); - printf("\tGlobal plus offset%s %o=%s+%o\n", - byte, addr, name, word); - i += 8; - badbin = 1; - break; - case 06: - rad50name(cp+i+2, name); - word = WORD(cp+i+6); - printf("\tGlobal plus offset displaced%s %o=%s+%o\n", - byte, addr, name, word); - i += 8; - badbin = 1; - break; - case 07: - rad50name(cp+i+2, name); - word = WORD(cp+i+6); - printf("\tLocation counter definition %s+%o\n", - name, word); - i += 8; + printf("GSD:\n"); - last_text_addr = word; - break; - case 010: - word = WORD(cp+i+2); - printf("\tLocation counter modification %o\n", word); - i += 4; + for (i = 0; i < nr_gsds; i++) { + fputs(all_gsds[i], stdout); + free(all_gsds[i]); + } - last_text_addr = word; - break; - case 011: - printf("\t.LIMIT %o\n", addr); - i += 2; - break; - - case 012: - rad50name(cp+i+2, name); - printf("\tPSECT%s %o=%s\n", byte, addr, name); - i += 6; - badbin = 1; - break; - case 014: - rad50name(cp+i+2, name); - - printf("\tPSECT displaced%s %o=%s+%o\n", byte, addr, name, word); - i += 6; - badbin = 1; - break; - case 015: - rad50name(cp+i+2, name); - word = WORD(cp+i+6); - printf("\tPSECT plus offset%s %o=%s+%o\n", - byte, addr, name, word); - i += 8; - badbin = 1; - break; - case 016: - rad50name(cp+i+2, name); - word = WORD(cp+i+6); - printf("\tPSECT plus offset displaced%s %o=%s+%o\n", - byte, addr, name, word); - i += 8; - badbin = 1; - break; - - case 017: - badbin = 1; - printf("\tComplex%s %o=", byte, addr); - i += 2; - { - char *xp = cp + i; - int size; - for(;;) - { - size = 1; - switch(*xp) - { - case 000: - fputs("nop ", stdout); break; - case 001: - fputs("+ ", stdout); break; - case 002: - fputs("- ", stdout); break; - case 003: - fputs("* ", stdout); break; - case 004: - fputs("/ ", stdout); break; - case 005: - fputs("& ", stdout); break; - case 006: - fputs("! ", stdout); break; - case 010: - fputs("neg ", stdout); break; - case 011: - fputs("^C ", stdout); break; - case 012: - fputs("store ", stdout); break; - case 013: - fputs("store{disp} ", stdout); break; - - case 016: - rad50name(xp+1, name); - printf("%s ", name); - size = 5; - break; - - case 017: - assert((xp[1] & 0377) < psectid); - printf("%s:%o ", - psects[xp[1] & 0377], - WORD(xp+2)); - size = 4; - break; - - case 020: - printf("%o ", WORD(xp+1)); - size = 3; - break; - default: - printf("**UNKNOWN COMPLEX CODE** %o\n", *xp & 0377); - return; - } - i += size; - if(*xp == 012 || *xp == 013) - break; - xp += size; - } - fputc('\n', stdout); - break; - } - - default: - printf("\t***Unknown RLD code %o\n", cp[i] & 0xff); - return; - } - } + printf("ENDGSD\n"); + free(all_gsds); + all_gsds = NULL; + nr_gsds = 0; + gsdsize = 0; } -void got_isd(char *cp, int len) +unsigned last_text_addr = 0; + +void got_text( + char *cp, + int len) { - printf("ISD len=%o\n"); + unsigned addr = WORD(cp + 2); + + last_text_addr = addr; + + printf("TEXT ADDR=%o LEN=%o\n", last_text_addr, len - 4); + + dump_words(last_text_addr, cp + 4, len - 4); + + if (bin) + dump_bin(last_text_addr, cp + 4, len - 4); } -void got_endmod(char *cp, int len) +void rad50name( + char *cp, + char *name) { - printf("ENDMOD\n"); + unrad50(WORD(cp), name); + unrad50(WORD(cp + 2), name + 3); + name[6] = 0; + trim(name); } -void got_libhdr(char *cp, int len) +void got_rld( + char *cp, + int len) { - printf("LIBHDR\n"); + int i; + + printf("RLD\n"); + + for (i = 2; i < len;) { + unsigned addr; + unsigned word; + unsigned disp = cp[i + 1] & 0xff; + char name[8]; + char *byte; + + addr = last_text_addr + disp - 4; + + byte = ""; + if (cp[i] & 0200) + byte = " byte"; + + switch (cp[i] & 0x7f) { + case 01: + printf("\tInternal%s %o=%o\n", byte, addr, WORD(cp + i + 2)); + i += 4; + break; + case 02: + rad50name(cp + i + 2, name); + printf("\tGlobal%s %o=%s\n", byte, addr, name); + i += 6; + break; + case 03: + printf("\tInternal displaced%s %o=%o\n", byte, addr, WORD(cp + i + 2)); + i += 4; + badbin = 1; + break; + case 04: + rad50name(cp + i + 2, name); + printf("\tGlobal displaced%s %o=%s\n", byte, addr, name); + i += 6; + badbin = 1; + break; + case 05: + rad50name(cp + i + 2, name); + word = WORD(cp + i + 6); + printf("\tGlobal plus offset%s %o=%s+%o\n", byte, addr, name, word); + i += 8; + badbin = 1; + break; + case 06: + rad50name(cp + i + 2, name); + word = WORD(cp + i + 6); + printf("\tGlobal plus offset displaced%s %o=%s+%o\n", byte, addr, name, word); + i += 8; + badbin = 1; + break; + case 07: + rad50name(cp + i + 2, name); + word = WORD(cp + i + 6); + printf("\tLocation counter definition %s+%o\n", name, word); + i += 8; + + last_text_addr = word; + break; + case 010: + word = WORD(cp + i + 2); + printf("\tLocation counter modification %o\n", word); + i += 4; + + last_text_addr = word; + break; + case 011: + printf("\t.LIMIT %o\n", addr); + i += 2; + break; + + case 012: + rad50name(cp + i + 2, name); + printf("\tPSECT%s %o=%s\n", byte, addr, name); + i += 6; + badbin = 1; + break; + case 014: + rad50name(cp + i + 2, name); + + printf("\tPSECT displaced%s %o=%s\n", byte, addr, name); + i += 6; + badbin = 1; + break; + case 015: + rad50name(cp + i + 2, name); + word = WORD(cp + i + 6); + printf("\tPSECT plus offset%s %o=%s+%o\n", byte, addr, name, word); + i += 8; + badbin = 1; + break; + case 016: + rad50name(cp + i + 2, name); + word = WORD(cp + i + 6); + printf("\tPSECT plus offset displaced%s %o=%s+%o\n", byte, addr, name, word); + i += 8; + badbin = 1; + break; + + case 017: + badbin = 1; + printf("\tComplex%s %o=", byte, addr); + i += 2; { + char *xp = cp + i; + int size; + + for (;;) { + size = 1; + switch (*xp) { + case 000: + fputs("nop ", stdout); + break; + case 001: + fputs("+ ", stdout); + break; + case 002: + fputs("- ", stdout); + break; + case 003: + fputs("* ", stdout); + break; + case 004: + fputs("/ ", stdout); + break; + case 005: + fputs("& ", stdout); + break; + case 006: + fputs("! ", stdout); + break; + case 010: + fputs("neg ", stdout); + break; + case 011: + fputs("^C ", stdout); + break; + case 012: + fputs("store ", stdout); + break; + case 013: + fputs("store{disp} ", stdout); + break; + + case 016: + rad50name(xp + 1, name); + printf("%s ", name); + size = 5; + break; + + case 017: + assert((xp[1] & 0377) < psectid); + printf("%s:%o ", psects[xp[1] & 0377], WORD(xp + 2)); + size = 4; + break; + + case 020: + printf("%o ", WORD(xp + 1)); + size = 3; + break; + default: + printf("**UNKNOWN COMPLEX CODE** %o\n", *xp & 0377); + return; + } + i += size; + if (*xp == 012 || *xp == 013) + break; + xp += size; + } + fputc('\n', stdout); + break; + } + + default: + printf("\t***Unknown RLD code %o\n", cp[i] & 0xff); + return; + } + } } -void got_libend(char *cp, int len) +void got_isd( + char *cp, + int len) { - printf("LIBEND\n"); + (void)cp; + printf("ISD len=%o\n", len); } -int main(int argc, char *argv[]) +void got_endmod( + char *cp, + int len) { - int len; - FILE *fp; - char *cp; - - fp = fopen(argv[1], "rb"); - if(fp == NULL) - return EXIT_FAILURE; - if(argv[2]) - { - bin = fopen(argv[2], "wb"); - if(bin == NULL) return EXIT_FAILURE; - } - - while((cp = readrec(fp, &len)) != NULL) - { - switch(cp[0] & 0xff) - { - case 1: - got_gsd(cp, len); - break; - case 2: - got_endgsd(cp, len); - break; - case 3: - got_text(cp, len); - break; - case 4: - got_rld(cp, len); - break; - case 5: - got_isd(cp, len); - break; - case 6: - got_endmod(cp, len); - break; - case 7: - got_libhdr(cp, len); - break; - case 8: - got_libend(cp, len); - break; - default: - printf("Unknown record type %d\n", cp[0] & 0xff); - break; - } - - free(cp); - } - - if (bin) - { dump_bin (xferad, NULL, 0); - fclose (bin); - if (badbin) fprintf (stderr, "Probable errors in binary file\n"); - } - - fclose (fp); - return EXIT_SUCCESS; + (void)cp; + (void)len; + printf("ENDMOD\n"); +} + +void got_libhdr( + char *cp, + int len) +{ + (void)cp; + (void)len; + printf("LIBHDR\n"); +} + +void got_libend( + char *cp, + int len) +{ + (void)cp; + (void)len; + printf("LIBEND\n"); +} + +int main( + int argc, + char *argv[]) +{ + int len; + FILE *fp; + int arg; + int rt11 = DEFAULT_OBJECTFORMAT_RT11; + char *infile = 0; + char *outfile = 0; + + for (arg = 1; arg < argc; arg++) { + if (*argv[arg] == '-') { + char *cp; + + cp = argv[arg] + 1; + if (!strcasecmp(cp, "rt11")) { + rt11 = 1; + } else if (!strcasecmp(cp, "rsx")) { + rt11 = 0; + } else { + fprintf(stderr, "Unknown option %s\n", argv[arg]); + exit(EXIT_FAILURE); + } + } + else if (infile == 0) { + infile = argv[arg]; + } + else if (outfile == 0) { + outfile = argv[arg]; + } + else { + fprintf(stderr, "Extra parameter %s\n", argv[arg]); + exit(EXIT_FAILURE); + } + } + + if (infile == 0) { + fprintf(stderr, "Usage: dumpobj [ -rt11 ] [ -rsx ] input.obj [ output.obj ]\n"); + exit(1); + } + + fp = fopen(infile, "rb"); + if (fp == NULL) { + fprintf(stderr, "Unable to open %s\n", infile); + return EXIT_FAILURE; + } + if (outfile != 0) { + bin = fopen(outfile, "wb"); + if (bin == NULL) { + fprintf(stderr, "Unable to open %s\n", outfile); + return EXIT_FAILURE; + } + } + + char *cp; + + while ((cp = readrec(fp, &len, rt11)) != NULL) { + switch (cp[0] & 0xff) { + case 1: + got_gsd(cp, len); + break; + case 2: + got_endgsd(cp, len); + break; + case 3: + got_text(cp, len); + break; + case 4: + got_rld(cp, len); + break; + case 5: + got_isd(cp, len); + break; + case 6: + got_endmod(cp, len); + break; + case 7: + got_libhdr(cp, len); + break; + case 8: + got_libend(cp, len); + break; + default: + printf("Unknown record type %o\n", cp[0] & 0xff); + break; + } + + free(cp); + } + + if (bin) { + dump_bin(xferad, NULL, 0); + fclose(bin); + if (badbin) + fprintf(stderr, "Probable errors in binary file\n"); + } + + fclose(fp); + return EXIT_SUCCESS; } diff --git a/crossassemblers/macro11/extree.c b/crossassemblers/macro11/extree.c new file mode 100644 index 0000000..2ee90fe --- /dev/null +++ b/crossassemblers/macro11/extree.c @@ -0,0 +1,957 @@ +#include +#include +#include +#include + +#include "extree.h" /* my own definitions */ + +#include "util.h" +#include "assemble_globals.h" +#include "assemble_aux.h" +#include "listing.h" +#include "object.h" + +#define DEBUG_REGEXPR 0 + +/* Diagnostic: print an expression tree. I used this in various + places to help me diagnose parse problems, by putting in calls to + print_tree when I didn't understand why something wasn't working. + This is currently dead code, nothing calls it; but I don't want it + to go away. Hopefully the compiler will realize when it's dead, and + eliminate it. */ + +void print_tree( + FILE *printfile, + EX_TREE *tp, + int depth) +{ + SYMBOL *sym; + + if (printfile == NULL) { + return; + } + + if (tp == NULL) { + fprintf(printfile, "(null)"); + return; + } + + switch (tp->type) { + case EX_LIT: + fprintf(printfile, "%o", tp->data.lit & 0177777); + break; + + case EX_SYM: + case EX_TEMP_SYM: + sym = tp->data.symbol; + fprintf(printfile, "%s{%s%o:%s}", tp->data.symbol->label, symflags(sym), sym->value, + sym->section->label); + break; + + case EX_UNDEFINED_SYM: + fprintf(printfile, "%s{%o:undefined}", tp->data.symbol->label, tp->data.symbol->value); + break; + + case EX_COM: + fprintf(printfile, "^C<"); + print_tree(printfile, tp->data.child.left, depth + 4); + fprintf(printfile, ">"); + break; + + case EX_NEG: + fprintf(printfile, "-<"); + print_tree(printfile, tp->data.child.left, depth + 4); + fputc('>', printfile); + break; + + case EX_REG: + fprintf(printfile, "%%<"); + print_tree(printfile, tp->data.child.left, depth + 4); + fputc('>', printfile); + break; + + case EX_ERR: + fprintf(printfile, "{expression error}"); + if (tp->data.child.left) { + fputc('<', printfile); + print_tree(printfile, tp->data.child.left, depth + 4); + fputc('>', printfile); + } + break; + + case EX_ADD: + fputc('<', printfile); + print_tree(printfile, tp->data.child.left, depth + 4); + fputc('+', printfile); + print_tree(printfile, tp->data.child.right, depth + 4); + fputc('>', printfile); + break; + + case EX_SUB: + fputc('<', printfile); + print_tree(printfile, tp->data.child.left, depth + 4); + fputc('-', printfile); + print_tree(printfile, tp->data.child.right, depth + 4); + fputc('>', printfile); + break; + + case EX_MUL: + fputc('<', printfile); + print_tree(printfile, tp->data.child.left, depth + 4); + fputc('*', printfile); + print_tree(printfile, tp->data.child.right, depth + 4); + fputc('>', printfile); + break; + + case EX_DIV: + fputc('<', printfile); + print_tree(printfile, tp->data.child.left, depth + 4); + fputc('/', printfile); + print_tree(printfile, tp->data.child.right, depth + 4); + fputc('>', printfile); + break; + + case EX_AND: + fputc('<', printfile); + print_tree(printfile, tp->data.child.left, depth + 4); + fputc('&', printfile); + print_tree(printfile, tp->data.child.right, depth + 4); + fputc('>', printfile); + break; + + case EX_OR: + fputc('<', printfile); + print_tree(printfile, tp->data.child.left, depth + 4); + fputc('!', printfile); + print_tree(printfile, tp->data.child.right, depth + 4); + fputc('>', printfile); + break; + + case EX_LSH: + fputc('<', printfile); + print_tree(printfile, tp->data.child.left, depth + 4); + fputc('_', printfile); + print_tree(printfile, tp->data.child.right, depth + 4); + fputc('>', printfile); + break; + + default: + fprintf(printfile, "(node %d)", tp->type); + break; + } + + if (depth == 0) + fputc('\n', printfile); +} + +/* num_subtrees tells you how many subtrees this EX_TREE has. */ + +int num_subtrees( + EX_TREE *tp) +{ + switch (tp->type) { + case EX_UNDEFINED_SYM: + case EX_TEMP_SYM: + case EX_SYM: + case EX_LIT: + return 0; + break; + + case EX_COM: + case EX_NEG: + case EX_REG: + case EX_ERR: + return 1; + + case EX_ADD: + case EX_SUB: + case EX_MUL: + case EX_DIV: + case EX_AND: + case EX_OR: + case EX_LSH: + return 2; + } + + return 0; +} + +/* free_tree frees an expression tree. */ + +void free_tree( + EX_TREE *tp) +{ + if (!tp) + return; + + switch (num_subtrees(tp)) { + case 0: + switch (tp->type) { + case EX_UNDEFINED_SYM: + case EX_TEMP_SYM: + free(tp->data.symbol->label); + free(tp->data.symbol); + break; + + case EX_SYM: + case EX_LIT: + break; + + default: + assert(0); + break; + } + break; + case 2: + free_tree(tp->data.child.right); + /* FALLTHROUGH */ + case 1: + free_tree(tp->data.child.left); + break; + } + + free(tp); +} + +/* new_temp_sym allocates a new EX_TREE entry of type "TEMPORARY + SYMBOL" (slight semantic difference from "UNDEFINED"). */ + +static EX_TREE *new_temp_sym( + char *label, + SECTION *section, + unsigned value) +{ + SYMBOL *sym; + EX_TREE *tp; + + sym = memcheck(malloc(sizeof(SYMBOL))); + sym->label = memcheck(strdup(label)); + sym->flags = SYMBOLFLAG_DEFINITION; + sym->stmtno = stmtno; + sym->next = NULL; + sym->section = section; + sym->value = value; + + tp = new_ex_tree(EX_TEMP_SYM); + tp->data.symbol = sym; + + return tp; +} + +SYMBOL *dup_symbol( + SYMBOL *sym) +{ + SYMBOL *res; + + if (sym == NULL) { + return NULL; + } + + res = memcheck(malloc(sizeof(SYMBOL))); + res->label = memcheck(strdup(sym->label)); + res->flags = sym->flags; + res->stmtno = sym->stmtno; + res->next = NULL; + res->section = sym->section; + res->value = sym->value; + + return res; +} + + +EX_TREE *dup_tree( + EX_TREE *tp) +{ + EX_TREE *res = NULL; + + if (tp == NULL) { + return NULL; + } + + res = new_ex_tree(tp->type); + res->cp = tp->cp; + + switch (num_subtrees(tp)) { + case 0: + switch (tp->type) { + case EX_UNDEFINED_SYM: + case EX_TEMP_SYM: + res->data.symbol = dup_symbol(tp->data.symbol); + break; + + /* The symbol reference in EX_SYM is not freed in free_tree() */ + case EX_SYM: + res->data.symbol = tp->data.symbol; + break; + + case EX_LIT: + res->data.lit = tp->data.lit; + break; + + default: + assert(0); + break; + } + break; + case 2: + res->data.child.right = dup_tree(tp->data.child.right); + /* FALLTHROUGH */ + case 1: + res->data.child.left = dup_tree(tp->data.child.left); + break; + } + + return res; +} + +#define RELTYPE(tp) (((tp)->type == EX_SYM || (tp)->type == EX_TEMP_SYM) && \ + (tp)->data.symbol->section->flags & PSECT_REL) + +/* evaluate "evaluates" an EX_TREE, ideally trying to produce a + constant value, else a symbol plus an offset. + Leaves the input tree untouched and creates a new tree as result. */ +EX_TREE *evaluate_rec( + EX_TREE *tp, + int flags, + int *outflags) +{ + EX_TREE *res; + char *cp = tp->cp; + + switch (tp->type) { + case EX_SYM: + { + SYMBOL *sym = tp->data.symbol; + + /* Change some symbols to "undefined" */ + + if (flags & EVALUATE_DEFINEDNESS) { + int is_undefined = 0; + + /* I'd prefer this behavior, but MACRO.SAV is a bit too primitive. */ +#if 0 + /* A temporary symbol defined later is "undefined." */ + if (!(sym->flags & PERMANENT) && sym->stmtno > stmtno) + is_undefined = 1; +#endif + + /* A global symbol with no assignment is "undefined." */ + /* Go figure. */ + if (SYM_IS_IMPORTED(sym)) + is_undefined = 1; + + /* A symbol marked as undefined is undefined */ + if (sym->flags & SYMBOLFLAG_UNDEFINED) + is_undefined = 1; + + if (is_undefined) { + res = new_temp_sym(tp->data.symbol->label, tp->data.symbol->section, + tp->data.symbol->value); + res->type = EX_UNDEFINED_SYM; + break; + } + } + + /* Turn defined absolute symbol to a literal */ + if (!(sym->section->flags & PSECT_REL) + && !SYM_IS_IMPORTED(sym)) { + res = new_ex_lit(sym->value); + + if (sym->section->type == SECTION_REGISTER) { + *outflags |= EVALUATE_OUT_IS_REGISTER; + } + + break; + } + + /* Make a temp copy of any reference to "." since it might + change as complex relocatable expressions are written out + */ + if (strcmp(sym->label, ".") == 0) { + res = new_temp_sym(".", sym->section, sym->value); + break; + } + + /* Copy other symbol reference verbatim. */ + res = dup_tree(tp); + break; + } + + case EX_LIT: + res = dup_tree(tp); + break; + + case EX_TEMP_SYM: + case EX_UNDEFINED_SYM: + /* Copy temp and undefined symbols */ + res = new_temp_sym(tp->data.symbol->label, tp->data.symbol->section, tp->data.symbol->value); + res->type = tp->type; + break; + + case EX_COM: + /* Complement */ + tp = evaluate_rec(tp->data.child.left, flags, outflags); + if (tp->type == EX_LIT) { + /* Complement the literal */ + res = new_ex_lit(~tp->data.lit); + free_tree(tp); + } else { + /* Copy verbatim. */ + res = new_ex_una(EX_COM, tp); + } + + break; + + case EX_NEG: + tp = evaluate_rec(tp->data.child.left, flags, outflags); + if (tp->type == EX_LIT) { + /* negate literal */ + res = new_ex_lit((unsigned) -(int) tp->data.lit); + free_tree(tp); + } else if (tp->type == EX_TEMP_SYM || + (tp->type == EX_SYM && + (tp->data.symbol->flags & SYMBOLFLAG_DEFINITION))) { + /* Make a temp sym with the negative value of the given + sym (this works for symbols within relocatable sections + too) */ + res = new_temp_sym("*NEG", tp->data.symbol->section, (unsigned) -(int) tp->data.symbol->value); + res->cp = tp->cp; + free_tree(tp); + } else { + /* Copy verbatim. */ + res = new_ex_una(EX_NEG, tp); + } + break; + + case EX_REG: + res = evaluate_rec(tp->data.child.left, flags, outflags); + *outflags |= EVALUATE_OUT_IS_REGISTER; + break; + + case EX_ERR: + /* Copy */ + res = dup_tree(tp); + break; + + case EX_ADD: + { + EX_TREE *left, + *right; + + left = evaluate_rec(tp->data.child.left, flags, outflags); + right = evaluate_rec(tp->data.child.right, flags, outflags); + + /* Both literals? Sum them and return result. */ + if (left->type == EX_LIT && right->type == EX_LIT) { + res = new_ex_lit(left->data.lit + right->data.lit); + free_tree(left); + free_tree(right); + break; + } + + /* Commutative: A+x == x+A. + Simplify by putting the literal on the right */ + if (left->type == EX_LIT) { + EX_TREE *temp = left; + + left = right; + right = temp; + } + + if (right->type == EX_LIT && /* Anything plus 0 == itself */ + right->data.lit == 0) { + res = left; + free_tree(right); + break; + } + + /* Relative symbol plus lit is replaced with a temp sym + holding the sum */ + if (RELTYPE(left) && right->type == EX_LIT) { + SYMBOL *sym = left->data.symbol; + + res = new_temp_sym("*ADD", sym->section, sym->value + right->data.lit); + free_tree(left); + free_tree(right); + break; + } + + /* Associative: +y == A+ */ + /* and if x+y is constant, I can do that math. */ + if (left->type == EX_ADD && right->type == EX_LIT) { + EX_TREE *leftright = left->data.child.right; + + if (leftright->type == EX_LIT) { + /* Do the shuffle */ + res = left; + leftright->data.lit += right->data.lit; + free_tree(right); + break; + } + } + + /* Associative: +y == A+ */ + /* and if y-x is constant, I can do that math. */ + if (left->type == EX_SUB && right->type == EX_LIT) { + EX_TREE *leftright = left->data.child.right; + + if (leftright->type == EX_LIT) { + /* Do the shuffle */ + res = left; + leftright->data.lit = right->data.lit - leftright->data.lit; + free_tree(right); + break; + } + } + + /* Anything else returns verbatim */ + res = new_ex_bin(EX_ADD, left, right); + } + break; + + case EX_SUB: + { + EX_TREE *left, + *right; + + left = evaluate_rec(tp->data.child.left, flags, outflags); + right = evaluate_rec(tp->data.child.right, flags, outflags); + + /* Both literals? Subtract them and return a lit. */ + if (left->type == EX_LIT && right->type == EX_LIT) { + res = new_ex_lit(left->data.lit - right->data.lit); + free_tree(left); + free_tree(right); + break; + } + + if (right->type == EX_LIT && /* Symbol minus 0 == symbol */ + right->data.lit == 0) { + res = left; + free_tree(right); + break; + } + + /* A relocatable minus an absolute - make a new temp sym + to represent that. */ + if (RELTYPE(left) && right->type == EX_LIT) { + SYMBOL *sym = left->data.symbol; + + res = new_temp_sym("*SUB", sym->section, sym->value - right->data.lit); + free_tree(left); + free_tree(right); + break; + } + + if (RELTYPE(left) && RELTYPE(right) && left->data.symbol->section == right->data.symbol->section) { + /* Two defined symbols in the same psect. Resolve + their difference as a literal. */ + res = new_ex_lit(left->data.symbol->value - right->data.symbol->value); + free_tree(left); + free_tree(right); + break; + } + + /* Associative: -y == A+ */ + /* and if x-y is constant, I can do that math. */ + if (left->type == EX_ADD && right->type == EX_LIT) { + EX_TREE *leftright = left->data.child.right; + + if (leftright->type == EX_LIT) { + /* Do the shuffle */ + res = left; + leftright->data.lit -= right->data.lit; + free_tree(right); + break; + } + } + + /* Associative: -y == A- */ + /* and if x+y is constant, I can do that math. */ + if (left->type == EX_SUB && right->type == EX_LIT) { + EX_TREE *leftright = left->data.child.right; + + if (leftright->type == EX_LIT) { + /* Do the shuffle */ + res = left; + leftright->data.lit += right->data.lit; + free_tree(right); + break; + } + } + + /* Anything else returns verbatim */ + res = new_ex_bin(EX_SUB, left, right); + } + break; + + case EX_MUL: + { + EX_TREE *left, + *right; + + left = evaluate_rec(tp->data.child.left, flags, outflags); + right = evaluate_rec(tp->data.child.right, flags, outflags); + + /* Can only multiply if both are literals */ + if (left->type == EX_LIT && right->type == EX_LIT) { + res = new_ex_lit(left->data.lit * right->data.lit); + free_tree(left); + free_tree(right); + break; + } + + /* Commutative: A*x == x*A. + Simplify by putting the literal on the right */ + if (left->type == EX_LIT) { + EX_TREE *temp = left; + + left = right; + right = temp; + } + + if (right->type == EX_LIT && /* Symbol times 1 == symbol */ + right->data.lit == 1) { + res = left; + free_tree(right); + break; + } + + if (right->type == EX_LIT && /* Symbol times 0 == 0 */ + right->data.lit == 0) { + res = right; + free_tree(left); + break; + } + + /* Associative: *y == A* */ + /* If x*y is constant, I can do this math. */ + /* Is this safe? I will potentially be doing it */ + /* with greater accuracy than the target platform. */ + /* Hmmm. */ + + if (left->type == EX_MUL && right->type == EX_LIT) { + EX_TREE *leftright = left->data.child.right; + + if (leftright->type == EX_LIT) { + /* Do the shuffle */ + res = left; + leftright->data.lit *= right->data.lit; + free_tree(right); + break; + } + } + + /* Anything else returns verbatim */ + res = new_ex_bin(EX_MUL, left, right); + } + break; + + case EX_DIV: + { + EX_TREE *left, + *right; + + left = evaluate_rec(tp->data.child.left, flags, outflags); + right = evaluate_rec(tp->data.child.right, flags, outflags); + + /* Can only divide if both are literals */ + if (left->type == EX_LIT && right->type == EX_LIT) { + res = new_ex_lit(left->data.lit / right->data.lit); + free_tree(left); + free_tree(right); + break; + } + + if (right->type == EX_LIT && /* Symbol divided by 1 == symbol */ + right->data.lit == 1) { + res = left; + free_tree(right); + break; + } + + /* Anything else returns verbatim */ + res = new_ex_bin(EX_DIV, left, right); + } + break; + + case EX_AND: + { + EX_TREE *left, + *right; + + left = evaluate_rec(tp->data.child.left, flags, outflags); + right = evaluate_rec(tp->data.child.right, flags, outflags); + + /* Operate if both are literals */ + if (left->type == EX_LIT && right->type == EX_LIT) { + res = new_ex_lit(left->data.lit & right->data.lit); + free_tree(left); + free_tree(right); + break; + } + + /* Commutative: A&x == x&A. + Simplify by putting the literal on the right */ + if (left->type == EX_LIT) { + EX_TREE *temp = left; + + left = right; + right = temp; + } + + if (right->type == EX_LIT && /* Symbol AND 0 == 0 */ + right->data.lit == 0) { + res = new_ex_lit(0); + free_tree(left); + free_tree(right); + break; + } + + if (right->type == EX_LIT && /* Symbol AND 0177777 == symbol */ + right->data.lit == 0177777) { + res = left; + free_tree(right); + break; + } + + /* Anything else returns verbatim */ + res = new_ex_bin(EX_AND, left, right); + } + break; + + case EX_OR: + { + EX_TREE *left, + *right; + + left = evaluate_rec(tp->data.child.left, flags, outflags); + right = evaluate_rec(tp->data.child.right, flags, outflags); + + /* Operate if both are literals */ + if (left->type == EX_LIT && right->type == EX_LIT) { + res = new_ex_lit(left->data.lit | right->data.lit); + free_tree(left); + free_tree(right); + break; + } + + /* Commutative: A!x == x!A. + Simplify by putting the literal on the right */ + if (left->type == EX_LIT) { + EX_TREE *temp = left; + + left = right; + right = temp; + } + + if (right->type == EX_LIT && /* Symbol OR 0 == symbol */ + right->data.lit == 0) { + res = left; + free_tree(right); + break; + } + + if (right->type == EX_LIT && /* Symbol OR 0177777 == 0177777 */ + right->data.lit == 0177777) { + res = new_ex_lit(0177777); + free_tree(left); + free_tree(right); + break; + } + + /* Anything else returns verbatim */ + res = new_ex_bin(EX_OR, left, right); + } + break; + + case EX_LSH: + { + EX_TREE *left, + *right; + + left = evaluate_rec(tp->data.child.left, flags, outflags); + right = evaluate_rec(tp->data.child.right, flags, outflags); + + /* Operate if both are literals */ + if (left->type == EX_LIT && right->type == EX_LIT) { + int shift = right->data.lit; + if (shift < 0) + res = new_ex_lit(left->data.lit >> -shift); + else + res = new_ex_lit(left->data.lit << shift); + free_tree(left); + free_tree(right); + break; + } + + if (right->type == EX_LIT && /* Symbol shifted 0 == symbol */ + right->data.lit == 0) { + res = left; + free_tree(right); + break; + } + + if (right->type == EX_LIT && /* Anything shifted 16 == 0 */ + ((int)right->data.lit > 15 || + (int)right->data.lit < -15)) { + res = new_ex_lit(0); + free_tree(left); + free_tree(right); + break; + } + + if (right->type == EX_LIT) { /* Other shifts become * or / */ + int shift = right->data.lit; + if (shift > 0) + res = new_ex_bin(EX_MUL, left, new_ex_lit(1 << shift)); + else + res = new_ex_bin(EX_DIV, left, new_ex_lit(1 << -shift)); + free_tree(right); + break; + } + + /* Anything else returns verbatim */ + res = new_ex_bin(EX_LSH, left, right); + } + break; + + default: + fprintf(stderr, "evaluate_rec: Invalid tree: "); + print_tree(stderr, tp, 0); + return NULL; + } + + res->cp = cp; + + return res; +} + +EX_TREE *evaluate( + EX_TREE *tp, + int flags) +{ + EX_TREE *res; + int outflags = 0; + +#if DEBUG_REGEXPR + fprintf(stderr, "evaluate: "); + print_tree(stderr, tp, 0); +#endif /* DEBUG_REGEXPR */ + /* + * Check the common simple case for register references: R0...PC. + * Copy those directly, without going into the recursion. + * Therefore, in the recursion it can be assumed that the expression + * is more complex, and it is worth the overhead of converting them + * to an EX_LIT plus the EVALUATE_OUT_IS_REGISTER flag + * (and back again, up here). + */ + if (tp->type == EX_SYM && + tp->data.symbol->section->type == SECTION_REGISTER && + !SYM_IS_IMPORTED(tp->data.symbol)) { + res = dup_tree(tp); + } else { + res = evaluate_rec(tp, flags, &outflags); + + if (outflags & EVALUATE_OUT_IS_REGISTER) { + int regno = get_register(res); + + if (regno == NO_REG) { + report(NULL, "Register expression out of range.\n"); +#if DEBUG_REGEXPR + print_tree(stderr, tp, 0); + print_tree(stderr, res, 0); +#endif /* DEBUG_REGEXPR */ + /* TODO: maybe make this a EX_TEMP_SYM? */ + res = ex_err(res, res->cp); + } else { + EX_TREE *newresult = new_ex_tree(EX_SYM); + + newresult->cp = res->cp; + newresult->data.symbol = reg_sym[regno]; + free_tree(res); + res = newresult; + } + } + } + + return res; +} + + +/* Allocate an EX_TREE */ + +EX_TREE *new_ex_tree( + int type) +{ + EX_TREE *tr = memcheck(calloc(1, sizeof(EX_TREE))); + + tr->type = type; + + return tr; +} + + +/* Create an EX_TREE representing a parse error */ + +EX_TREE *ex_err( + EX_TREE *tp, + char *cp) +{ + EX_TREE *errtp; + + errtp = new_ex_tree(EX_ERR); + errtp->cp = cp; + errtp->data.child.left = tp; + + return errtp; +} + +/* Create an EX_TREE representing a literal value */ + +EX_TREE *new_ex_lit( + unsigned value) +{ + EX_TREE *tp; + + tp = new_ex_tree(EX_LIT); + tp->data.lit = value; + + return tp; +} + +/* Create an EX_TREE representing a binary expression */ + +EX_TREE *new_ex_bin( + int type, + EX_TREE *left, + EX_TREE *right) +{ + EX_TREE *tp; + + tp = new_ex_tree(type); + tp->data.child.left = left; + tp->data.child.right = right; + + tp->cp = right->cp; + + return tp; +} + +/* Create an EX_TREE representing a unary expression */ + +EX_TREE *new_ex_una( + int type, + EX_TREE *left) +{ + EX_TREE *tp; + + tp = new_ex_tree(type); + tp->data.child.left = left; + tp->data.child.right = NULL; + + tp->cp = left->cp; + + return tp; +} + diff --git a/crossassemblers/macro11/extree.h b/crossassemblers/macro11/extree.h new file mode 100644 index 0000000..6dce952 --- /dev/null +++ b/crossassemblers/macro11/extree.h @@ -0,0 +1,84 @@ + +#ifndef EXTREE__H +#define EXTREE__H + +#include "symbols.h" + +typedef struct ex_tree { + enum ex_type { + EX_LIT = 1, + /* Expression is a literal value */ + EX_SYM = 2, + /* Expression has a symbol reference + * (symbol from symbol table, so not freed) */ + EX_UNDEFINED_SYM = 3, + /* Expression is undefined sym reference */ + EX_TEMP_SYM = 4, + /* Expression is temp sym reference */ + + EX_COM = 5, + /* One's complement */ + EX_NEG = 6, + /* Negate */ + EX_REG = 7, + /* register value (%) */ + EX_ERR = 8, + /* Expression with an error */ + + EX_ADD = 9, + /* Add */ + EX_SUB = 10, + /* Subtract */ + EX_MUL = 11, + /* Multiply */ + EX_DIV = 12, + /* Divide */ + EX_AND = 13, + /* bitwise and */ + EX_OR = 14, + /* bitwise or */ + EX_LSH = 15, + /* left shift */ + } type; + + char *cp; /* points to end of parsed expression */ + + union { + struct { + struct ex_tree *left, + *right; /* Left, right children */ + } child; + unsigned lit; /* Literal value */ + SYMBOL *symbol; /* Symbol reference */ + } data; +} EX_TREE; + + +EX_TREE *new_ex_tree( + int type); +void free_tree( + EX_TREE *tp); + +EX_TREE *new_ex_lit( + unsigned value); +EX_TREE *ex_err( + EX_TREE *tp, + char *cp); +EX_TREE *new_ex_bin( + int type, + EX_TREE *left, + EX_TREE *right); +EX_TREE *new_ex_una( + int type, + EX_TREE *left); +int num_subtrees( + EX_TREE *tp); +EX_TREE *evaluate( + EX_TREE *tp, + int flags); + +#define EVALUATE_DEFINEDNESS 1 + +#define EVALUATE_OUT_IS_REGISTER 1 + +#endif diff --git a/crossassemblers/macro11/listing.c b/crossassemblers/macro11/listing.c new file mode 100644 index 0000000..cd34d16 --- /dev/null +++ b/crossassemblers/macro11/listing.c @@ -0,0 +1,198 @@ +#define LISTING__C + +#include +#include +#include +#include + +#include "listing.h" /* my own definitions */ + +#include "util.h" +#include "assemble_globals.h" + + +/* GLOBAL VARIABLES */ + +int list_md = 1; /* option to list macro/rept definition = yes */ + +int list_me = 1; /* option to list macro/rept expansion = yes */ + +int list_bex = 1; /* option to show binary */ + +int list_level = 1; /* Listing control level. .LIST + increments; .NLIST decrements */ + +static char *listline; /* Source lines */ + +static char *binline; /* for octal expansion */ + +FILE *lstfile = NULL; + +int list_pass_0 = 0;/* Also list what happens during the first pass */ + +static int errline = 0; /* Set if current line has an error */ + +/* maybe_list returns TRUE if listing may happen for this line. */ + +static int can_list( + void) +{ + int ok = lstfile != NULL && + (pass > 0 || list_pass_0); + + return ok; +} + +/* do_list returns TRUE if listing is enabled. */ + +static int dolist( + void) +{ + int ok = can_list () && + (list_level > 0 || errline); + + return ok; +} + +/* list_source saves a text line for later listing by list_flush */ + +void list_source( + STREAM *str, + char *cp) +{ + if (can_list()) { + int len = strcspn(cp, "\n"); + + /* Not an error yet */ + errline = 0; + /* Save the line text away for later... */ + if (listline) + free(listline); + listline = memcheck(malloc(len + 1)); + memcpy(listline, cp, len); + listline[len] = 0; + + if (!binline) + binline = memcheck(malloc(sizeof(LSTFORMAT) + 16)); + + sprintf(binline, "%*s%*d", (int)SIZEOF_MEMBER(LSTFORMAT, flag), "", (int)SIZEOF_MEMBER(LSTFORMAT, line_number), + str->line); + } +} + +/* list_flush produces a buffered list line. */ + +void list_flush( + void) +{ + if (dolist()) { + padto(binline, offsetof(LSTFORMAT, source)); + fputs(binline, lstfile); + fputs(listline, lstfile); + fputc('\n', lstfile); + listline[0] = 0; + binline[0] = 0; + } +} + +/* list_fit checks to see if a word will fit in the current listing + line. If not, it flushes and prepares another line. */ + +static void list_fit( + STREAM *str, + unsigned addr) +{ + size_t col1 = offsetof(LSTFORMAT, source); + size_t col2 = offsetof(LSTFORMAT, pc); + + if (strlen(binline) >= col1) { + list_flush(); + listline[0] = 0; + binline[0] = 0; + sprintf(binline, "%*s %6.6o", (int)offsetof(LSTFORMAT, pc), "", addr); + padto(binline, offsetof(LSTFORMAT, words)); + } else if (strlen(binline) <= col2) { + sprintf(binline, "%*s%*d %6.6o", (int)SIZEOF_MEMBER(LSTFORMAT, flag), "", + (int)SIZEOF_MEMBER(LSTFORMAT, line_number), str->line, addr); + padto(binline, offsetof(LSTFORMAT, words)); + } +} + +/* list_value is used to show a computed value */ + +void list_value( + STREAM *str, + unsigned word) +{ + if (dolist()) { + /* Print the value and go */ + binline[0] = 0; + sprintf(binline, "%*s%*d %6.6o", (int)SIZEOF_MEMBER(LSTFORMAT, flag), "", + (int)SIZEOF_MEMBER(LSTFORMAT, line_number), str->line, word & 0177777); + } +} + +/* Print a word to the listing file */ + +void list_word( + STREAM *str, + unsigned addr, + unsigned value, + int size, + char *flags) +{ + if (dolist()) { + list_fit(str, addr); + if (size == 1) + sprintf(binline + strlen(binline), " %3.3o%1.1s ", value & 0377, flags); + else + sprintf(binline + strlen(binline), "%6.6o%1.1s ", value & 0177777, flags); + } +} + + +/* Print just a line with the address to the listing file */ + +void list_location( + STREAM *str, + unsigned addr) +{ + if (dolist()) { + list_fit(str, addr); + } +} + + + +/* reports errors */ +void report( + STREAM *str, + char *fmt, + ...) +{ + va_list ap; + char *name = "**"; + int line = 0; + + if (!pass && list_pass_0 < 2) + return; /* Don't report now. */ + + errline = 1; + + if (str) { + name = str->name; + line = str->line; + } + + fprintf(stderr, "%s:%d: ***ERROR ", name, line); + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + + if (lstfile) { + fprintf(lstfile, "%s:%d: ***ERROR ", name, line); + va_start(ap, fmt); + vfprintf(lstfile, fmt, ap); + va_end(ap); + } +} diff --git a/crossassemblers/macro11/listing.h b/crossassemblers/macro11/listing.h new file mode 100644 index 0000000..184edd5 --- /dev/null +++ b/crossassemblers/macro11/listing.h @@ -0,0 +1,69 @@ + +#ifndef LISTING__H +#define LISTING__H + +#include "stream2.h" + +/* + format of a listing line + Interestingly, no instances of this struct are ever created. + It lives to be a way to layout the format of a list line. + I wonder if I should have bothered. +*/ + +typedef struct lstformat { + char flag[2]; /* Error flags */ + char line_number[6]; /* Line number */ + char pc[8]; /* Location */ + char words[8][3]; /* three instruction words */ + char source[1]; /* source line */ +} LSTFORMAT; + + +/* GLOBAL VARIABLES */ +#ifndef LISTING__C +extern int list_md; /* option to list macro/rept definition = yes */ + +extern int list_me; /* option to list macro/rept expansion = yes */ + +extern int list_bex; /* option to show binary */ + +extern int list_level; /* Listing control level. .LIST + increments; .NLIST decrements */ + +extern FILE *lstfile; + +extern int list_pass_0; /* Also list what happens during the first pass */ + +#endif + + +void list_word( + STREAM *str, + unsigned addr, + unsigned value, + int size, + char *flags); + +void list_value( + STREAM *str, + unsigned word); + +void list_location( + STREAM *str, + unsigned word); + +void list_source( + STREAM *str, + char *cp); + +void list_flush( + void); + +void report( + STREAM *str, + char *fmt, + ...); + + +#endif diff --git a/crossassemblers/macro11/macro11.c b/crossassemblers/macro11/macro11.c index 8283e6f..5444bb9 100644 --- a/crossassemblers/macro11/macro11.c +++ b/crossassemblers/macro11/macro11.c @@ -1,5 +1,5 @@ /* - Assembler compatible with MACRO-11. + Assembler compatible with MACRO-11. Copyright (c) 2001, Richard Krehbiel All rights reserved. @@ -34,6164 +34,392 @@ DAMAGE. */ -#include -#include #include #include #include -#include #include -#include -#include #include "macro11.h" -#include "rad50.h" - -#include "object.h" - -#include "stream2.h" - -#include "mlb.h" - #include "util.h" -#define SYMMAX 6 /* I will honor this many character - symbols */ - -#define issym(c) (isalpha(c) || isdigit(c) || (c) == '.' || (c) == '$') - -/* Program sections: */ - -typedef struct section -{ - char *label; /* Section name */ - unsigned type; /* Section type */ -#define USER 1 /* user-defined */ -#define SYSTEM 2 /* A system symbol (like "."; value is an - enum) */ -#define INSTRUCTION 3 /* An instruction code (like "MOV"; value is - an enum) */ -#define PSEUDO 4 /* A pseudo-op (.PSECT, .TITLE, .MACRO, .IF; - value is an enum) */ -#define REGISTER 5 /* Symbol is a register (value 0=$0, value - 1=$1, ... $7) */ -#define USERMACRO 6 /* Symbol is a user macro */ - - unsigned flags; /* Flags, defined in object.h */ - unsigned pc; /* Current offset in the section */ - unsigned size; /* Current section size */ - unsigned sector; /* Used for complex relocation, and naught else */ -} SECTION; - -/* Symbol table entries */ - -typedef struct symbol -{ - char *label; /* Symbol name */ - unsigned value; /* Symbol value */ - int stmtno; /* Statement number of symbol's definition */ - unsigned flags; /* Symbol flags */ -#define PERMANENT 1 /* Symbol may not be redefined */ -#define GLOBAL 2 /* Symbol is global */ -#define WEAK 4 /* Symbol definition is weak */ -#define DEFINITION 8 /* Symbol is a global definition, not - reference */ -#define UNDEFINED 16 /* Symbol is a phony, undefined */ -#define LOCAL 32 /* Set if this is a local label (i.e. 10$) */ - - SECTION *section; /* Section in which this symbol is defined */ - struct symbol *next; /* Next symbol with the same hash value */ -} SYMBOL; - -/* Arguments given to macros or .IRP/.IRPC blocks */ - -typedef struct arg -{ - struct arg *next; /* Pointer in arg list */ - int locsym; /* Whether arg represents an optional - local symbol */ - char *label; /* Argument name */ - char *value; /* Default or active substitution */ -} ARG; - -/* A MACRO is a superstructure surrounding a SYMBOL. */ - -typedef struct macro -{ - SYMBOL sym; /* Surrounds a symbol, contains the macro - name */ - ARG *args; /* The argument list */ - BUFFER *text; /* The macro text */ -} MACRO; - -typedef struct ex_tree -{ - enum ex_type - { - EX_LIT=1, /* Expression is a literal value */ - EX_SYM=2, /* Expression has a symbol reference */ - EX_UNDEFINED_SYM=3, /* Expression is undefined sym reference */ - EX_TEMP_SYM=4, /* Expression is temp sym reference */ - - EX_COM=5, /* One's complement */ - EX_NEG=6, /* Negate */ - EX_ERR=7, /* Expression with an error */ - - EX_ADD=8, /* Add */ - EX_SUB=9, /* Subtract */ - EX_MUL=10, /* Multiply */ - EX_DIV=11, /* Divide */ - EX_AND=12, /* bitwise and */ - EX_OR=13 /* bitwise or */ - } type; - - char *cp; /* points to end of parsed expression */ - - union - { - struct - { - struct ex_tree *left, *right; /* Left, right children */ - } child; - unsigned lit; /* Literal value */ - SYMBOL *symbol; /* Symbol reference */ - } data; - -} EX_TREE; - -typedef struct addr_mode -{ - unsigned type; /* The bits that represent the addressing mode */ - /* bits 0:2 are register number */ - /* bit 3 is indirect */ - /* bits 4:6 are mode, where 0=Rn, 1=(Rn)+, - 2=-(Rn), 3=offset(Rn) */ - int rel; /* the addressing mode is PC-relative */ - EX_TREE *offset; /* Expression giving the offset */ -} ADDR_MODE; - -#define FALSE 0 /* Everybody needs FALSE and TRUE */ -#define TRUE 1 - -enum pseudo_ops -{ - P_ASCII, P_ASCIZ, P_ASECT, P_BLKB, P_BLKW, P_BYTE, P_CSECT, P_DSABL, - P_ENABL, P_END, P_ENDC, P_ENDM, P_ENDR, P_EOT, P_ERROR, P_EVEN, - P_FLT2, P_FLT4, P_GLOBL, P_IDENT, P_IF, P_IFF, P_IFT, P_IFTF, P_IIF, - P_IRP, P_IRPC, P_LIMIT, P_LIST, P_MCALL, P_MEXIT, P_NARG, P_NCHR, - P_NLIST, P_NTYPE, P_ODD, P_PACKED, P_PAGE, P_PRINT, P_PSECT, P_RADIX, - P_RAD50, P_REM, P_REPT, P_RESTORE, P_SAVE, P_SBTTL, P_TITLE, - P_WORD, P_MACRO, P_INCLU, P_WEAK, P_IFDF -}; - -enum instruction_ops -{ - I_ADC = 0005500, I_ADCB = 0105500, I_ADD = 0060000, I_ASH = 0072000, - I_ASHC = 0073000, I_ASL = 0006300, I_ASLB = 0106300, I_ASR = 0006200, - I_ASRB = 0106200, I_BCC = 0103000, I_BCS = 0103400, I_BEQ = 0001400, - I_BGE = 0002000, I_BGT = 0003000, I_BHI = 0101000, I_BHIS = 0103000, - I_BIC = 0040000, I_BICB = 0140000, I_BIS = 0050000, I_BISB = 0150000, - I_BIT = 0030000, I_BITB = 0130000, I_BLE = 0003400, I_BLO = 0103400, - I_BLOS = 0101400, I_BLT = 0002400, I_BMI = 0100400, I_BNE = 0001000, - I_BPL = 0100000, I_BPT = 0000003, I_BR = 0000400, I_BVC = 0102000, - I_BVS = 0102400, I_CALL = 0004700, I_CALLR = 0000100, I_CCC = 0000257, - I_CLC = 0000241, I_CLN = 0000250, I_CLR = 0005000, I_CLRB = 0105000, - I_CLV = 0000242, I_CLZ = 0000244, I_CMP = 0020000, I_CMPB = 0120000, - I_COM = 0005100, I_COMB = 0105100, I_DEC = 0005300, I_DECB = 0105300, - I_DIV = 0071000, I_EMT = 0104000, I_FADD = 0075000, I_FDIV = 0075030, - I_FMUL = 0075020, I_FSUB = 0075010, I_HALT = 0000000, I_INC = 0005200, - I_INCB = 0105200, I_IOT = 0000004, I_JMP = 0000100, I_JSR = 0004000, - I_MARK = 0006400, I_MED6X = 0076600, I_MED74C= 0076601, I_MFPD = 0106500, - I_MFPI = 0006500, I_MFPS = 0106700, I_MOV = 0010000, I_MOVB = 0110000, - I_MTPD = 0106600, I_MTPI = 0006600, I_MTPS = 0106400, I_MUL = 0070000, - I_NEG = 0005400, I_NEGB = 0105400, I_NOP = 0000240, I_RESET = 0000005, - I_RETURN= 0000207, I_ROL = 0006100, I_ROLB = 0106100, I_ROR = 0006000, - I_RORB = 0106000, I_RTI = 0000002, I_RTS = 0000200, I_RTT = 0000006, - I_SBC = 0005600, I_SBCB = 0105600, I_SCC = 0000277, I_SEC = 0000261, - I_SEN = 0000270, I_SEV = 0000262, I_SEZ = 0000264, I_SOB = 0077000, - I_SPL = 0000230, I_SUB = 0160000, I_SWAB = 0000300, I_SXT = 0006700, - I_TRAP = 0104400, I_TST = 0005700, I_TSTB = 0105700, I_WAIT = 0000001, - I_XFC = 0076700, I_XOR = 0074000, I_MFPT = 0000007, - /* CIS not implemented - maybe later */ - /* FPU */ - I_ABSD = 0170600, I_ABSF = 0170600, I_ADDD = 0172000, I_ADDF = 0172000, - I_CFCC = 0170000, I_CLRD = 0170400, I_CLRF = 0170400, I_CMPD = 0173400, - I_CMPF = 0173400, I_DIVD = 0174400, I_DIVF = 0174400, I_LDCDF = 0177400, - I_LDCFD = 0177400, I_LDCID = 0177000, I_LDCIF = 0177000, I_LDCLD = 0177000, - I_LDCLF = 0177000, I_LDD = 0172400, I_LDEXP = 0176400, I_LDF = 0172400, - I_LDFPS = 0170100, I_MODD = 0171400, I_MODF = 0171400, I_MULD = 0171000, - I_MULF = 0171000, I_NEGD = 0170700, I_NEGF = 0170700, I_SETD = 0170011, - I_SETF = 0170001, I_SETI = 0170002, I_SETL = 0170012, I_STA0 = 0170005, - I_STB0 = 0170006, I_STCDF = 0176000, I_STCDI = 0175400, I_STCDL = 0175400, - I_STCFD = 0176000, I_STCFI = 0175400, I_STCFL = 0175400, I_STD = 0174000, - I_STEXP = 0175000, I_STF = 0174000, I_STFPS = 0170200, I_STST = 0170300, - I_SUBD = 0173000, I_SUBF = 0173000, I_TSTD = 0170500, I_TSTF = 0170500 -}; - -enum operand_codes -{ - OC_MASK = 0xff00, /* mask over flags for operand types */ - OC_NONE = 0x0000, /* No operands */ - OC_1GEN = 0x0100, /* One general operand (CLR, TST, etc.) */ - OC_2GEN = 0x0200, /* Two general operand (MOV, CMP, etc.) */ - OC_BR = 0x0300, /* Branch */ - OC_ASH = 0x0400, /* ASH and ASHC (one gen, one reg) */ - OC_MARK = 0x0500, /* MARK instruction operand */ - OC_JSR = 0x0600, /* JSR, XOR (one reg, one gen) */ - OC_1REG = 0x0700, /* FADD, FSUB, FMUL, FDIV, RTS */ - OC_SOB = 0x0800, /* SOB */ - OC_1FIS = 0x0900, /* FIS (reg, gen) */ - OC_2FIS = 0x0a00, /* FIS (gen, reg) */ - OC__LAST = 0xff00 }; - -/* - format of a listing line - Interestingly, no instances of this struct are ever created. - It lives to be a way to layout the format of a list line. - I wonder if I should have bothered. -*/ - -typedef struct lstformat -{ - char flag[2]; /* Error flags */ - char line_number[6]; /* Line number */ - char pc[8]; /* Location */ - char words[8][3]; /* three instruction words */ - char source[1]; /* source line */ -} LSTFORMAT; - -#define SIZEOF_MEMBER(s, m) (sizeof((s *)0)->m) - -/* GLOBAL VARIABLES */ - -int pass = 0; /* The current assembly pass. 0 = first - pass. */ - -int stmtno = 0; /* The current source line number */ -int radix = 8; /* The current input conversion radix */ -int lsb = 0; /* The current local symbol section identifier */ -int last_lsb = 0; /* The last block in which a macro - automatic label was created */ -int last_locsym = 32768; /* The last local symbol number generated */ - -int enabl_debug = 0; /* Whether assembler debugging is enabled */ - -int enabl_ama = 0; /* When set, chooses absolute (037) versus - PC-relative */ - /* (067) addressing mode */ -int enabl_lsb = 0; /* When set, stops non-local symbol - definitions from delimiting local - symbol sections. */ - -int enabl_gbl = 1; /* Implicit definition of global symbols */ - -int list_md = 1; /* option to list macro/rept definition = yes */ - -int list_me = 1; /* option to list macro/rept expansion = yes */ - -int list_bex = 1; /* option to show binary */ - -int list_level = 1; /* Listing control level. .LIST - increments; .NLIST decrements */ - -char *listline; /* Source lines */ - -char *binline; /* for octal expansion */ - -FILE *lstfile = NULL; - -int suppressed = 0; /* Assembly suppressed by failed conditional */ - -#define MAX_MLBS 32 -MLB *mlbs[MAX_MLBS]; /* macro libraries specified on the - command line */ -int nr_mlbs = 0; /* Number of macro libraries */ - -typedef struct cond -{ - int ok; /* What the condition evaluated to */ - char *file; /* What file and line it occurred */ - int line; -} COND; - -#define MAX_CONDS 256 -COND conds[MAX_CONDS]; /* Stack of recent conditions */ -int last_cond; /* 0 means no stacked cond. */ - -SECTION *sect_stack[32]; /* 32 saved sections */ -int sect_sp; /* Stack pointer */ - -char *module_name = NULL; /* The module name (taken from the 'TITLE'); */ - -char *ident = NULL; /* .IDENT name */ - -EX_TREE *xfer_address = NULL; /* The transfer address */ - -SYMBOL *current_pc; /* The current program counter */ - -unsigned last_dot_addr; /* Last coded PC... */ -SECTION *last_dot_section; /* ...and it's program section */ - -#define DOT (current_pc->value) /* Handy reference to the current location */ - -/* The following are dummy psects for symbols which have meaning to - the assembler: */ - -SECTION register_section = -{ "", REGISTER, 0, 0 }; /* the section containing the registers */ - -SECTION pseudo_section = -{ "", PSEUDO, 0, 0 }; /* the section containing the - pseudo-operations */ - -SECTION instruction_section = -{ ". ABS.", INSTRUCTION, 0, 0 }; /* the section containing instructions */ - -SECTION macro_section = -{ "", SYSTEM, 0, 0, 0 }; /* Section for macros */ - -/* These are real psects that get written out to the object file */ - -SECTION absolute_section = -{ ". ABS.", SYSTEM, PSECT_GBL|PSECT_COM, 0, 0, 0}; /* The default - absolute section */ - -SECTION blank_section = -{ "", SYSTEM, PSECT_REL, 0, 0, 1}; /* The default relocatable section */ - -SECTION *sections[256] = { /* Array of sections in the order they were - defined */ - &absolute_section, &blank_section, }; - -int sector = 2; /* number of such sections */ - -SYMBOL *reg_sym[8]; /* Keep the register symbols in a handy array */ - -/* symbol tables */ - -#define HASH_SIZE 1023 - -typedef struct symbol_table -{ - SYMBOL *hash[HASH_SIZE]; -} SYMBOL_TABLE; - -SYMBOL_TABLE system_st; /* System symbols (Instructions, - pseudo-ops, registers) */ - -SYMBOL_TABLE section_st; /* Program sections */ - -SYMBOL_TABLE symbol_st; /* User symbols */ - -SYMBOL_TABLE macro_st; /* Macros */ - -SYMBOL_TABLE implicit_st; /* The symbols which may be implicit globals */ - -/* SYMBOL_ITER is used for iterating thru a symbol table. */ - -typedef struct symbol_iter -{ - int subscript; /* Current hash subscript */ - SYMBOL *current; /* Current symbol */ -} SYMBOL_ITER; - -/* EOL says whether a char* is pointing at the end of a line */ -#define EOL(c) (!(c) || (c) == '\n' || (c) == ';') - -/* reports errors */ -static void report(STREAM *str, char *fmt, ...) -{ - va_list ap; - char *name = "**"; - int line = 0; - - if(!pass) - return; /* Don't report now. */ - - if(str) - { - name = str->name; - line = str->line; - } - - fprintf(stderr, "%s:%d: ***ERROR ", name, line); - va_start(ap, fmt); - vfprintf(stderr, fmt, ap); - va_end(ap); - - if(lstfile) - { - fprintf(lstfile, "%s:%d: ***ERROR ", name, line); - va_start(ap, fmt); - vfprintf(lstfile, fmt, ap); - va_end(ap); - } -} - -/* memcheck - crash out if a pointer (returned from malloc) is NULL. */ - -void *memcheck(void *ptr) -{ - if(ptr == NULL) - { - fprintf(stderr, "Out of memory.\n"); - exit(EXIT_FAILURE); - } - - return ptr; -} - -/* upcase turns a string to upper case */ - -static void upcase(char *str) -{ - while(*str) - { - *str = toupper(*str); - str++; - } -} - -/* hash_name hashes a name into a value from 0-HASH_SIZE */ - -static int hash_name(char *label) -{ - unsigned accum = 0; - - while(*label) - accum = (accum << 1) ^ *label++; - - accum %= HASH_SIZE; - - return accum; -} - -/* Allocate a new symbol. Does not add it to any symbol table. */ - -static SYMBOL *new_sym(char *label) -{ - SYMBOL *sym = memcheck(malloc(sizeof(SYMBOL))); - sym->label = memcheck(strdup(label)); - sym->section = NULL; - sym->value = 0; - sym->flags = 0; - return sym; -} - -/* Free a symbol. Does not remove it from any symbol table. */ - -static void free_sym(SYMBOL *sym) -{ - if(sym->label) - { - free(sym->label); - sym->label = NULL; - } - free(sym); -} - -/* remove_sym removes a symbol from it's symbol table. */ - -static void remove_sym(SYMBOL *sym, SYMBOL_TABLE *table) -{ - SYMBOL **prevp, *symp; - int hash; - - hash = hash_name(sym->label); - prevp = &table->hash[hash]; - while(symp = *prevp, symp != NULL && symp != sym) - prevp = &symp->next; - - if(symp) - *prevp = sym->next; -} - -/* lookup_sym finds a symbol in a table */ - -static SYMBOL *lookup_sym(char *label, SYMBOL_TABLE *table) -{ - unsigned hash; - SYMBOL *sym; - - hash = hash_name(label); - - sym = table->hash[hash]; - while(sym && strcmp(sym->label, label) != 0) - sym = sym->next; - - return sym; -} - -/* next_sym - returns the next symbol from a symbol table. Must be - preceeded by first_sym. Returns NULL after the last symbol. */ - -static SYMBOL *next_sym(SYMBOL_TABLE *table, SYMBOL_ITER *iter) -{ - if(iter->current) - iter->current = iter->current->next; - - while(iter->current == NULL) - { - if(iter->subscript >= HASH_SIZE) - return NULL; /* No more symbols. */ - iter->current = table->hash[iter->subscript]; - iter->subscript++; - } - - return iter->current; /* Got a symbol. */ -} - -/* first_sym - returns the first symbol from a symbol table. Symbols - are stored in random order. */ - -static SYMBOL *first_sym(SYMBOL_TABLE *table, SYMBOL_ITER *iter) -{ - iter->subscript = 0; - iter->current = NULL; - return next_sym(table, iter); -} - -/* add_table - add a symbol to a symbol table. */ - -static void add_table(SYMBOL *sym, SYMBOL_TABLE *table) -{ - int hash = hash_name(sym->label); - sym->next = table->hash[hash]; - table->hash[hash] = sym; -} - -/* add_sym - used throughout to add or update symbols in a symbol - table. */ - -static SYMBOL *add_sym(char *label, unsigned value, unsigned flags, - SECTION *section, SYMBOL_TABLE *table) -{ - SYMBOL *sym; - - sym = lookup_sym(label, table); - if(sym != NULL) - { - // A symbol registered as "undefined" can be changed. - - if((sym->flags & UNDEFINED) && - !(flags & UNDEFINED)) - { - sym->flags &= ~(PERMANENT|UNDEFINED); - } - - /* Check for compatible definition */ - else if(sym->section == section && - sym->value == value) - { - sym->flags |= flags; /* Merge flags quietly */ - return sym; /* 's okay */ - } - - if(!(sym->flags & PERMANENT)) - { - /* permit redefinition */ - sym->value = value; - sym->flags |= flags; - sym->section = section; - return sym; - } - - return NULL; /* Bad symbol redefinition */ - } - - sym = new_sym(label); - sym->flags = flags; - sym->stmtno = stmtno; - sym->section = section; - sym->value = value; - - add_table(sym, table); - - return sym; -} - -/* Allocate a new section */ - -static SECTION *new_section(void) -{ - SECTION *sect = memcheck(malloc(sizeof(SECTION))); - sect->flags = 0; - sect->size = 0; - sect->pc = 0; - sect->type = 0; - sect->sector = 0; - sect->label = NULL; - return sect; -} - -/* Allocate a new ARG */ - -static ARG *new_arg(void) -{ - ARG *arg = memcheck(malloc(sizeof(ARG))); - arg->locsym = 0; - arg->value = NULL; - arg->next = NULL; - arg->label = NULL; - return arg; -} - -/* Allocate a new macro */ - -static MACRO *new_macro(char *label) -{ - MACRO *mac = memcheck(malloc(sizeof(MACRO))); - - mac->sym.flags = 0; - mac->sym.label = label; - mac->sym.stmtno = stmtno; - mac->sym.next = NULL; - mac->sym.section = ¯o_section; - mac->sym.value = 0; - mac->args = NULL; - mac->text = NULL; - - return mac; -} - -/* Free a list of args (as for a macro, or a macro expansion) */ - -static void free_args(ARG *arg) -{ - ARG *next; - - while(arg) - { - next = arg->next; - if(arg->label) - { - free(arg->label); - arg->label = NULL; - } - if(arg->value) - { - free(arg->value); - arg->value = NULL; - } - free(arg); - arg = next; - } -} - -/* free a macro, it's args, it's text, etc. */ - -static void free_macro(MACRO *mac) -{ - if(mac->text) - { - free(mac->text); - } - free_args(mac->args); - free_sym(&mac->sym); -} - -/* do_list returns TRUE if listing is enabled. */ - -static int dolist(void) -{ - int ok = lstfile != NULL && pass > 0 && list_level > 0; - return ok; -} - -/* list_source saves a text line for later listing by list_flush */ - -static void list_source(STREAM *str, char *cp) -{ - if(dolist()) - { - int len = strcspn(cp, "\n"); - /* Save the line text away for later... */ - if(listline) - free(listline); - listline = memcheck(malloc(len + 1)); - memcpy(listline, cp, len); - listline[len] = 0; - - if(!binline) - binline = memcheck(malloc(sizeof(LSTFORMAT) + 16)); - - sprintf(binline, "%*s%*d", - SIZEOF_MEMBER(LSTFORMAT, flag), "", - SIZEOF_MEMBER(LSTFORMAT, line_number), str->line); - } -} - -/* padto adds blanks to the end of a string until it's the given - length. */ - -static void padto(char *str, int to) -{ - int needspace = to - strlen(str); - str += strlen(str); - while(needspace > 0) - *str++ = ' ', needspace--; - *str = 0; -} - -/* list_flush produces a buffered list line. */ - -static void list_flush(void) -{ - if(dolist()) - { - padto(binline, offsetof(LSTFORMAT, source)); - fputs(binline, lstfile); - fputs(listline, lstfile); - fputc('\n', lstfile); - listline[0] = 0; - binline[0] = 0; - } -} - -/* list_fit checks to see if a word will fit in the current listing - line. If not, it flushes and prepares another line. */ - -static void list_fit(STREAM *str, unsigned addr) -{ - int len = strlen(binline); - size_t col1 = offsetof(LSTFORMAT, source); - size_t col2 = offsetof(LSTFORMAT, pc); - - if(strlen(binline) >= col1) - { - int offset = offsetof(LSTFORMAT, pc); - list_flush(); - listline[0] = 0; - binline[0] = 0; - sprintf(binline, "%*s %6.6o", - offsetof(LSTFORMAT, pc), "", - addr); - padto(binline, offsetof(LSTFORMAT, words)); - } - else if(strlen(binline) <= col2) - { - sprintf(binline, "%*s%*d %6.6o", - SIZEOF_MEMBER(LSTFORMAT, flag), "", - SIZEOF_MEMBER(LSTFORMAT, line_number), str->line, - addr); - padto(binline, offsetof(LSTFORMAT, words)); - } -} - -/* list_value is used to show a computed value */ - -static void list_value(STREAM *str, unsigned word) -{ - if(dolist()) - { - /* Print the value and go */ - binline[0] = 0; - sprintf(binline, "%*s%*d %6.6o", - SIZEOF_MEMBER(LSTFORMAT, flag), "", - SIZEOF_MEMBER(LSTFORMAT, line_number), str->line, - word & 0177777); - } -} - -/* Print a word to the listing file */ - -void list_word(STREAM *str, unsigned addr, unsigned value, int size, - char *flags) -{ - if(dolist()) - { - list_fit(str, addr); - if(size == 1) - sprintf(binline + strlen(binline), " %3.3o%1.1s ", - value & 0377, flags); - else - sprintf(binline + strlen(binline), "%6.6o%1.1s ", - value & 0177777, flags); - } -} - -/* This is called by places that are about to store some code, or - which want to manually update DOT. */ - -static void change_dot(TEXT_RLD *tr, int size) -{ - if(size > 0) - { - if(last_dot_section != current_pc->section) - { - text_define_location(tr, current_pc->section->label, - ¤t_pc->value); - last_dot_section = current_pc->section; - last_dot_addr = current_pc->value; - } - if(last_dot_addr != current_pc->value) - { - text_modify_location(tr, ¤t_pc->value); - last_dot_addr = current_pc->value; - } - - /* Update for next time */ - last_dot_addr += size; - } - - if(DOT+size > current_pc->section->size) - current_pc->section->size = DOT+size; -} - -/* store_word stores a word to the object file and lists it to the - listing file */ - -static int store_word(STREAM *str, TEXT_RLD *tr, int size, - unsigned word) -{ - change_dot(tr, size); - list_word(str, DOT, word, size, ""); - return text_word(tr, &DOT, size, word); -} - -/* store_word stores a word to the object file and lists it to the - listing file */ - -static int store_displaced_word(STREAM *str, TEXT_RLD *tr, - int size, unsigned word) -{ - change_dot(tr, size); - list_word(str, DOT, word, size, "'"); - return text_displaced_word(tr, &DOT, size, word); -} - -static int store_global_displaced_offset_word(STREAM *str, TEXT_RLD *tr, - int size, - unsigned word, char *global) -{ - change_dot(tr, size); - list_word(str, DOT, word, size, "G"); - return text_global_displaced_offset_word(tr, &DOT, size, - word, global); -} - -static int store_global_offset_word(STREAM *str, TEXT_RLD *tr, - int size, - unsigned word, char *global) -{ - change_dot(tr, size); - list_word(str, DOT, word, size, "G"); - return text_global_offset_word(tr, &DOT, size, word, global); -} - -static int store_internal_word(STREAM *str, TEXT_RLD *tr, - int size, unsigned word) -{ - change_dot(tr, size); - list_word(str, DOT, word, size, ""); - return text_internal_word(tr, &DOT, size, word); -} - -static int store_psect_displaced_offset_word(STREAM *str, TEXT_RLD *tr, - int size, - unsigned word, char *name) -{ - change_dot(tr, size); - list_word(str, DOT, word, size, ""); - return text_psect_displaced_offset_word(tr, &DOT, size, word, name); -} - -static int store_psect_offset_word(STREAM *str, TEXT_RLD *tr, - int size, - unsigned word, char *name) -{ - change_dot(tr, size); - list_word(str, DOT, word, size, ""); - return text_psect_offset_word(tr, &DOT, size, word, name); -} - -static int store_limits(STREAM *str, TEXT_RLD *tr) -{ - change_dot(tr, 4); - list_word(str, DOT, 0, 2, ""); - list_word(str, DOT+2, 0, 2, ""); - return text_limits(tr, &DOT); -} - -/* skipwhite - used everywhere to advance a char pointer past spaces */ - -static char *skipwhite(char *cp) -{ - while(*cp == ' ' || *cp == '\t') - cp++; - return cp; -} - -/* skipdelim - used everywhere to advance between tokens. Whitespace - and one comma are allowed delims. */ - -static char *skipdelim(char *cp) -{ - cp = skipwhite(cp); - if(*cp == ',') - cp = skipwhite(cp+1); - return cp; -} - -/* Parse PDP-11 64-bit floating point format. */ -/* Give a pointer to "size" words to receive the result. */ -/* Note: there are probably degenerate cases that store incorrect - results. For example, I think rounding up a FLT2 might cause - exponent overflow. Sorry. */ -/* Note also that the full 49 bits of precision probably aren't - available on the source platform, given the widespread application - of IEEE floating point formats, so expect some differences. Sorry - again. */ - -int parse_float(char *cp, char **endp, int size, unsigned *flt) -{ - double d; /* value */ - double frac; /* fractional value */ - ulong64 ufrac; /* fraction converted to 49 bit - unsigned integer */ - int i; /* Number of fields converted by sscanf */ - int n; /* Number of characters converted by sscanf */ - int sexp; /* Signed exponent */ - unsigned exp; /* Unsigned excess-128 exponent */ - unsigned sign = 0; /* Sign mask */ - - i = sscanf(cp, "%lf%n", &d, &n); - if(i == 0) - return 0; /* Wasn't able to convert */ - - cp += n; - if(endp) - *endp = cp; - - if(d == 0.0) - { - flt[0] = flt[1] = flt[2] = flt[3] = 0; /* All-bits-zero equals zero */ - return 1; /* Good job. */ - } - - frac = frexp(d, &sexp); /* Separate into exponent and mantissa */ - if(sexp < -128 || sexp > 127) - return 0; /* Exponent out of range. */ - - exp = sexp + 128; /* Make excess-128 mode */ - exp &= 0xff; /* express in 8 bits */ - - if(frac < 0) - { - sign = 0100000; /* Negative sign */ - frac = -frac; /* fix the mantissa */ - } - - /* The following big literal is 2 to the 49th power: */ - ufrac = (ulong64) (frac * 72057594037927936.0); /* Align fraction bits */ - - /* Round from FLT4 to FLT2 */ - if(size < 4) - { - ufrac += 0x80000000; /* Round to nearest 32-bit - representation */ - - if(ufrac > 0x200000000000) /* Overflow? */ - { - ufrac >>= 1; /* Normalize */ - exp--; - } - } - - flt[0] = (unsigned) (sign | (exp << 7) | (ufrac >> 48) & 0x7F); - if(size > 1) - { - flt[1] = (unsigned) ((ufrac >> 32) & 0xffff); - if(size > 2) - { - flt[2] = (unsigned) ((ufrac >> 16) & 0xffff); - flt[3] = (unsigned) ((ufrac >> 0) & 0xffff); - } - } - - return 1; -} - -/* Allocate an EX_TREE */ - -static EX_TREE *new_ex_tree(void) -{ - EX_TREE *tr = memcheck(malloc(sizeof(EX_TREE))); - return tr; -} - - -/* Create an EX_TREE representing a parse error */ - -static EX_TREE *ex_err(EX_TREE *tp, char *cp) -{ - EX_TREE *errtp; - - errtp = new_ex_tree(); - errtp->cp = cp; - errtp->type = EX_ERR; - errtp->data.child.left = tp; - - return errtp; -} - -/* Create an EX_TREE representing a literal value */ - -static EX_TREE *new_ex_lit(unsigned value) -{ - EX_TREE *tp; - - tp = new_ex_tree(); - tp->type = EX_LIT; - tp->data.lit = value; - - return tp; -} - -/* The recursive-descent expression parser parse_expr. */ - -/* This parser was designed for expressions with operator precedence. - However, MACRO-11 doesn't observe any sort of operator precedence. - If you feel your source deserves better, give the operators - appropriate precedence values right here. */ - -#define ADD_PREC 1 -#define MUL_PREC 1 -#define AND_PREC 1 -#define OR_PREC 1 - -EX_TREE *parse_unary(char *cp); /* Prototype for forward calls */ - -EX_TREE *parse_binary(char *cp, char term, int depth) -{ - EX_TREE *leftp, *rightp, *tp; - - leftp = parse_unary(cp); - - while(leftp->type != EX_ERR) - { - cp = skipwhite(leftp->cp); - - if(*cp == term) - return leftp; - - switch(*cp) - { - case '+': - if(depth >= ADD_PREC) - return leftp; - - rightp = parse_binary(cp+1, term, ADD_PREC); - tp = new_ex_tree(); - tp->type = EX_ADD; - tp->data.child.left = leftp; - tp->data.child.right = rightp; - tp->cp = rightp->cp; - leftp = tp; - break; - - case '-': - if(depth >= ADD_PREC) - return leftp; - - rightp = parse_binary(cp+1, term, ADD_PREC); - tp = new_ex_tree(); - tp->type = EX_SUB; - tp->data.child.left = leftp; - tp->data.child.right = rightp; - tp->cp = rightp->cp; - leftp = tp; - break; - - case '*': - if(depth >= MUL_PREC) - return leftp; - - rightp = parse_binary(cp+1, term, MUL_PREC); - tp = new_ex_tree(); - tp->type = EX_MUL; - tp->data.child.left = leftp; - tp->data.child.right = rightp; - tp->cp = rightp->cp; - leftp = tp; - break; - - case '/': - if(depth >= MUL_PREC) - return leftp; - - rightp = parse_binary(cp+1, term, MUL_PREC); - tp = new_ex_tree(); - tp->type = EX_DIV; - tp->data.child.left = leftp; - tp->data.child.right = rightp; - tp->cp = rightp->cp; - leftp = tp; - break; - - case '!': - if(depth >= OR_PREC) - return leftp; - - rightp = parse_binary(cp+1, term, 2); - tp = new_ex_tree(); - tp->type = EX_OR; - tp->data.child.left = leftp; - tp->data.child.right = rightp; - tp->cp = rightp->cp; - leftp = tp; - break; - - case '&': - if(depth >= AND_PREC) - return leftp; - - rightp = parse_binary(cp+1, term, AND_PREC); - tp = new_ex_tree(); - tp->type = EX_AND; - tp->data.child.left = leftp; - tp->data.child.right = rightp; - tp->cp = rightp->cp; - leftp = tp; - break; - - default: - /* Some unknown character. Let caller decide if it's okay. */ - - return leftp; - - } /* end switch */ - } /* end while */ - - /* Can't be reached except by error. */ - return leftp; -} - -/* get_symbol is used all over the place to pull a symbol out of the - text. */ - -static char *get_symbol(char *cp, char **endp, int *islocal) -{ - int len; - char *symcp; - int digits = 0; - - cp = skipwhite(cp); /* Skip leading whitespace */ - - if(!issym(*cp)) - return NULL; - - digits = 0; - if(isdigit(*cp)) - digits = 2; /* Think about digit count */ - - for(symcp = cp + 1; issym(*symcp); symcp++) - { - if(!isdigit(*symcp)) /* Not a digit? */ - digits--; /* Make a note. */ - } - - if(digits == 2) - return NULL; /* Not a symbol, it's a digit string */ - - if(endp) - *endp = symcp; - - len = symcp - cp; - - /* Now limit length */ - if(len > SYMMAX) - len = SYMMAX; - - symcp = memcheck(malloc(len + 1)); - - memcpy(symcp, cp, len); - symcp[len] = 0; - upcase(symcp); - - if(islocal) - { - *islocal = 0; - - /* Turn to local label format */ - if(digits == 1) - { - if(symcp[len-1] == '$') - { - char *newsym = memcheck(malloc(32)); /* Overkill */ - sprintf(newsym, "%d$%d", strtol(symcp, NULL, 10), lsb); - free(symcp); - symcp = newsym; - if(islocal) - *islocal = LOCAL; - } - else - { - free(symcp); - return NULL; - } - } - } - else - { - /* disallow local label format */ - if(isdigit(*symcp)) - { - free(symcp); - return NULL; - } - } - - return symcp; -} - -/* - brackrange is used to find a range of text which may or may not be - bracketed. - - If the brackets are <>, then nested brackets are detected. - If the brackets are of the form ^/.../ no detection of nesting is - attempted. - - Using brackets ^<...< will mess this routine up. What in the world - are you thinking? -*/ - -int brackrange(char *cp, int *start, int *length, char **endp) -{ - char endstr[6]; - int endlen; - int nest; - int len; - - switch(*cp) - { - case '^': - endstr[0] = cp[1]; - strcpy(endstr+1, "\n"); - *start = 2; - endlen = 1; - break; - case '<': - strcpy(endstr, "<>\n"); - endlen = 1; - *start = 1; - break; - default: - return FALSE; - } - - cp += *start; - - len = 0; - nest = 1; - while(nest) - { - int sublen; - sublen = strcspn(cp+len, endstr); - if(cp[len+sublen] == '<') - nest++; - else - nest--; - len += sublen; - } - - *length = len; - if(endp) - *endp = cp + len + endlen; - - return 1; -} - -/* parse_unary parses out a unary operator or leaf expression. */ - -EX_TREE *parse_unary(char *cp) -{ - EX_TREE *tp; - - /* Skip leading whitespace */ - cp = skipwhite(cp); - - if(*cp == '%') /* Register notation */ - { - unsigned reg; - cp++; - reg = strtoul(cp, &cp, 8); - if(reg > 7) - return ex_err(NULL, cp); - - /* This returns references to the built-in register symbols */ - tp = new_ex_tree(); - tp->type = EX_SYM; - tp->data.symbol = reg_sym[reg]; - tp->cp = cp; - return tp; - } - - /* Unary negate */ - if(*cp == '-') - { - tp = new_ex_tree(); - tp->type = EX_NEG; - tp->data.child.left = parse_unary(cp+1); - tp->cp = tp->data.child.left->cp; - return tp; - } - - /* Unary + I can ignore. */ - if(*cp == '+') - return parse_unary(cp+1); - - if(*cp == '^') - { - int save_radix; - switch(tolower(cp[1])) - { - case 'c': - /* ^C, ones complement */ - tp = new_ex_tree(); - tp->type = EX_COM; - tp->data.child.left = parse_unary(cp+2); - tp->cp = tp->data.child.left->cp; - return tp; - case 'b': - /* ^B, binary radix modifier */ - save_radix = radix; - radix = 2; - tp = parse_unary(cp+2); - radix = save_radix; - return tp; - case 'o': - /* ^O, octal radix modifier */ - save_radix = radix; - radix = 8; - tp = parse_unary(cp+2); - radix = save_radix; - return tp; - case 'd': - /* ^D, decimal radix modifier */ - save_radix = radix; - radix = 10; - tp = parse_unary(cp+2); - radix = save_radix; - return tp; - case 'x': - /* An enhancement! ^X, hexadecimal radix modifier */ - save_radix = radix; - radix = 16; - tp = parse_unary(cp+2); - radix = save_radix; - return tp; - case 'r': - /* ^R, RAD50 literal */ - { - int start, len; - char *endcp; - unsigned value; - cp += 2; - if(brackrange(cp, &start, &len, &endcp)) - value = rad50(cp+start, NULL); - else - value = rad50(cp, &endcp); - tp = new_ex_lit(value); - tp->cp = endcp; - return tp; - } - case 'f': - /* ^F, single-word floating point literal indicator */ - { - unsigned flt[1]; - char *endcp; - if(!parse_float(cp+2, &endcp, 1, flt)) - { - tp = ex_err(NULL, cp+2); - } - else - { - tp = new_ex_lit(flt[0]); - tp->cp = endcp; - } - return tp; - } - } - - if(ispunct(cp[1])) - { - char *ecp; - /* oddly-bracketed expression like this: ^/expression/ */ - tp = parse_binary(cp+2, cp[1], 0); - ecp = skipwhite(tp->cp); - - if(*ecp != cp[1]) - return ex_err(tp, ecp); - - tp->cp = ecp + 1; - return tp; - } - } - - /* Bracketed subexpression */ - if(*cp == '<') - { - char *ecp; - tp = parse_binary(cp+1, '>', 0); - ecp = skipwhite(tp->cp); - if(*ecp != '>') - return ex_err(tp, ecp); - - tp->cp = ecp + 1; - return tp; - } - - /* Check for ASCII constants */ - - if(*cp == '\'') - { - /* 'x single ASCII character */ - cp++; - tp = new_ex_tree(); - tp->type = EX_LIT; - tp->data.lit = *cp & 0xff; - tp->cp = ++cp; - return tp; - } - - if(*cp == '\"') - { - /* "xx ASCII character pair */ - cp++; - tp = new_ex_tree(); - tp->type = EX_LIT; - tp->data.lit = (cp[0] & 0xff) | ((cp[1] & 0xff) << 8); - tp->cp = cp + 2; - return tp; - } - - /* Numeric constants are trickier than they need to be, */ - /* since local labels start with a digit too. */ - if(isdigit(*cp)) - { - char *label; - int local; - - if((label = get_symbol(cp, NULL, &local)) == NULL) - { - char *endcp; - unsigned long value; - int rad = radix; - - /* get_symbol returning NULL assures me that it's not a - local label. */ - - /* Look for a trailing period, to indicate decimal... */ - for(endcp = cp; isdigit(*endcp); endcp++) - ; - if(*endcp == '.') - rad = 10; - - value = strtoul(cp, &endcp, rad); - if(*endcp == '.') - endcp++; - - tp = new_ex_tree(); - tp->type = EX_LIT; - tp->data.lit = value; - tp->cp = endcp; - - return tp; - } - - free(label); - } - - /* Now check for a symbol */ - - { - char *label; - int local; - SYMBOL *sym; - - /* Optimization opportunity: I don't really need to call - get_symbol a second time. */ - - if(!(label = get_symbol(cp, &cp, &local))) - { - tp = ex_err(NULL, cp); /* Not a valid label. */ - return tp; - } - - sym = lookup_sym(label, &symbol_st); - if(sym == NULL) - { - /* A symbol from the "PST", which means an instruction - code. */ - sym = lookup_sym(label, &system_st); - } - - if(sym != NULL) - { - tp = new_ex_tree(); - tp->cp = cp; - tp->type = EX_SYM; - tp->data.symbol = sym; - - free(label); - return tp; - } - - /* The symbol was not found. Create an "undefined symbol" - reference. */ - sym = memcheck(malloc(sizeof(SYMBOL))); - sym->label = label; - sym->flags = UNDEFINED | local; - sym->stmtno = stmtno; - sym->next = NULL; - sym->section = &absolute_section; - sym->value = 0; - - tp = new_ex_tree(); - tp->cp = cp; - tp->type = EX_UNDEFINED_SYM; - tp->data.symbol = sym; - - return tp; - } -} - -/* Diagnostic: symflags returns a char* which gives flags I can use to - show the context of a symbol. */ - -static char *symflags(SYMBOL *sym) -{ - static char temp[8]; - char *fp = temp; - if(sym->flags & GLOBAL) - *fp++ = 'G'; - if(sym->flags & PERMANENT) - *fp++ = 'P'; - if(sym->flags & DEFINITION) - *fp++ = 'D'; - *fp = 0; - return fp; -} - -/* Diagnostic: print an expression tree. I used this in various - places to help me diagnose parse problems, by putting in calls to - print_tree when I didn't understand why something wasn't working. - This is currently dead code, nothing calls it; but I don't want it - to go away. Hopefully the compiler will realize when it's dead, and - eliminate it. */ - -static void print_tree(FILE *printfile, EX_TREE *tp, int depth) -{ - SYMBOL *sym; - - switch(tp->type) - { - case EX_LIT: - fprintf(printfile, "%o", tp->data.lit & 0177777); - break; - - case EX_SYM: - case EX_TEMP_SYM: - sym = tp->data.symbol; - fprintf(printfile, "%s{%s%o:%s}", tp->data.symbol->label, - symflags(sym), sym->value, sym->section->label); - break; - - case EX_UNDEFINED_SYM: - fprintf(printfile, "%s{%o:undefined}", tp->data.symbol->label, - tp->data.symbol->value); - break; - - case EX_COM: - fprintf(printfile, "^C<"); - print_tree(printfile, tp->data.child.left, depth+4); - fprintf(printfile, ">"); - break; - - case EX_NEG: - fprintf(printfile, "-<"); - print_tree(printfile, tp->data.child.left, depth+4); - fputc('>', printfile); - break; - - case EX_ERR: - fprintf(printfile, "{expression error}"); - if(tp->data.child.left) - { - fputc('<', printfile); - print_tree(printfile, tp->data.child.left, depth+4); - fputc('>', printfile); - } - break; - - case EX_ADD: - fputc('<', printfile); - print_tree(printfile, tp->data.child.left, depth+4); - fputc('+', printfile); - print_tree(printfile, tp->data.child.right, depth+4); - fputc('>', printfile); - break; - - case EX_SUB: - fputc('<', printfile); - print_tree(printfile, tp->data.child.left, depth+4); - fputc('-', printfile); - print_tree(printfile, tp->data.child.right, depth+4); - fputc('>', printfile); - break; - - case EX_MUL: - fputc('<', printfile); - print_tree(printfile, tp->data.child.left, depth+4); - fputc('*', printfile); - print_tree(printfile, tp->data.child.right, depth+4); - fputc('>', printfile); - break; - - case EX_DIV: - fputc('<', printfile); - print_tree(printfile, tp->data.child.left, depth+4); - fputc('/', printfile); - print_tree(printfile, tp->data.child.right, depth+4); - fputc('>', printfile); - break; - - case EX_AND: - fputc('<', printfile); - print_tree(printfile, tp->data.child.left, depth+4); - fputc('&', printfile); - print_tree(printfile, tp->data.child.right, depth+4); - fputc('>', printfile); - break; - - case EX_OR: - fputc('<', printfile); - print_tree(printfile, tp->data.child.left, depth+4); - fputc('!', printfile); - print_tree(printfile, tp->data.child.right, depth+4); - fputc('>', printfile); - break; - } - - if(depth == 0) - fputc('\n', printfile); -} - -/* free_tree frees an expression tree. */ - -static void free_tree(EX_TREE *tp) -{ - switch(tp->type) - { - case EX_UNDEFINED_SYM: - case EX_TEMP_SYM: - free(tp->data.symbol->label); - free(tp->data.symbol); - case EX_LIT: - case EX_SYM: - free(tp); - break; - - case EX_COM: - case EX_NEG: - free_tree(tp->data.child.left); - free(tp); - break; - - case EX_ERR: - if(tp->data.child.left) - free_tree(tp->data.child.left); - free(tp); - break; - - case EX_ADD: - case EX_SUB: - case EX_MUL: - case EX_DIV: - case EX_AND: - case EX_OR: - free_tree(tp->data.child.left); - free_tree(tp->data.child.right); - free(tp); - break; - } -} - -/* new_temp_sym allocates a new EX_TREE entry of type "TEMPORARY - SYMBOL" (slight semantic difference from "UNDEFINED"). */ - -static EX_TREE *new_temp_sym(char *label, SECTION *section, unsigned value) -{ - SYMBOL *sym; - EX_TREE *tp; - - sym = memcheck(malloc(sizeof(SYMBOL))); - sym->label = memcheck(strdup(label)); - sym->flags = 0; - sym->stmtno = stmtno; - sym->next = NULL; - sym->section = section; - sym->value = value; - - tp = new_ex_tree(); - tp->type = EX_TEMP_SYM; - tp->data.symbol = sym; - - return tp; -} - -#define RELTYPE(tp) (((tp)->type == EX_SYM || (tp)->type == EX_TEMP_SYM) && \ - (tp)->data.symbol->section->flags & PSECT_REL) - -/* evaluate "evaluates" an EX_TREE, ideally trying to produce a - constant value, else a symbol plus an offset. */ - -static EX_TREE *evaluate(EX_TREE *tp, int undef) -{ - EX_TREE *res; - char *cp = tp->cp; - - switch(tp->type) - { - case EX_SYM: - { - SYMBOL *sym = tp->data.symbol; - - /* Change some symbols to "undefined" */ - - if(undef) - { - int change = 0; - -#if 0 /* I'd prefer this behavior, but - MACRO.SAV is a bit too - primitive. */ - /* A temporary symbol defined later is "undefined." */ - if(!(sym->flags & PERMANENT) && sym->stmtno > stmtno) - change = 1; -#endif - - /* A global symbol with no assignment is "undefined." */ - /* Go figure. */ - if((sym->flags & (GLOBAL|DEFINITION)) == GLOBAL) - change = 1; - - if(change) - { - res = new_temp_sym(tp->data.symbol->label, - tp->data.symbol->section, tp->data.symbol->value); - res->type = EX_UNDEFINED_SYM; - break; - } - } - - /* Turn defined absolute symbol to a literal */ - if(!(sym->section->flags & PSECT_REL) && - (sym->flags & (GLOBAL|DEFINITION)) != GLOBAL && - sym->section->type != REGISTER) - { - res = new_ex_lit(sym->value); - break; - } - - /* Make a temp copy of any reference to "." since it might - change as complex relocatable expressions are written out - */ - if(strcmp(sym->label, ".") == 0) - { - res = new_temp_sym(".", sym->section, sym->value); - break; - } - - /* Copy other symbol reference verbatim. */ - res = new_ex_tree(); - res->type = EX_SYM; - res->data.symbol = tp->data.symbol; - res->cp = tp->cp; - break; - } - - case EX_LIT: - res = new_ex_tree(); - *res = *tp; - break; - - case EX_TEMP_SYM: - case EX_UNDEFINED_SYM: - /* Copy temp and undefined symbols */ - res = new_temp_sym(tp->data.symbol->label, - tp->data.symbol->section, - tp->data.symbol->value); - res->type = tp->type; - break; - - case EX_COM: - /* Complement */ - tp = evaluate(tp->data.child.left, undef); - if(tp->type == EX_LIT) - { - /* Complement the literal */ - res = new_ex_lit(~tp->data.lit); - free_tree(tp); - } - else - { - /* Copy verbatim. */ - res = new_ex_tree(); - res->type = EX_NEG; - res->cp = tp->cp; - res->data.child.left = tp; - } - - break; - - case EX_NEG: - tp = evaluate(tp->data.child.left, undef); - if(tp->type == EX_LIT) - { - /* negate literal */ - res = new_ex_lit((unsigned)-(int)tp->data.lit); - free_tree(tp); - } - else if(tp->type == EX_SYM || tp->type == EX_TEMP_SYM) - { - /* Make a temp sym with the negative value of the given - sym (this works for symbols within relocatable sections - too) */ - res = new_temp_sym("*TEMP", tp->data.symbol->section, - (unsigned)-(int)tp->data.symbol->value); - res->cp = tp->cp; - free_tree(tp); - } - else - { - /* Copy verbatim. */ - res = new_ex_tree(); - res->type = EX_NEG; - res->cp = tp->cp; - res->data.child.left = tp; - } - break; - - case EX_ERR: - /* Copy */ - res = ex_err(tp->data.child.left, tp->cp); - break; - - case EX_ADD: - { - EX_TREE *left, *right; - - left = evaluate(tp->data.child.left, undef); - right = evaluate(tp->data.child.right, undef); - - /* Both literals? Sum them and return result. */ - if(left->type == EX_LIT && right->type == EX_LIT) - { - res = new_ex_lit(left->data.lit + right->data.lit); - free_tree(left); - free_tree(right); - break; - } - - /* Commutative: A+x == x+A. - Simplify by putting the literal on the right */ - if(left->type == EX_LIT) - { - EX_TREE *temp = left; - left = right; - right = temp; - } - - if(right->type == EX_LIT && /* Anything plus 0 == itself */ - right->data.lit == 0) - { - res = left; - free_tree(right); - break; - } - - /* Relative symbol plus lit is replaced with a temp sym - holding the sum */ - if(RELTYPE(left) && right->type == EX_LIT) - { - SYMBOL *sym = left->data.symbol; - res = new_temp_sym("*ADD", sym->section, sym->value + - right->data.lit); - free_tree(left); - free_tree(right); - break; - } - - /* Associative: +y == A+ */ - /* and if x+y is constant, I can do that math. */ - if(left->type == EX_ADD && right->type == EX_LIT) - { - EX_TREE *leftright = left->data.child.right; - if(leftright->type == EX_LIT) - { - /* Do the shuffle */ - res = left; - leftright->data.lit += right->data.lit; - free_tree(right); - break; - } - } - - /* Associative: +y == A+ */ - /* and if y-x is constant, I can do that math. */ - if(left->type == EX_SUB && right->type == EX_LIT) - { - EX_TREE *leftright = left->data.child.right; - if(leftright->type == EX_LIT) - { - /* Do the shuffle */ - res = left; - leftright->data.lit = right->data.lit - leftright->data.lit; - free_tree(right); - break; - } - } - - /* Anything else returns verbatim */ - res = new_ex_tree(); - res->type = EX_ADD; - res->data.child.left = left; - res->data.child.right = right; - } - break; - - case EX_SUB: - { - EX_TREE *left, *right; - - left = evaluate(tp->data.child.left, undef); - right = evaluate(tp->data.child.right, undef); - - /* Both literals? Subtract them and return a lit. */ - if(left->type == EX_LIT && right->type == EX_LIT) - { - res = new_ex_lit(left->data.lit - right->data.lit); - free_tree(left); - free_tree(right); - break; - } - - if(right->type == EX_LIT && /* Symbol minus 0 == symbol */ - right->data.lit == 0) - { - res = left; - free_tree(right); - break; - } - - /* A relocatable minus an absolute - make a new temp sym - to represent that. */ - if(RELTYPE(left) && - right->type == EX_LIT) - { - SYMBOL *sym = left->data.symbol; - res = new_temp_sym("*SUB", sym->section, - sym->value - right->data.lit); - free_tree(left); - free_tree(right); - break; - } - - if(RELTYPE(left) && - RELTYPE(right) && - left->data.symbol->section == right->data.symbol->section) - { - /* Two defined symbols in the same psect. Resolve - their difference as a literal. */ - res = new_ex_lit(left->data.symbol->value - - right->data.symbol->value); - free_tree(left); - free_tree(right); - break; - } - - /* Associative: -y == A+ */ - /* and if x-y is constant, I can do that math. */ - if(left->type == EX_ADD && right->type == EX_LIT) - { - EX_TREE *leftright = left->data.child.right; - if(leftright->type == EX_LIT) - { - /* Do the shuffle */ - res = left; - leftright->data.lit -= right->data.lit; - free_tree(right); - break; - } - } - - /* Associative: -y == A- */ - /* and if x+y is constant, I can do that math. */ - if(left->type == EX_SUB && right->type == EX_LIT) - { - EX_TREE *leftright = left->data.child.right; - if(leftright->type == EX_LIT) - { - /* Do the shuffle */ - res = left; - leftright->data.lit += right->data.lit; - free_tree(right); - break; - } - } - - /* Anything else returns verbatim */ - res = new_ex_tree(); - res->type = EX_SUB; - res->data.child.left = left; - res->data.child.right = right; - } - break; - - case EX_MUL: - { - EX_TREE *left, *right; - - left = evaluate(tp->data.child.left, undef); - right = evaluate(tp->data.child.right, undef); - - /* Can only multiply if both are literals */ - if(left->type == EX_LIT && right->type == EX_LIT) - { - res = new_ex_lit(left->data.lit * right->data.lit); - free_tree(left); - free_tree(right); - break; - } - - /* Commutative: A*x == x*A. - Simplify by putting the literal on the right */ - if(left->type == EX_LIT) - { - EX_TREE *temp = left; - left = right; - right = temp; - } - - if(right->type == EX_LIT && /* Symbol times 1 == symbol */ - right->data.lit == 1) - { - res = left; - free_tree(right); - break; - } - - if(right->type == EX_LIT && /* Symbol times 0 == 0 */ - right->data.lit == 0) - { - res = right; - free_tree(left); - break; - } - - /* Associative: *y == A* */ - /* If x*y is constant, I can do this math. */ - /* Is this safe? I will potentially be doing it */ - /* with greater accuracy than the target platform. */ - /* Hmmm. */ - - if(left->type == EX_MUL && right->type == EX_LIT) - { - EX_TREE *leftright = left->data.child.right; - if(leftright->type == EX_LIT) - { - /* Do the shuffle */ - res = left; - leftright->data.lit *= right->data.lit; - free_tree(right); - break; - } - } - - /* Anything else returns verbatim */ - res = new_ex_tree(); - res->type = EX_MUL; - res->data.child.left = left; - res->data.child.right = right; - } - break; - - case EX_DIV: - { - EX_TREE *left, *right; - - left = evaluate(tp->data.child.left, undef); - right = evaluate(tp->data.child.right, undef); - - /* Can only divide if both are literals */ - if(left->type == EX_LIT && right->type == EX_LIT) - { - res = new_ex_lit(left->data.lit / right->data.lit); - free_tree(left); - free_tree(right); - break; - } - - if(right->type == EX_LIT && /* Symbol divided by 1 == symbol */ - right->data.lit == 1) - { - res = left; - free_tree(right); - break; - } - - /* Anything else returns verbatim */ - res = new_ex_tree(); - res->type = EX_DIV; - res->data.child.left = left; - res->data.child.right = right; - } - break; - - case EX_AND: - { - EX_TREE *left, *right; - - left = evaluate(tp->data.child.left, undef); - right = evaluate(tp->data.child.right, undef); - - /* Operate if both are literals */ - if(left->type == EX_LIT && right->type == EX_LIT) - { - res = new_ex_lit(left->data.lit & right->data.lit); - free_tree(left); - free_tree(right); - break; - } - - /* Commutative: A&x == x&A. - Simplify by putting the literal on the right */ - if(left->type == EX_LIT) - { - EX_TREE *temp = left; - left = right; - right = temp; - } - - if(right->type == EX_LIT && /* Symbol AND 0 == 0 */ - right->data.lit == 0) - { - res = new_ex_lit(0); - free_tree(left); - free_tree(right); - break; - } - - if(right->type == EX_LIT && /* Symbol AND 0177777 == symbol */ - right->data.lit == 0177777) - { - res = left; - free_tree(right); - break; - } - - /* Anything else returns verbatim */ - res = new_ex_tree(); - res->type = EX_AND; - res->data.child.left = left; - res->data.child.right = right; - } - break; - - case EX_OR: - { - EX_TREE *left, *right; - - left = evaluate(tp->data.child.left, undef); - right = evaluate(tp->data.child.right, undef); - - /* Operate if both are literals */ - if(left->type == EX_LIT && right->type == EX_LIT) - { - res = new_ex_lit(left->data.lit | right->data.lit); - free_tree(left); - free_tree(right); - break; - } - - /* Commutative: A!x == x!A. - Simplify by putting the literal on the right */ - if(left->type == EX_LIT) - { - EX_TREE *temp = left; - left = right; - right = temp; - } - - if(right->type == EX_LIT && /* Symbol OR 0 == symbol */ - right->data.lit == 0) - { - res = left; - free_tree(right); - break; - } - - if(right->type == EX_LIT && /* Symbol OR 0177777 == 0177777 */ - right->data.lit == 0177777) - { - res = new_ex_lit(0177777); - free_tree(left); - free_tree(right); - break; - } - - /* Anything else returns verbatim */ - res = new_ex_tree(); - res->type = EX_OR; - res->data.child.left = left; - res->data.child.right = right; - } - break; - } - - res->cp = cp; - return res; -} - -/* - parse_expr - this gets called everywhere. It parses and evaluates - an arithmetic expression. -*/ - -EX_TREE *parse_expr(char *cp, int undef) -{ - EX_TREE *expr; - EX_TREE *value; - - expr = parse_binary(cp, 0, 0); /* Parse into a tree */ - value = evaluate(expr, undef); /* Perform the arithmetic */ - value->cp = expr->cp; /* Pointer to end of text is part of - the rootmost node */ - free_tree(expr); /* Discard parse in favor of - evaluation */ - - return value; -} - -/* free_addr_mode frees the storage consumed by an addr_mode */ - -static void free_addr_mode(ADDR_MODE *mode) -{ - if(mode->offset) - free_tree(mode->offset); - mode->offset = NULL; -} - -/* Get the register indicated by the expression */ - -#define NO_REG 0777 - -static unsigned get_register(EX_TREE *expr) -{ - unsigned reg; - - if(expr->type == EX_LIT && - expr->data.lit <= 7) - { - reg = expr->data.lit; - return reg; - } - - if(expr->type == EX_SYM && - expr->data.symbol->section->type == REGISTER) - { - reg = expr->data.symbol->value; - return reg; - } - - return NO_REG; -} - -/* get_mode - parse a general addressing mode. */ - -int get_mode(char *cp, char **endp, ADDR_MODE *mode) -{ - EX_TREE *value; - - mode->offset = NULL; - mode->rel = 0; - mode->type = 0; - - cp = skipwhite(cp); - - /* @ means "indirect," sets bit 3 */ - if(*cp == '@') - { - cp++; - mode->type |= 010; - } - - /* Immediate modes #imm and @#imm */ - if(*cp == '#') - { - cp++; - mode->type |= 027; - mode->offset = parse_expr(cp, 0); - if(endp) - *endp = mode->offset->cp; - return TRUE; - } - - /* Check for -(Rn) */ - - if(*cp == '-') - { - char *tcp = skipwhite(cp + 1); - if(*tcp++ == '(') - { - unsigned reg; - /* It's -(Rn) */ - value = parse_expr(tcp, 0); - reg = get_register(value); - if(reg == NO_REG || - (tcp = skipwhite(value->cp), *tcp++ != ')')) - { - free_tree(value); - return FALSE; - } - mode->type |= 040 | reg; - if(endp) - *endp = tcp; - free_tree(value); - return TRUE; - } - } - - /* Check for (Rn) */ - if(*cp == '(') - { - char *tcp; - unsigned reg; - value = parse_expr(cp + 1, 0); - reg = get_register(value); - - if(reg == NO_REG || - (tcp = skipwhite(value->cp), *tcp++ != ')')) - { - free_tree(value); - return FALSE; - } - - tcp = skipwhite(tcp); - if(*tcp == '+') - { - tcp++; /* It's (Rn)+ */ - if(endp) - *endp = tcp; - mode->type |= 020 | reg; - free_tree(value); - return TRUE; - } - - if(mode->type == 010) /* For @(Rn) there's an implied 0 offset */ - { - mode->offset = new_ex_lit(0); - mode->type |= 060 | reg; - free_tree(value); - if(endp) - *endp = tcp; - return TRUE; - } - - mode->type |= 010 | reg; /* Mode 10 is register indirect as - in (Rn) */ - free_tree(value); - if(endp) - *endp = tcp; - return TRUE; - } - - /* Modes with an offset */ - - mode->offset = parse_expr(cp, 0); - - cp = skipwhite(mode->offset->cp); - - if(*cp == '(') - { - unsigned reg; - /* indirect register plus offset */ - value = parse_expr(cp+1, 0); - reg = get_register(value); - if(reg == NO_REG || - (cp = skipwhite(value->cp), *cp++ != ')')) - { - free_tree(value); - return FALSE; /* Syntax error in addressing mode */ - } - - mode->type |= 060 | reg; - - free_tree(value); - - if(endp) - *endp = cp; - return TRUE; - } - - /* Plain old expression. */ - - if(endp) - *endp = cp; - - /* It might be a register, though. */ - if(mode->offset->type == EX_SYM) - { - SYMBOL *sym = mode->offset->data.symbol; - if(sym->section->type == REGISTER) - { - free_tree(mode->offset); - mode->offset = NULL; - mode->type |= sym->value; - return TRUE; - } - } - - /* It's either 067 (PC-relative) or 037 (absolute) mode, depending */ - /* on user option. */ - - if(mode->type & 010) /* Have already noted indirection? */ - { - mode->type |= 067; /* If so, then PC-relative is the only - option */ - mode->rel = 1; /* Note PC-relative */ - } - else if(enabl_ama) /* User asked for absolute adressing? */ - { - mode->type |= 037; /* Give it to him. */ - } - else - { - mode->type |= 067; /* PC-relative */ - mode->rel = 1; /* Note PC-relative */ - } - - return TRUE; -} - -/* - implicit_gbl is a self-recursive routine that adds undefined symbols - to the "implicit globals" symbol table. -*/ - -void implicit_gbl(EX_TREE *value) -{ - if(pass) - return; /* Only do this in first pass */ - - if(!enabl_gbl) - return; /* Option not enabled, don't do it. */ - - switch(value->type) - { - case EX_UNDEFINED_SYM: - { - SYMBOL *sym; - if(!(value->data.symbol->flags & LOCAL)) /* Unless it's a - local symbol, */ - { - sym = add_sym(value->data.symbol->label, - 0, GLOBAL, &absolute_section, &implicit_st); - } - } - break; - case EX_LIT: - case EX_SYM: - return; - case EX_ADD: - case EX_SUB: - case EX_MUL: - case EX_DIV: - case EX_AND: - case EX_OR: - implicit_gbl(value->data.child.right); - /* falls into... */ - case EX_COM: - case EX_NEG: - implicit_gbl(value->data.child.left); - break; - case EX_ERR: - if(value->data.child.left) - implicit_gbl(value->data.child.left); - break; - } -} - -/* Done between the first and second passes */ -/* Migrates the symbols from the "implicit" table into the main table. */ - -static void migrate_implicit(void) -{ - SYMBOL_ITER iter; - SYMBOL *isym, *sym; - - for(isym = first_sym(&implicit_st, &iter); - isym != NULL; - isym = next_sym(&implicit_st, &iter)) - { - sym = lookup_sym(isym->label, &symbol_st); - if(sym) - continue; // It's already in there. Great. - sym = add_sym(isym->label, isym->value, isym->flags, - isym->section, &symbol_st); - // Just one other thing - migrate the stmtno - sym->stmtno = isym->stmtno; - } -} - -static int express_sym_offset(EX_TREE *value, SYMBOL **sym, unsigned *offset) -{ - implicit_gbl(value); /* Translate tree's undefined syms - into global syms */ - - /* Internally relocatable symbols will have been summed down into - EX_TEMP_SYM's. */ - - if(value->type == EX_SYM || - value->type == EX_TEMP_SYM) - { - *sym = value->data.symbol; - *offset = 0; - return 1; - } - - /* What remains is external symbols. */ - - if(value->type == EX_ADD) - { - EX_TREE *left = value->data.child.left; - EX_TREE *right = value->data.child.right; - if((left->type != EX_SYM && - left->type != EX_UNDEFINED_SYM) || - right->type != EX_LIT) - return 0; /* Failed. */ - *sym = left->data.symbol; - *offset = right->data.lit; - return 1; - } - - if(value->type == EX_SUB) - { - EX_TREE *left = value->data.child.left; - EX_TREE *right = value->data.child.right; - if((left->type != EX_SYM && - left->type != EX_UNDEFINED_SYM) || - right->type != EX_LIT) - return 0; /* Failed. */ - *sym = left->data.symbol; - *offset = (unsigned)-(int)(right->data.lit); - return 1; - } - - return 0; -} - -/* - Translate an EX_TREE into a TEXT_COMPLEX suitable for encoding - into the object file. */ - -int complex_tree(TEXT_COMPLEX *tx, EX_TREE *tree) -{ - switch(tree->type) - { - case EX_LIT: - text_complex_lit(tx, tree->data.lit); - return 1; - - case EX_TEMP_SYM: - case EX_SYM: - { - SYMBOL *sym = tree->data.symbol; - if((sym->flags & (GLOBAL|DEFINITION)) == GLOBAL) - { - text_complex_global(tx, sym->label); - } - else - { - text_complex_psect(tx, sym->section->sector, sym->value); - } - } - return 1; - - case EX_COM: - if(!complex_tree(tx, tree->data.child.left)) - return 0; - text_complex_com(tx); - return 1; - - case EX_NEG: - if(!complex_tree(tx, tree->data.child.left)) - return 0; - text_complex_neg(tx); - return 1; - - case EX_ADD: - if(!complex_tree(tx, tree->data.child.left)) - return 0; - if(!complex_tree(tx, tree->data.child.right)) - return 0; - text_complex_add(tx); - return 1; - - case EX_SUB: - if(!complex_tree(tx, tree->data.child.left)) - return 0; - if(!complex_tree(tx, tree->data.child.right)) - return 0; - text_complex_sub(tx); - return 1; - - case EX_MUL: - if(!complex_tree(tx, tree->data.child.left)) - return 0; - if(!complex_tree(tx, tree->data.child.right)) - return 0; - text_complex_mul(tx); - return 1; - - case EX_DIV: - if(!complex_tree(tx, tree->data.child.left)) - return 0; - if(!complex_tree(tx, tree->data.child.right)) - return 0; - text_complex_div(tx); - return 1; - - case EX_AND: - if(!complex_tree(tx, tree->data.child.left)) - return 0; - if(!complex_tree(tx, tree->data.child.right)) - return 0; - text_complex_and(tx); - return 1; - - case EX_OR: - if(!complex_tree(tx, tree->data.child.left)) - return 0; - if(!complex_tree(tx, tree->data.child.right)) - return 0; - text_complex_or(tx); - return 1; - - default: - return 0; - } -} - -/* store a word which is represented by a complex expression. */ - -static void store_complex(STREAM *refstr, TEXT_RLD *tr, - int size, EX_TREE *value) -{ - TEXT_COMPLEX tx; - - change_dot(tr, size); /* About to store - update DOT */ - - implicit_gbl(value); /* Turn undefined symbols into globals */ - - text_complex_begin(&tx); /* Open complex expression */ - - if(!complex_tree(&tx, value)) /* Translate */ - { - report(refstr, "Invalid expression\n"); - store_word(refstr, tr, size, 0); - } - else - { - list_word(refstr, DOT, 0, size, "C"); - text_complex_commit(tr, &DOT, size, &tx, 0); - } -} - -/* store_complex_displaced is the same as store_complex but uses the - "displaced" RLD code */ - -static void store_complex_displaced(STREAM *refstr, TEXT_RLD *tr, - int size, - EX_TREE *value) -{ - TEXT_COMPLEX tx; - - change_dot(tr, size); - - implicit_gbl(value); /* Turn undefined symbols into globals */ - - text_complex_begin(&tx); - - if(!complex_tree(&tx, value)) - { - report(refstr, "Invalid expression\n"); - store_word(refstr, tr, size, 0); - } - else - { - list_word(refstr, DOT, 0, size, "C"); - text_complex_commit_displaced(tr, &DOT, size, &tx, 0); - } -} - -/* - mode_extension - writes the extension word required by an addressing - mode */ - -static void mode_extension(TEXT_RLD *tr, ADDR_MODE *mode, - STREAM *str) -{ - EX_TREE *value = mode->offset; - SYMBOL *sym; - unsigned offset; - - /* Also frees the mode. */ - - if(value == NULL) - { - free_addr_mode(mode); - return; - } - - if(value->type == EX_LIT) - { - if(mode->rel) /* PC-relative? */ - store_displaced_word(str, tr, 2, value->data.lit); - else - store_word(str, tr, 2, value->data.lit); /* Just a - known - value. */ - } - else if(express_sym_offset(value, &sym, &offset)) - { - if((sym->flags & (GLOBAL|DEFINITION)) == GLOBAL) - { - /* Reference to a global symbol. */ - /* Global symbol plus offset */ - if(mode->rel) - store_global_displaced_offset_word(str, tr, - 2, offset, sym->label); - else - store_global_offset_word(str, tr, 2, offset, - sym->label); - } - else - { - /* Relative to non-external symbol. */ - if(current_pc->section == sym->section) - { - /* In the same section */ - if(mode->rel) - { - /* I can compute this myself. */ - store_word(str, tr, 2, - sym->value + offset - DOT - 2); - } - else - store_internal_word(str, tr, 2, sym->value+offset); - } - else - { - /* In a different section */ - if(mode->rel) - store_psect_displaced_offset_word(str, tr, 2, - sym->value+offset, sym->section->label); - else - store_psect_offset_word(str, tr, 2, - sym->value+offset, sym->section->label); - } - } - } - else - { - /* Complex relocation */ - - if(mode->rel) - store_complex_displaced(str, tr, 2, mode->offset); - else - store_complex(str, tr, 2, mode->offset); - } - - free_addr_mode(mode); -} - -/* eval_defined - take an EX_TREE and returns TRUE if the tree - represents "defined" symbols. */ - -int eval_defined(EX_TREE *value) -{ - switch(value->type) - { - case EX_LIT: - return 1; - case EX_SYM: - return 1; - case EX_UNDEFINED_SYM: - return 0; - case EX_AND: - return eval_defined(value->data.child.left) && - eval_defined(value->data.child.right); - case EX_OR: - return eval_defined(value->data.child.left) || - eval_defined(value->data.child.right); - default: - return 0; - } -} - -/* eval_undefined - take an EX_TREE and returns TRUE if it represents - "undefined" symbols. */ - -int eval_undefined(EX_TREE *value) -{ - switch(value->type) - { - case EX_UNDEFINED_SYM: - return 1; - case EX_SYM: - return 0; - case EX_AND: - return eval_undefined(value->data.child.left) && - eval_undefined(value->data.child.right); - case EX_OR: - return eval_undefined(value->data.child.left) || - eval_undefined(value->data.child.right); - default: - return 0; - } -} - -/* push_cond - a new conditional (.IF) block has been activated. Push - it's context. */ - -void push_cond(int ok, STREAM *str) -{ - last_cond++; - assert(last_cond < MAX_CONDS); - conds[last_cond].ok = ok; - conds[last_cond].file = memcheck(strdup(str->name)); - conds[last_cond].line = str->line; -} - -/* - pop_cond - pop stacked conditionals. */ - -void pop_cond(int to) -{ - while(last_cond > to) - { - free(conds[last_cond].file); - last_cond--; - } -} - -/* Parses a string from the input stream. */ -/* If not bracketed by <...> or ^/.../, then */ -/* the string is delimited by trailing comma or whitespace. */ -/* Allows nested <>'s */ - -char *getstring(char *cp, char **endp) -{ - int len; - int start; - char *str; - - if(!brackrange(cp, &start, &len, endp)) - { - start = 0; - len = strcspn(cp, " \t\n,;"); - if(endp) - *endp = cp + len; - } - - str = memcheck(malloc(len + 1)); - memcpy(str, cp + start, len); - str[len] = 0; - - return str; -} - -/* Get what would be the operation code from the line. */ -/* Used to find the ends of streams without evaluating them, like - finding the closing .ENDM on a macro definition */ - -SYMBOL *get_op(char *cp, char **endp) -{ - int local; - char *label; - SYMBOL *op; - - cp = skipwhite(cp); - if(EOL(*cp)) - return NULL; - - label = get_symbol(cp, &cp, &local); - if(label == NULL) - return NULL; /* No operation code. */ - - cp = skipwhite(cp); - if(*cp == ':') /* A label definition? */ - { - cp++; - if(*cp == ':') - cp++; /* Skip it */ - free(label); - label = get_symbol(cp, &cp, NULL); - if(label == NULL) - return NULL; - } - - op = lookup_sym(label, &system_st); - free(label); - - if(endp) - *endp = cp; - - return op; -} - -/* Here's where I pretend I'm a C++ compiler. :-/ */ - -/* *** derive a MACRO_STREAM from a BUFFER_STREAM with a few other args */ - -typedef struct macro_stream -{ - BUFFER_STREAM bstr; /* Base class: buffer stream */ - int nargs; /* Add number-of-macro-arguments */ - int cond; /* Add saved conditional stack */ -} MACRO_STREAM; - -/* macro_stream_delete is called when a macro expansion is - exhausted. The unique behavior is to unwind any stacked - conditionals. This allows a nested .MEXIT to work. */ - -void macro_stream_delete(STREAM *str) -{ - MACRO_STREAM *mstr = (MACRO_STREAM *)str; - pop_cond(mstr->cond); - buffer_stream_delete(str); -} - -STREAM_VTBL macro_stream_vtbl = { macro_stream_delete, - buffer_stream_gets, - buffer_stream_rewind }; - -STREAM *new_macro_stream(STREAM *refstr, BUFFER *buf, MACRO *mac, - ARG *args) -{ - MACRO_STREAM *mstr = memcheck(malloc(sizeof(MACRO_STREAM))); - - { - char *name = memcheck(malloc(strlen(refstr->name) + 32)); - sprintf(name, "%s:%d->%s", refstr->name, refstr->line, - mac->sym.label); - buffer_stream_construct(&mstr->bstr, buf, name); - free(name); - } - - mstr->bstr.stream.vtbl = ¯o_stream_vtbl; - /* Count the args and save their number */ - for(mstr->nargs = 0; args; args = args->next, mstr->nargs++) - ; - mstr->cond = last_cond; - return &mstr->bstr.stream; -} - -/* read_body fetches the body of .MACRO, .REPT, .IRP, or .IRPC into a - BUFFER. */ - -void read_body(STACK *stack, BUFFER *gb, char *name, - int called) -{ - int nest; - - /* Read the stream in until the end marker is hit */ - - /* Note: "called" says that this body is being pulled from a macro - library, and so under no circumstance should it be listed. */ - - nest = 1; - for(;;) - { - SYMBOL *op; - char *nextline; - char *cp; - - nextline = stack_gets(stack); /* Now read the line */ - if(nextline == NULL) /* End of file. */ - { - report(stack->top, "Macro body not closed\n"); - break; - } - - if(!called && (list_level - 1 + list_md) > 0) - { - list_flush(); - list_source(stack->top, nextline); - } - - op = get_op(nextline, &cp); - - if(op == NULL) /* Not a pseudo-op */ - { - buffer_append_line(gb, nextline); - continue; - } - if(op->section->type == PSEUDO) - { - if(op->value == P_MACRO || - op->value == P_REPT || - op->value == P_IRP || - op->value == P_IRPC) - nest++; - - if(op->value == P_ENDM || - op->value == P_ENDR) - { - nest--; - /* If there's a name on the .ENDM, then */ - /* close the body early if it matches the definition */ - if(name && op->value == P_ENDM) - { - cp = skipwhite(cp); - if(!EOL(*cp)) - { - char *label = get_symbol(cp, &cp, NULL); - if(label) - { - if(strcmp(label, name) == 0) - nest = 0; /* End of macro body. */ - free(label); - } - } - } - } - - if(nest == 0) - return; /* All done. */ - } - - buffer_append_line(gb, nextline); - } -} - -/* Diagnostic: dumpmacro dumps a macro definition to stdout. - I used this for debugging; it's not called at all right now, but - I hate to delete good code. */ - -void dumpmacro(MACRO *mac, FILE *fp) -{ - ARG *arg; - - fprintf(fp, ".MACRO %s ", mac->sym.label); - - for(arg = mac->args; arg != NULL; arg = arg->next) - { - fputs(arg->label, fp); - if(arg->value) - { - fputc('=', fp); - fputs(arg->value, fp); - } - fputc(' ', fp); - } - fputc('\n', fp); - - fputs(mac->text->buffer, fp); - - fputs(".ENDM\n", fp); -} - -/* defmacro - define a macro. */ -/* Also used by .MCALL to pull macro definitions from macro libraries */ - -MACRO *defmacro(char *cp, STACK *stack, int called) -{ - MACRO *mac; - ARG *arg, **argtail; - char *label; - - cp = skipwhite(cp); - label = get_symbol(cp, &cp, NULL); - if(label == NULL) - { - report(stack->top, "Invalid macro definition\n"); - return NULL; - } - - /* Allow redefinition of a macro; new definition replaces the old. */ - mac = (MACRO *)lookup_sym(label, ¯o_st); - if(mac) - { - /* Remove from the symbol table... */ - remove_sym(&mac->sym, ¯o_st); - free_macro(mac); - } - - mac = new_macro(label); - - add_table(&mac->sym, ¯o_st); - - argtail = &mac->args; - cp = skipdelim(cp); - - while(!EOL(*cp)) - { - arg = new_arg(); - if(arg->locsym = (*cp == '?')) /* special argument flag? */ - cp++; - arg->label = get_symbol(cp, &cp, NULL); - if(arg->label == NULL) - { - /* It turns out that I have code which is badly formatted - but which MACRO.SAV assembles. Sigh. */ - /* So, just quit defining arguments. */ - break; -#if 0 - report(str, "Illegal macro argument\n"); - remove_sym(&mac->sym, ¯o_st); - free_macro(mac); - return NULL; -#endif - } - - cp = skipwhite(cp); - if(*cp == '=') - { - /* Default substitution given */ - arg->value = getstring(cp+1, &cp); - if(arg->value == NULL) - { - report(stack->top, "Illegal macro argument\n"); - remove_sym(&mac->sym, ¯o_st); - free_macro(mac); - return NULL; - } - } - - /* Append to list of arguments */ - arg->next = NULL; - *argtail = arg; - argtail = &arg->next; - - cp = skipdelim(cp); - } - - /* Read the stream in until the end marker is hit */ - { - BUFFER *gb; - int levelmod = 0; - - gb = new_buffer(); - - if(!called && !list_md) - { - list_level--; - levelmod = 1; - } - - read_body(stack, gb, mac->sym.label, called); - - list_level += levelmod; - - if(mac->text != NULL) /* Discard old macro body */ - buffer_free(mac->text); - - mac->text = gb; - } - - return mac; -} - -/* find_arg - looks for an arg with the given name in the given - argument list */ - -static ARG *find_arg(ARG *arg, char *name) -{ - for(; arg != NULL; arg = arg->next) - { - if(strcmp(arg->label, name) == 0) - return arg; - } - - return NULL; -} - -/* subst_args - given a BUFFER and a list of args, generate a new - BUFFER with argument replacement having taken place. */ - -BUFFER *subst_args(BUFFER *text, ARG *args) -{ - char *in; - char *begin; - BUFFER *gb; - char *label; - ARG *arg; - - gb = new_buffer(); - - /* Blindly look for argument symbols in the input. */ - /* Don't worry about quotes or comments. */ - - for(begin = in = text->buffer; in < text->buffer + text->length;) - { - char *next; - - if(issym(*in)) - { - label = get_symbol(in, &next, NULL); - if(label) - { - if(arg = find_arg(args, label)) - { - /* An apostrophy may appear before or after the symbol. */ - /* In either case, remove it from the expansion. */ - - if(in > begin && in[-1] == '\'') - in --; /* Don't copy it. */ - if(*next == '\'') - next++; - - /* Copy prior characters */ - buffer_appendn(gb, begin, in-begin); - /* Copy replacement string */ - buffer_append_line(gb, arg->value); - in = begin = next; - --in; /* prepare for subsequent increment */ - } - free(label); - in = next; - } - else - in++; - } - else - in++; - } - - /* Append the rest of the text */ - buffer_appendn(gb, begin, in - begin); - - return gb; /* Done. */ -} - -/* eval_arg - the language allows an argument expression to be given - as "\expression" which means, evaluate the expression and - substitute the numeric value in the current radix. */ - -void eval_arg(STREAM *refstr, ARG *arg) -{ - /* Check for value substitution */ - - if(arg->value[0] == '\\') - { - EX_TREE *value = parse_expr(arg->value+1, 0); - unsigned word = 0; - char temp[10]; - if(value->type != EX_LIT) - { - report(refstr, "Constant value required\n"); - } - else - word = value->data.lit; - - free_tree(value); - - /* printf can't do base 2. */ - my_ultoa(word & 0177777, temp, radix); - free(arg->value); - arg->value = memcheck(strdup(temp)); - } - -} - -/* expandmacro - return a STREAM containing the expansion of a macro */ - -STREAM *expandmacro(STREAM *refstr, MACRO *mac, char *cp) -{ - ARG *arg, *args, *macarg; - char *label; - STREAM *str; - BUFFER *buf; - - args = NULL; - arg = NULL; - - /* Parse the arguments */ - - while(!EOL(*cp)) - { - char *nextcp; - /* Check for named argument */ - label = get_symbol(cp, &nextcp, NULL); - if(label && - (nextcp = skipwhite(nextcp), *nextcp == '=') && - (macarg = find_arg(mac->args, label))) - { - /* Check if I've already got a value for it */ - if(find_arg(args, label) != NULL) - { - report(refstr, "Duplicate submission of keyword " - "argument %s\n", label); - free(label); - free_args(args); - return NULL; - } - - arg = new_arg(); - arg->label = label; - nextcp = skipwhite(nextcp+1); - arg->value = getstring(nextcp, &nextcp); - } - else - { - if(label) - free(label); - - /* Find correct positional argument */ - - for(macarg = mac->args; macarg != NULL; macarg = macarg->next) - { - if(find_arg(args, macarg->label) == NULL) - break; /* This is the next positional arg */ - } - - if(macarg == NULL) - break; /* Don't pick up any more arguments. */ - - arg = new_arg(); - arg->label = memcheck(strdup(macarg->label)); /* Copy the name */ - arg->value = getstring(cp, &nextcp); - } - - arg->next = args; - args = arg; - - eval_arg(refstr, arg); /* Check for expression evaluation */ - - cp = skipdelim(nextcp); - } - - /* Now go back and fill in defaults */ - - { - int locsym; - if(last_lsb != lsb) - locsym = last_locsym = 32768; - else - locsym = last_locsym; - last_lsb = lsb; - - for(macarg = mac->args; macarg != NULL; macarg = macarg->next) - { - arg = find_arg(args, macarg->label); - if(arg == NULL) - { - arg = new_arg(); - arg->label = memcheck(strdup(macarg->label)); - if(macarg->locsym) - { - char temp[32]; - /* Here's where we generate local labels */ - sprintf(temp, "%d$", locsym++); - arg->value = memcheck(strdup(temp)); - } - else if(macarg->value) - { - arg->value = memcheck(strdup(macarg->value)); - } - else - arg->value = memcheck(strdup("")); - - arg->next = args; - args = arg; - } - } - - last_locsym = locsym; - } - - buf = subst_args(mac->text, args); - - str = new_macro_stream(refstr, buf, mac, args); - - free_args(args); - buffer_free(buf); - - return str; -} - -/* *** implement REPT_STREAM */ - -typedef struct rept_stream -{ - BUFFER_STREAM bstr; - int count; /* The current repeat countdown */ - int savecond; /* conditional stack level at time of - expansion */ -} REPT_STREAM; - -/* rept_stream_gets gets a line from a repeat stream. At the end of - each count, the coutdown is decreated and the stream is reset to - it's beginning. */ - -char *rept_stream_gets(STREAM *str) -{ - REPT_STREAM *rstr = (REPT_STREAM *)str; - char *cp; - - for(;;) - { - if((cp = buffer_stream_gets(str)) != NULL) - return cp; - - if(--rstr->count <= 0) - return NULL; - - buffer_stream_rewind(str); - } -} - -/* rept_stream_delete unwinds nested conditionals like .MEXIT does. */ - -void rept_stream_delete(STREAM *str) -{ - REPT_STREAM *rstr = (REPT_STREAM *)str; - pop_cond(rstr->savecond); /* complete unterminated - conditionals */ - buffer_stream_delete(&rstr->bstr.stream); -} - -/* The VTBL */ - -STREAM_VTBL rept_stream_vtbl = { rept_stream_delete, - rept_stream_gets, - buffer_stream_rewind }; - -/* expand_rept is called when a .REPT is encountered in the input. */ - -STREAM *expand_rept(STACK *stack, char *cp) -{ - EX_TREE *value; - BUFFER *gb; - REPT_STREAM *rstr; - int levelmod; - - value = parse_expr(cp, 0); - if(value->type != EX_LIT) - { - report(stack->top, ".REPT value must be constant\n"); - free_tree(value); - return NULL; - } - - gb = new_buffer(); - - levelmod = 0; - if(!list_md) - { - list_level--; - levelmod = 1; - } - - read_body(stack, gb, NULL, FALSE); - - list_level += levelmod; - - rstr = memcheck(malloc(sizeof(REPT_STREAM))); - { - char *name = memcheck(malloc(strlen(stack->top->name) + 32)); - sprintf(name, "%s:%d->.REPT", stack->top->name, stack->top->line); - buffer_stream_construct(&rstr->bstr, gb, name); - free(name); - } - - rstr->count = value->data.lit; - rstr->bstr.stream.vtbl = &rept_stream_vtbl; - rstr->savecond = last_cond; - - buffer_free(gb); - free_tree(value); - - return &rstr->bstr.stream; -} - -/* *** implement IRP_STREAM */ - -typedef struct irp_stream -{ - BUFFER_STREAM bstr; - char *label; /* The substitution label */ - char *items; /* The substitution items (in source code - format) */ - int offset; /* Current offset into "items" */ - BUFFER *body; /* Original body */ - int savecond; /* Saved conditional level */ -} IRP_STREAM; - -/* irp_stream_gets expands the IRP as the stream is read. */ -/* Each time an iteration is exhausted, the next iteration is - generated. */ - -char *irp_stream_gets(STREAM *str) -{ - IRP_STREAM *istr = (IRP_STREAM *)str; - char *cp; - BUFFER *buf; - ARG *arg; - - for(;;) - { - if((cp = buffer_stream_gets(str)) != NULL) - return cp; - - cp = istr->items + istr->offset; - - if(!*cp) - return NULL; /* No more items. EOF. */ - - arg = new_arg(); - arg->next = NULL; - arg->locsym = 0; - arg->label = istr->label; - arg->value = getstring(cp, &cp); - cp = skipdelim(cp); - istr->offset = cp - istr->items; - - eval_arg(str, arg); - buf = subst_args(istr->body, arg); - - free(arg->value); - free(arg); - buffer_stream_set_buffer(&istr->bstr, buf); - buffer_free(buf); - } -} - -/* irp_stream_delete - also pops the conditional stack */ - -void irp_stream_delete(STREAM *str) -{ - IRP_STREAM *istr = (IRP_STREAM *)str; - - pop_cond(istr->savecond); /* complete unterminated - conditionals */ - - buffer_free(istr->body); - free(istr->items); - free(istr->label); - buffer_stream_delete(str); -} - -STREAM_VTBL irp_stream_vtbl = { irp_stream_delete, irp_stream_gets, - buffer_stream_rewind }; - -/* expand_irp is called when a .IRP is encountered in the input. */ - -STREAM *expand_irp(STACK *stack, char *cp) -{ - char *label, *items; - BUFFER *gb; - int levelmod = 0; - IRP_STREAM *str; - - label = get_symbol(cp, &cp, NULL); - if(!label) - { - report(stack->top, "Illegal .IRP syntax\n"); - return NULL; - } - - cp = skipdelim(cp); - - items = getstring(cp, &cp); - if(!items) - { - report(stack->top, "Illegal .IRP syntax\n"); - free(label); - return NULL; - } - - gb = new_buffer(); - - levelmod = 0; - if(!list_md) - { - list_level--; - levelmod++; - } - - read_body(stack, gb, NULL, FALSE); - - list_level += levelmod; - - str = memcheck(malloc(sizeof(IRP_STREAM))); - { - char *name = memcheck(malloc(strlen(stack->top->name) + 32)); - sprintf(name, "%s:%d->.IRP", stack->top->name, stack->top->line); - buffer_stream_construct(&str->bstr, NULL, name); - free(name); - } - - str->bstr.stream.vtbl = &irp_stream_vtbl; - - str->body = gb; - str->items = items; - str->offset = 0; - str->label = label; - str->savecond = last_cond; - - return &str->bstr.stream; -} - -/* *** implement IRPC_STREAM */ - -typedef struct irpc_stream -{ - BUFFER_STREAM bstr; - char *label; /* The substitution label */ - char *items; /* The substitution items (in source code - format) */ - int offset; /* Current offset in "items" */ - BUFFER *body; /* Original body */ - int savecond; /* conditional stack at invocation */ -} IRPC_STREAM; - -/* irpc_stream_gets - same comments apply as with irp_stream_gets, but - the substitution is character-by-character */ - -char *irpc_stream_gets(STREAM *str) -{ - IRPC_STREAM *istr = (IRPC_STREAM *)str; - char *cp; - BUFFER *buf; - ARG *arg; - - for(;;) - { - if((cp = buffer_stream_gets(str)) != NULL) - return cp; - - cp = istr->items + istr->offset; - - if(!*cp) - return NULL; /* No more items. EOF. */ - - arg = new_arg(); - arg->next = NULL; - arg->locsym = 0; - arg->label = istr->label; - arg->value = memcheck(malloc(2)); - arg->value[0] = *cp++; - arg->value[1] = 0; - istr->offset = cp - istr->items; - - buf = subst_args(istr->body, arg); - - free(arg->value); - free(arg); - buffer_stream_set_buffer(&istr->bstr, buf); - buffer_free(buf); - } -} - -/* irpc_stream_delete - also pops contidionals */ - -void irpc_stream_delete(STREAM *str) -{ - IRPC_STREAM *istr = (IRPC_STREAM *)str; - pop_cond(istr->savecond); /* complete unterminated - conditionals */ - buffer_free(istr->body); - free(istr->items); - free(istr->label); - buffer_stream_delete(str); -} - -STREAM_VTBL irpc_stream_vtbl = { irpc_stream_delete, - irpc_stream_gets, - buffer_stream_rewind }; - -/* expand_irpc - called when .IRPC is encountered in the input */ - -STREAM *expand_irpc(STACK *stack, char *cp) -{ - char *label, *items; - BUFFER *gb; - int levelmod = 0; - IRPC_STREAM *str; - - label = get_symbol(cp, &cp, NULL); - if(!label) - { - report(stack->top, "Illegal .IRPC syntax\n"); - return NULL; - } - - cp = skipdelim(cp); - - items = getstring(cp, &cp); - if(!items) - { - report(stack->top, "Illegal .IRPC syntax\n"); - free(label); - return NULL; - } - - gb = new_buffer(); - - levelmod = 0; - if(!list_md) - { - list_level--; - levelmod++; - } - - read_body(stack, gb, NULL, FALSE); - - list_level += levelmod; - - str = memcheck(malloc(sizeof(IRPC_STREAM))); - { - char *name = memcheck(malloc(strlen(stack->top->name) + 32)); - sprintf(name, "%s:%d->.IRPC", stack->top->name, stack->top->line); - buffer_stream_construct(&str->bstr, NULL, name); - free(name); - } - - str->bstr.stream.vtbl = &irpc_stream_vtbl; - str->body = gb; - str->items = items; - str->offset = 0; - str->label = label; - str->savecond = last_cond; - - return &str->bstr.stream; -} - -/* go_section - sets current_pc to a new program section */ - -void go_section(TEXT_RLD *tr, SECTION *sect) -{ - if(current_pc->section == sect) - return; /* This is too easy */ - - /* save current PC value for old section */ - current_pc->section->pc = DOT; - - /* Set current section and PC value */ - current_pc->section = sect; - DOT = sect->pc; -} - -/* - store_value - used to store a value represented by an expression - tree into the object file. Used by do_word and .ASCII/.ASCIZ. -*/ - -static void store_value(STACK *stack, TEXT_RLD *tr, - int size, EX_TREE *value) -{ - SYMBOL *sym; - unsigned offset; - - implicit_gbl(value); /* turn undefined symbols into globals */ - - if(value->type == EX_LIT) - { - store_word(stack->top, tr, size, value->data.lit); - } - else if(!express_sym_offset(value, &sym, &offset)) - { - store_complex(stack->top, tr, size, value); - } - else - { - if((sym->flags & (GLOBAL|DEFINITION)) == GLOBAL) - { - store_global_offset_word(stack->top, tr, size, - sym->value+offset, - sym->label); - } - else if(sym->section != current_pc->section) - { - store_psect_offset_word(stack->top, tr, size, - sym->value+offset, - sym->section->label); - } - else - { - store_internal_word(stack->top, tr, size, - sym->value+offset); - } - } -} - -/* do_word - used by .WORD, .BYTE, and implied .WORD. */ - -static int do_word(STACK *stack, TEXT_RLD *tr, char *cp, int size) -{ - - if(size == 2 && (DOT & 1)) - { - report(stack->top, ".WORD on odd boundary\n"); - store_word(stack->top, tr, 1, 0); /* Align it */ - } - - do - { - EX_TREE *value = parse_expr(cp, 0); - - store_value(stack, tr, size, value); - - cp = skipdelim(value->cp); - - free_tree(value); - - } while(cp = skipdelim(cp), !EOL(*cp)); - - return 1; -} - -/* - check_branch - check branch distance. -*/ - -static int check_branch(STACK *stack, unsigned offset, int min, int max) -{ - int s_offset; - /* Sign-extend */ - if(offset & 0100000) - s_offset = offset | ~0177777; - else - s_offset = offset & 077777; - if(s_offset > max || s_offset < min) - { - char temp[16]; - /* printf can't do signed octal. */ - my_ltoa(s_offset, temp, 8); - report(stack->top, - "Branch target out of range (distance=%s)\n", - temp); - return 0; - } - return 1; -} - -/* assemble - read a line from the input stack, assemble it. */ - -/* This function is way way too large, because I just coded most of - the operation code and pseudo-op handling right in line. */ - -int assemble(STACK *stack, TEXT_RLD *tr) -{ - char *cp; /* Parse character pointer */ - char *opcp; /* Points to operation mnemonic text */ - char *ncp; /* "next" cp */ - char *label; /* A label */ - char *line; /* The whole line */ - SYMBOL *op; /* The operation SYMBOL */ - int local; /* Whether a label is a local label or - not */ - - line = stack_gets(stack); - if(line == NULL) - return -1; /* Return code for EOF. */ - - cp = line; - - /* Frankly, I don't need to keep "line." But I found it quite - handy during debugging, to see what the whole operation was, - when I'm down to parsing the second operand and things aren't - going right. */ - - stmtno++; /* Increment statement number */ - - list_source(stack->top, line); /* List source */ - - if(suppressed) - { - /* Assembly is suppressed by unsatisfoed conditional. Look - for ending and enabling statements. */ - - op = get_op(cp, &cp); /* Look at operation code */ - - /* FIXME: this code will blindly look into .REM commentary and - find operation codes. Incidentally, so will read_body. */ - - if(op == NULL) - return 1; /* Not found. Don't care. */ - if(op->section->type != PSEUDO) - return 1; /* Not a pseudo-op. */ - switch(op->value) - { - case P_IF: - case P_IFDF: - suppressed++; /* Nested. Suppressed. */ - break; - case P_IFTF: - if(suppressed == 1) /* Reduce suppression from 1 to 0. */ - suppressed = 0; - break; - case P_IFF: - if(suppressed == 1) /* Can reduce suppression from 1 to 0. */ - { - if(!conds[last_cond].ok) - suppressed = 0; - } - break; - case P_IFT: - if(suppressed == 1) /* Can reduce suppression from 1 to 0. */ - { - if(conds[last_cond].ok) - suppressed = 0; - } - break; - case P_ENDC: - suppressed--; /* Un-nested. */ - if(suppressed == 0) - pop_cond(last_cond-1); /* Re-enabled. */ - break; - } - return 1; - } - - /* The line may begin with "label:[:]" */ - - opcp = cp; - if((label = get_symbol(cp, &ncp, &local)) != NULL) - { - int flag = PERMANENT|DEFINITION|local; - SYMBOL *sym; - - ncp = skipwhite(ncp); - if(*ncp == ':') /* Colon, for symbol definition? */ - { - ncp++; - /* maybe it's a global definition */ - if(*ncp == ':') - { - flag |= GLOBAL; /* Yes, include global flag */ - ncp++; - } - - sym = add_sym(label, DOT, flag, current_pc->section, &symbol_st); - cp = ncp; - - if(sym == NULL) - report(stack->top, "Illegal symbol definition %s\n", label); - - free(label); - - /* See if local symbol block should be incremented */ - if(!enabl_lsb && !local) - lsb++; - - cp = skipwhite(ncp); - opcp = cp; - label = get_symbol(cp, &ncp, NULL); /* Now, get what follows */ - } - } - - /* PSEUDO P_IIF jumps here. */ -reassemble: - cp = skipwhite(cp); - - if(EOL(*cp)) - return 1; /* It's commentary. All done. */ - - if(label) /* Something looks like a label. */ - { - /* detect assignment */ - - ncp = skipwhite(ncp); /* The pointer to the text that - follows the symbol */ - - if(*ncp == '=') - { - unsigned flags; - EX_TREE *value; - SYMBOL *sym; - - cp = ncp; - - /* Symbol assignment. */ - - flags = DEFINITION|local; - cp++; - if(*cp == '=') - { - flags |= GLOBAL; /* Global definition */ - cp++; - } - if(*cp == ':') - { - flags |= PERMANENT; - cp++; - } - - cp = skipwhite(cp); - - value = parse_expr(cp, 0); - - /* Special code: if the symbol is the program counter, - this is harder. */ - - if(strcmp(label, ".") == 0) - { - if(current_pc->section->flags & PSECT_REL) - { - SYMBOL *sym; - unsigned offset; - - /* Express the given expression as a symbol and an - offset. The symbol must not be global, the - section must = current. */ - - if(!express_sym_offset(value, &sym, &offset)) - { - report(stack->top, "Illegal ORG\n"); - } - else if((sym->flags & (GLOBAL|DEFINITION)) == GLOBAL) - { - report(stack->top, - "Can't ORG to external location\n"); - } - else if(sym->flags & UNDEFINED) - { - report(stack->top, "Can't ORG to undefined sym\n"); - } - else if(sym->section != current_pc->section) - { - report(stack->top, - "Can't ORG to alternate section " - "(use PSECT)\n"); - } - else - { - DOT = sym->value + offset; - list_value(stack->top, DOT); - change_dot(tr, 0); - } - } - else - { - /* If the current section is absolute, the value - must be a literal */ - if(value->type != EX_LIT) - { - report(stack->top, - "Can't ORG to non-absolute location\n"); - free_tree(value); - free(label); - return 0; - } - DOT = value->data.lit; - list_value(stack->top, DOT); - change_dot(tr, 0); - } - free_tree(value); - free(label); - return 1; - } - - /* regular symbols */ - if(value->type == EX_LIT) - { - sym = add_sym(label, value->data.lit, - flags, &absolute_section, &symbol_st); - } - else if(value->type == EX_SYM || - value->type == EX_TEMP_SYM) - { - sym = add_sym(label, value->data.symbol->value, - flags, value->data.symbol->section, &symbol_st); - } - else - { - report(stack->top, "Complex expression cannot be assigned " - "to a symbol\n"); - - if(!pass) - { - /* This may work better in pass 2 - something in - RT-11 monitor needs the symbol to apear to be - defined even if I can't resolve it's value. */ - sym = add_sym(label, 0, UNDEFINED, - &absolute_section, &symbol_st); - } - else - sym = NULL; - } - - if(sym != NULL) - list_value(stack->top, sym->value); - - free_tree(value); - free(label); - - return sym != NULL; - } - - /* Try to resolve macro */ - - op = lookup_sym(label, ¯o_st); - if(op && - op->stmtno < stmtno) - { - STREAM *macstr; - - free(label); - - macstr = expandmacro(stack->top, (MACRO *)op, ncp); - - stack_push(stack, macstr); /* Push macro expansion - onto input stream */ - - return 1; - } - - /* Try to resolve instruction or pseudo */ - op = lookup_sym(label, &system_st); - if(op) - { - cp = ncp; - - free(label); /* Don't need this hanging around anymore */ - - switch(op->section->type) - { - case PSEUDO: - switch(op->value) - { - case P_ENDR: - case P_ENDM: - case P_SBTTL: - case P_LIST: - case P_NLIST: - case P_PRINT: - return 1; /* Accepted, ignored. (An obvious - need: get assembly listing - controls working. ) */ - - case P_IDENT: - { - char endc[6]; - int len; - - cp = skipwhite(cp); - endc[0] = *cp++; - endc[1] = '\n'; - endc[2] = 0; - len = strcspn(cp, endc); - if(len > 6) - len = 6; - - if(ident) /* An existing ident? */ - free(ident); /* Discard it. */ - - ident = memcheck(malloc(len + 1)); - memcpy(ident, cp, len); - ident[len] = 0; - upcase(ident); - - return 1; - } - - case P_RADIX: - { - int old_radix = radix; - radix = strtoul(cp, &cp, 10); - if(radix != 8 && radix != 10 && radix != 16 && - radix != 2) - { - radix = old_radix; - report(stack->top, "Illegal radix\n"); - return 0; - } - return 1; - } - - case P_FLT4: - case P_FLT2: - { - int ok = 1; - - while(!EOL(*cp)) - { - unsigned flt[4]; - if(parse_float(cp, &cp, - (op->value == P_FLT4 ? 4 : 2), - flt)) - { - /* Store the word values */ - store_word(stack->top, tr, 2, flt[0]); - store_word(stack->top, tr, 2, flt[1]); - if(op->value == P_FLT4) - { - store_word(stack->top, tr, - 2, flt[2]); - store_word(stack->top, tr, - 2, flt[3]); - } - } - else - { - report(stack->top, - "Bad floating point format\n"); - ok = 0; - } - cp = skipdelim(cp); - } - return ok; - } - - case P_ERROR: - report(stack->top, "%.*s\n", strcspn(cp, "\n"), cp); - return 0; - - case P_SAVE: - sect_sp++; - sect_stack[sect_sp] = current_pc->section; - return 1; - - case P_RESTORE: - if(sect_sp < 0) - { - report(stack->top, "No saved section for .RESTORE\n"); - return 0; - } - else - { - go_section(tr, sect_stack[sect_sp]); - sect_sp++; - } - return 1; - - case P_NARG: - { - STREAM *str; - MACRO_STREAM *mstr; - int local; - - label = get_symbol(cp, &cp, &local); - - if(label == NULL) - { - report(stack->top, "Bad .NARG syntax\n"); - return 0; - } - - /* Walk up the stream stack to find the - topmost macro stream */ - for(str = stack->top; - str != NULL && - str->vtbl != ¯o_stream_vtbl; - str = str->next) - ; - - if(!str) - { - report(str, ".NARG not within macro expansion\n"); - free(label); - return 0; - } - - mstr = (MACRO_STREAM *)str; - - add_sym(label, mstr->nargs, DEFINITION|local, - &absolute_section, &symbol_st); - free(label); - return 1; - } - - case P_NCHR: - { - char *string; - int local; - label = get_symbol(cp, &cp, &local); - - if(label == NULL) - { - report(stack->top, "Bad .NCHR syntax\n"); - return 0; - } - - cp = skipdelim(cp); - - string = getstring(cp, &cp); - - add_sym(label, strlen(string), - DEFINITION|local, - &absolute_section, &symbol_st); - free(label); - free(string); - return 1; - } - - case P_NTYPE: - { - ADDR_MODE mode; - int local; - - label = get_symbol(cp, &cp, &local); - if(label == NULL) - { - report(stack->top, "Bad .NTYPE syntax\n"); - return 0; - } - - cp = skipdelim(cp); - - if(!get_mode(cp, &cp, &mode)) - { - report(stack->top, - "Bad .NTYPE addressing mode\n"); - free(label); - return 0; - } - - add_sym(label, mode.type, DEFINITION|local, - &absolute_section, &symbol_st); - free_addr_mode(&mode); - free(label); - - return 1; - } - - case P_INCLU: - { - char *name = getstring(cp, &cp); - STREAM *incl; - - if(name == NULL) - { - report(stack->top, "Bad .INCLUDE file name\n"); - return 0; - } - - incl = new_file_stream(name); - if(incl == NULL) - { - report(stack->top, - "Unable to open .INCLUDE file %s\n", name); - free(name); - return 0; - } - - free(name); - - stack_push(stack, incl); - - return 1; - } - - case P_REM: - { - char quote[4]; - /* Read and discard lines until one with a - closing quote */ - - cp = skipwhite(cp); - quote[0] = *cp++; - quote[1] = '\n'; - quote[2] = 0; - - for(;;) - { - cp += strcspn(cp, quote); - if(*cp == quote[0]) - break; /* Found closing quote */ - cp = stack_gets(stack); /* Read next input line */ - if(cp == NULL) - break; /* EOF */ - } - } - return 1; - - case P_IRP: - { - STREAM *str = expand_irp(stack, cp); - if(str) - stack_push(stack, str); - return str != NULL; - } - - case P_IRPC: - { - STREAM *str = expand_irpc(stack, cp); - if(str) - stack_push(stack, str); - return str != NULL; - } - - case P_MCALL: - { - STREAM *macstr; - BUFFER *macbuf; - char *maccp; - int saveline; - MACRO *mac; - int i; - char macfile[FILENAME_MAX]; - char hitfile[FILENAME_MAX]; - - for(;;) - { - cp = skipdelim(cp); - - if(EOL(*cp)) - return 1; - - label = get_symbol(cp, &cp, NULL); - if(!label) - { - report(stack->top, "Illegal .MCALL format\n"); - return 0; - } - - /* See if that macro's already defined */ - if(lookup_sym(label, ¯o_st)) - { - free(label); /* Macro already - registered. No - prob. */ - cp = skipdelim(cp); - continue; - } - - /* Find the macro in the list of included - macro libraries */ - macbuf = NULL; - for(i = 0; i < nr_mlbs; i++) - { - if((macbuf = mlb_entry(mlbs[i], - label)) != NULL) - break; - } - if(macbuf != NULL) - { - macstr = new_buffer_stream(macbuf, label); - buffer_free(macbuf); - } - else - { - strncpy(macfile, label, sizeof(macfile)); - strncat(macfile, ".MAC", sizeof(macfile) - strlen(macfile)); - my_searchenv(macfile, "MCALL", hitfile, sizeof(hitfile)); - if(hitfile[0]) - macstr = new_file_stream(hitfile); - } - - if(macstr != NULL) - { - for(;;) - { - char *mlabel; - maccp = macstr->vtbl->gets(macstr); - if(maccp == NULL) - break; - mlabel = get_symbol(maccp, &maccp, NULL); - if(mlabel == NULL) - continue; - op = lookup_sym(mlabel, &system_st); - free(mlabel); - if(op == NULL) - continue; - if(op->value == P_MACRO) - break; - } - - if(maccp != NULL) - { - STACK macstack = { macstr }; - int savelist = list_level; - saveline = stmtno; - list_level = -1; - mac = defmacro(maccp, &macstack, TRUE); - if(mac == NULL) - { - report(stack->top, - "Failed to define macro " - "called %s\n", - label); - } - - stmtno = saveline; - list_level = savelist; - } - - macstr->vtbl->delete(macstr); - } - else - report(stack->top, - "MACRO %s not found\n", label); - - free(label); - } - } - return 1; - - case P_MACRO: - { - MACRO *mac = defmacro(cp, stack, FALSE); - return mac != NULL; - } - - case P_MEXIT: - { - STREAM *macstr; - - /* Pop a stream from the input. */ - /* It must be the first stream, and it must be */ - /* a macro, rept, irp, or irpc. */ - macstr = stack->top; - if(macstr->vtbl != ¯o_stream_vtbl && - macstr->vtbl != &rept_stream_vtbl && - macstr->vtbl != &irp_stream_vtbl && - macstr->vtbl != &irpc_stream_vtbl) - { - report(stack->top, ".MEXIT not within a macro\n"); - return 0; - } - - /* and finally, pop the macro */ - stack_pop(stack); - - return 1; - } - - case P_REPT: - { - STREAM *reptstr = expand_rept(stack, cp); - if(reptstr) - stack_push(stack, reptstr); - return reptstr != NULL; - } - - case P_ENABL: - - /* FIXME - add all the rest of the options. */ - while(!EOL(*cp)) - { - label = get_symbol(cp, &cp, NULL); - if(strcmp(label, "AMA") == 0) - enabl_ama = 1; - else if(strcmp(label, "LSB") == 0) - { - enabl_lsb = 1; - lsb++; - } - else if(strcmp(label, "GBL") == 0) - enabl_gbl = 1; - free(label); - cp = skipdelim(cp); - } - return 1; - - case P_DSABL: - - /* FIXME Ditto as for .ENABL */ - while(!EOL(*cp)) - { - label = get_symbol(cp, &cp, NULL); - if(strcmp(label, "AMA") == 0) - enabl_ama = 0; - else if(strcmp(label, "LSB") == 0) - enabl_lsb = 0; - else if(strcmp(label, "GBL") == 0) - enabl_gbl = 0; - free(label); - cp = skipdelim(cp); - } - return 1; - - case P_LIMIT: - store_limits(stack->top, tr); - return 1; - - case P_TITLE: - /* accquire module name */ - if(module_name != NULL) - { - free(module_name); - } - module_name = get_symbol(cp, &cp, NULL); - return 1; - - case P_END: - /* Accquire transfer address */ - cp = skipwhite(cp); - if(!EOL(*cp)) - { - if(xfer_address) - free_tree(xfer_address); - xfer_address = parse_expr(cp, 0); - } - return 1; - - case P_IFDF: - opcp = skipwhite(opcp); - cp = opcp + 3; /* Point cp at the "DF" or - "NDF" part */ - /* Falls into... */ - case P_IIF: - case P_IF: - { - EX_TREE *value; - int ok; - - label = get_symbol(cp, &cp, NULL); /* Get condition */ - cp = skipdelim(cp); - - if(strcmp(label, "DF") == 0) - { - value = parse_expr(cp, 1); - cp = value->cp; - ok = eval_defined(value); - free_tree(value); - } - else if(strcmp(label, "NDF") == 0) - { - value = parse_expr(cp, 1); - cp = value->cp; - ok = eval_undefined(value); - free_tree(value); - } - else if(strcmp(label, "B") == 0) - { - char *thing; - cp = skipwhite(cp); - if(!EOL(*cp)) - thing = getstring(cp, &cp); - else - thing = memcheck(strdup("")); - ok = (*thing == 0); - free(thing); - } - else if(strcmp(label, "NB") == 0) - { - char *thing; - cp = skipwhite(cp); - if(!EOL(*cp)) - thing = getstring(cp, &cp); - else - thing = memcheck(strdup("")); - ok = (*thing != 0); - free(thing); - } - else if(strcmp(label, "IDN") == 0) - { - char *thing1, *thing2; - thing1 = getstring(cp, &cp); - cp = skipdelim(cp); - if(!EOL(*cp)) - thing2 = getstring(cp, &cp); - else - thing2 = memcheck(strdup("")); - ok = (strcmp(thing1, thing2) == 0); - free(thing1); - free(thing2); - } - else if(strcmp(label, "DIF") == 0) - { - char *thing1, *thing2; - thing1 = getstring(cp, &cp); - cp = skipdelim(cp); - if(!EOL(*cp)) - thing2 = getstring(cp, &cp); - else - thing2 = memcheck(strdup("")); - ok = (strcmp(thing1, thing2) != 0); - free(thing1); - free(thing2); - } - else - { - int sword; - unsigned uword; - EX_TREE *value = parse_expr(cp, 0); - - cp = value->cp; - - if(value->type != EX_LIT) - { - report(stack->top, "Bad .IF expression\n"); - list_value(stack->top, 0); - free_tree(value); - ok = FALSE; /* Pick something. */ - } - else - { - unsigned word; - /* Convert to signed and unsigned words */ - sword = value->data.lit & 0x7fff; - - /* FIXME I don't know if the following - is portable enough. */ - if(value->data.lit & 0x8000) - sword |= ~0xFFFF; /* Render negative */ - - /* Reduce unsigned value to 16 bits */ - uword = value->data.lit & 0xffff; - - if(strcmp(label, "EQ") == 0 || - strcmp(label, "Z") == 0) - ok = (uword == 0), word = uword; - else if(strcmp(label, "NE") == 0 || - strcmp(label, "NZ") == 0) - ok = (uword != 0), word = uword; - else if(strcmp(label, "GT") == 0 || - strcmp(label, "G") == 0) - ok = (sword > 0), word = sword; - else if(strcmp(label, "GE") == 0) - ok = (sword >= 0), word = sword; - else if(strcmp(label, "LT") == 0 || - strcmp(label, "L") == 0) - ok = (sword < 0), word = sword; - else if(strcmp(label, "LE") == 0) - ok = (sword <= 0), word = sword; - - list_value(stack->top, word); - - free_tree(value); - } - } - - free(label); - - if(op->value == P_IIF) - { - stmtno++; /* the second half is a - separate statement */ - if(ok) - { - /* The "immediate if" */ - /* Only slightly tricky. */ - cp = skipdelim(cp); - label = get_symbol(cp, &ncp, &local); - goto reassemble; - } - return 1; - } - - push_cond(ok, stack->top); - - if(!ok) - suppressed++; /* Assembly - suppressed - until .ENDC */ - } - return 1; - - case P_IFF: - if(last_cond < 0) - { - report(stack->top, "No conditional block active\n"); - return 0; - } - if(conds[last_cond].ok) /* Suppress if last cond - is true */ - suppressed++; - return 1; - - case P_IFT: - if(last_cond < 0) - { - report(stack->top, "No conditional block active\n"); - return 0; - } - if(!conds[last_cond].ok) /* Suppress if last cond - is false */ - suppressed++; - return 1; - - case P_IFTF: - if(last_cond < 0) - { - report(stack->top, "No conditional block active\n"); - return 0; - } - return 1; /* Don't suppress. */ - - case P_ENDC: - if(last_cond < 0) - { - report(stack->top, "No conditional block active\n"); - return 0; - } - - pop_cond(last_cond-1); - return 1; - - case P_EVEN: - if(DOT & 1) - { - list_word(stack->top, DOT, 0, 1, ""); - DOT++; - } - return 1; - - case P_ODD: - if(!(DOT & 1)) - { - list_word(stack->top, DOT, 0, 1, ""); - DOT++; - } - return 1; - - case P_ASECT: - go_section(tr, &absolute_section); - return 1; - - case P_CSECT: - case P_PSECT: - { - SYMBOL *sectsym; - SECTION *sect; - - label = get_symbol(cp, &cp, NULL); - if(label == NULL) - label = memcheck(strdup("")); /* Allow blank */ - - sectsym = lookup_sym(label, §ion_st); - if(sectsym) - { - sect = sectsym->section; - free(label); - } - else - { - sect = new_section(); - sect->label = label; - sect->flags = 0; - sect->pc = 0; - sect->size = 0; - sect->type = USER; - sections[sector++] = sect; - sectsym = add_sym(label, 0, 0, sect, §ion_st); - } - - if(op->value == P_PSECT) - sect->flags |= PSECT_REL; - else if(op->value == P_CSECT) - sect->flags |= PSECT_REL|PSECT_COM|PSECT_GBL; - - while(cp = skipdelim(cp), !EOL(*cp)) - { - /* Parse section options */ - label = get_symbol(cp, &cp, NULL); - if(strcmp(label, "ABS") == 0) - { - sect->flags &= ~PSECT_REL; /* Not relative */ - sect->flags |= PSECT_COM; /* implies common */ - } - else if(strcmp(label, "REL") == 0) - { - sect->flags |= PSECT_REL; /* Is relative */ - } - else if(strcmp(label, "SAV") == 0) - { - sect->flags |= PSECT_SAV; /* Is root */ - } - else if(strcmp(label, "OVR") == 0) - { - sect->flags |= PSECT_COM; /* Is common */ - } - else if(strcmp(label, "RW") == 0) - { - sect->flags &= ~PSECT_RO; /* Not read-only */ - } - else if(strcmp(label, "RO") == 0) - { - sect->flags |= PSECT_RO; /* Is read-only */ - } - else if(strcmp(label, "I") == 0) - { - sect->flags &= ~PSECT_DATA; /* Not data */ - } - else if(strcmp(label, "D") == 0) - { - sect->flags |= PSECT_DATA; /* data */ - } - else if(strcmp(label, "GBL") == 0) - { - sect->flags |= PSECT_GBL; /* Global */ - } - else if(strcmp(label, "LCL") == 0) - { - sect->flags &= ~PSECT_GBL; /* Local */ - } - else - { - report(stack->top, - "Unknown flag %s given to " - ".PSECT directive\n", label); - free(label); - return 0; - } - - free(label); - } - - go_section(tr, sect); - - return 1; - } /* end PSECT code */ - break; - - case P_WEAK: - case P_GLOBL: - { - SYMBOL *sym; - while(!EOL(*cp)) - { - /* Loop and make definitions for - comma-separated symbols */ - label = get_symbol(cp, &ncp, NULL); - if(label == NULL) - { - report(stack->top, - "Illegal .GLOBL/.WEAK " - "syntax\n"); - return 0; - } - - sym = lookup_sym(label, &symbol_st); - if(sym) - { - sym->flags |= - GLOBAL| - (op->value == P_WEAK ? WEAK : 0); - } - else - sym = add_sym(label, 0, - GLOBAL| - (op->value == P_WEAK ? WEAK : 0), - &absolute_section, &symbol_st); - - free(label); - cp = skipdelim(ncp); - } - } - return 1; - - case P_WORD: - { - /* .WORD might be followed by nothing, which - is an implicit .WORD 0 */ - if(EOL(*cp)) - { - if(DOT & 1) - { - report(stack->top, ".WORD on odd " - "boundary\n"); - DOT++; /* Fix it, too */ - } - store_word(stack->top, tr, 2, 0); - return 1; - } - else - return do_word(stack, tr, cp, 2); - } - - case P_BYTE: - if(EOL(*cp)) - { - /* Blank .BYTE. Same as .BYTE 0 */ - store_word(stack->top, tr, 1, 0); - return 1; - } - else - return do_word(stack, tr, cp, 1); - - case P_BLKW: - case P_BLKB: - { - EX_TREE *value = parse_expr(cp, 0); - int ok = 1; - if(value->type != EX_LIT) - { - report(stack->top, - "Argument to .BLKB/.BLKW " - "must be constant\n"); - ok = 0; - } - else - { - list_value(stack->top, DOT); - DOT += value->data.lit * - (op->value == P_BLKW ? 2 : 1); - change_dot(tr, 0); - } - free_tree(value); - return ok; - } - - case P_ASCIZ: - case P_ASCII: - { - EX_TREE *value; - - do - { - cp = skipwhite(cp); - if(*cp == '<' || *cp == '^') - { - /* A byte value */ - value = parse_expr(cp, 0); - cp = value->cp; - store_value(stack, tr, 1, value); - free_tree(value); - } - else - { - char quote = *cp++; - while(*cp && *cp != '\n' && *cp != quote) - { - store_word(stack->top, tr, 1, *cp++); - } - cp++; /* Skip closing quote */ - } - - cp = skipwhite(cp); - } while(!EOL(*cp)); - - if(op->value == P_ASCIZ) - { - store_word(stack->top, tr, 1, 0); - } - - return 1; - } - - case P_RAD50: - - if(DOT & 1) - { - report(stack->top, ".RAD50 on odd " - "boundary\n"); - DOT++; /* Fix it */ - } - - while(!EOL(*cp)) - { - char endstr[6]; - int len; - char *radstr; - char *radp; - - endstr[0] = *cp++; - endstr[1] = '\n'; - endstr[2] = 0; - - len = strcspn(cp, endstr); - radstr = memcheck(malloc(len + 1)); - memcpy(radstr, cp, len); - radstr[len] = 0; - cp += len; - if(*cp && *cp != '\n') - cp++; - for(radp = radstr; *radp;) - { - unsigned rad; - rad = rad50(radp, &radp); - store_word(stack->top, tr, 2, rad); - } - free(radstr); - - cp = skipwhite(cp); - } - return 1; - - default: - report(stack->top, "Unimplemented directive %s\n", - op->label); - return 0; - - } /* end switch (PSEUDO operation) */ - - case INSTRUCTION: - { - /* The PC must always be even. */ - if(DOT & 1) - { - report(stack->top, - "Instruction on odd address\n"); - DOT++; /* ...and fix it... */ - } - - switch(op->flags & OC_MASK) - { - case OC_NONE: - /* No operands. */ - store_word(stack->top, tr, 2, op->value); - return 1; - - case OC_MARK: - /* MARK, EMT, TRAP */ - { - EX_TREE *value; - unsigned word; - - cp = skipwhite(cp); - if(*cp == '#') - cp++; /* Allow the hash, but - don't require it */ - value = parse_expr(cp, 0); - if(value->type != EX_LIT) - { - report(stack->top, - "Instruction requires " - "simple literal operand\n"); - word = op->value; - } - else - { - word = op->value | value->data.lit; - } - - store_word(stack->top, tr, 2, word); - free_tree(value); - } - return 1; - - case OC_1GEN: - /* One general addressing mode */ - { - ADDR_MODE mode; - unsigned word; - - if(!get_mode(cp, &cp, &mode)) - { - report(stack->top, - "Illegal addressing mode\n"); - return 0; - } - - if(op->value == 0100 && - (mode.type & 07) == 0) - { - report(stack->top, - "JMP Rn is illegal\n"); - /* But encode it anyway... */ - } - - /* Build instruction word */ - word = op->value | mode.type; - store_word(stack->top, tr, 2, word); - mode_extension(tr, &mode, stack->top); - } - return 1; - - case OC_2GEN: - /* Two general addressing modes */ - { - ADDR_MODE left, right; - unsigned word; - - if(!get_mode(cp, &cp, &left)) - { - report(stack->top, - "Illegal addressing mode\n"); - return 0; - } - - if(*cp++ != ',') - { - report(stack->top, "Illegal syntax\n"); - free_addr_mode(&left); - return 0; - } - - if(!get_mode(cp, &cp, &right)) - { - report(stack->top, - "Illegal addressing mode\n"); - free_addr_mode(&left); - return 0; - } - - /* Build instruction word */ - word = op->value | left.type << 6 | right.type; - store_word(stack->top, tr, 2, word); - mode_extension(tr, &left, stack->top); - mode_extension(tr, &right, stack->top); - } - return 1; - - case OC_BR: - /* branches */ - { - EX_TREE *value; - unsigned offset; - - value = parse_expr(cp, 0); - cp = value->cp; - - /* Relative PSECT or absolute? */ - if(current_pc->section->flags & PSECT_REL) - { - SYMBOL *sym; - - /* Can't branch unless I can - calculate the offset. */ - - /* You know, I *could* branch - between sections if I feed the - linker a complex relocation - expression to calculate the - offset. But I won't. */ - - if(!express_sym_offset(value, - &sym, - &offset) || - sym->section != current_pc->section) - { - report(stack->top, - "Bad branch target\n"); - store_word(stack->top, tr, - 2, op->value); - free_tree(value); - return 0; - } - - /* Compute the branch offset and - check for addressability */ - offset += sym->value; - offset -= DOT + 2; - } - else - { - if(value->type != EX_LIT) - { - report(stack->top, - "Bad branch target\n"); - store_word(stack->top, tr, - 2, op->value); - free_tree(value); - return 0; - } - - offset = value->data.lit - - (DOT + 2); - } - - if(!check_branch(stack, offset, -256, - 255)) - offset = 0; - - /* Emit the branch code */ - offset &= 0777;/* Reduce to 9 bits */ - offset >>= 1; /* Shift to become - word offset */ - - store_word(stack->top, tr, - 2, op->value | offset); - - free_tree(value); - } - return 1; - - case OC_SOB: - { - EX_TREE *value; - unsigned reg; - unsigned offset; - - value = parse_expr(cp, 0); - cp = value->cp; - - reg = get_register(value); - free_tree(value); - if(reg == NO_REG) - { - report(stack->top, - "Illegal addressing mode\n"); - return 0; - } - - cp = skipwhite(cp); - if(*cp++ != ',') - { - report(stack->top, "Illegal syntax\n"); - return 0; - } - - value = parse_expr(cp, 0); - cp = value->cp; - - /* Relative PSECT or absolute? */ - if(current_pc->section->flags & PSECT_REL) - { - SYMBOL *sym; - - if(!express_sym_offset(value, - &sym, &offset)) - { - report(stack->top, - "Bad branch target\n"); - free_tree(value); - return 0; - } - /* Must be same section */ - if(sym->section != current_pc->section) - { - report(stack->top, - "Bad branch target\n"); - free_tree(value); - offset = 0; - } - else - { - /* Calculate byte offset */ - offset += DOT + 2; - offset -= sym->value; - } - } - else - { - if(value->type != EX_LIT) - { - report(stack->top, "Bad branch " - "target\n"); - offset = 0; - } - else - { - offset = DOT + 2 - - value->data.lit; - } - } - - if(!check_branch(stack, offset, 0, 126)) - offset = 0; - - offset &= 0177; /* Reduce to 7 bits */ - offset >>= 1; /* Shift to become word offset */ - store_word(stack->top, tr, 2, - op->value | offset | (reg << 6)); - - free_tree(value); - } - return 1; - - case OC_ASH: - /* First op is gen, second is register. */ - { - ADDR_MODE mode; - EX_TREE *value; - unsigned reg; - unsigned word; - - if(!get_mode(cp, &cp, &mode)) - { - report(stack->top, "Illegal addressing mode\n"); - return 0; - } - - cp = skipwhite(cp); - if(*cp++ != ',') - { - report(stack->top, "Illegal addressing mode\n"); - free_addr_mode(&mode); - return 0; - } - value = parse_expr(cp, 0); - cp = value->cp; - - reg = get_register(value); - if(reg == NO_REG) - { - report(stack->top, - "Illegal addressing mode\n"); - free_tree(value); - free_addr_mode(&mode); - return 0; - } - - /* Instruction word */ - word = op->value | mode.type | (reg << 6); - store_word(stack->top, tr, 2, word); - mode_extension(tr, &mode, stack->top); - free_tree(value); - } - return 1; - - case OC_JSR: - /* First op is register, second is gen. */ - { - ADDR_MODE mode; - EX_TREE *value; - unsigned reg; - unsigned word; - - value = parse_expr(cp, 0); - cp = value->cp; - - reg = get_register(value); - if(reg == NO_REG) - { - report(stack->top, - "Illegal addressing mode\n"); - free_tree(value); - return 0; - } - - cp = skipwhite(cp); - if(*cp++ != ',') - { - report(stack->top, - "Illegal addressing mode\n"); - return 0; - } - - if(!get_mode(cp, &cp, &mode)) - { - report(stack->top, - "Illegal addressing mode\n"); - free_tree(value); - return 0; - } - word = op->value | mode.type | (reg << 6); - store_word(stack->top, tr, 2, word); - mode_extension(tr, &mode, stack->top); - free_tree(value); - } - return 1; - - case OC_1REG: - /* One register (RTS) */ - { - EX_TREE *value; - unsigned reg; - - value = parse_expr(cp, 0); - cp = value->cp; - reg = get_register(value); - if(reg == NO_REG) - { - report(stack->top, - "Illegal addressing mode\n"); - free_tree(value); - reg = 0; - } - - store_word(stack->top, tr, - 2, op->value | reg); - free_tree(value); - } - return 1; - - case OC_1FIS: - /* One one gen and one reg 0-3 */ - { - ADDR_MODE mode; - EX_TREE *value; - unsigned reg; - unsigned word; - - if(!get_mode(cp, &cp, &mode)) - { - report(stack->top, - "Illegal addressing mode\n"); - return 0; - } - - cp = skipwhite(cp); - if(*cp++ != ',') - { - report(stack->top, - "Illegal addressing mode\n"); - free_addr_mode(&mode); - return 0; - } - - value = parse_expr(cp, 0); - cp = value->cp; - - reg = get_register(value); - if(reg == NO_REG || reg > 4) - { - report(stack->top, - "Invalid destination register\n"); - reg = 0; - } - - word = op->value | mode.type | (reg << 6); - store_word(stack->top, tr, 2, word); - mode_extension(tr, &mode, stack->top); - free_tree(value); - } - return 1; - - case OC_2FIS: - /* One reg 0-3 and one gen */ - { - ADDR_MODE mode; - EX_TREE *value; - unsigned reg; - unsigned word; - int ok = 1; - - value = parse_expr(cp, 0); - cp = value->cp; - - reg = get_register(value); - if(reg == NO_REG || reg > 4) - { - report(stack->top, - "Illegal source register\n"); - reg = 0; - ok = 0; - } - - cp = skipwhite(cp); - if(*cp++ != ',') - { - report(stack->top, - "Illegal addressing mode\n"); - free_tree(value); - return 0; - } - - if(!get_mode(cp, &cp, &mode)) - { - report(stack->top, - "Illegal addressing mode\n"); - free_tree(value); - return 0; - } - - word = op->value | mode.type | (reg << 6); - store_word(stack->top, tr, 2, word); - mode_extension(tr, &mode, stack->top); - free_tree(value); - } - return 1; - - default: - report(stack->top, - "Unimplemented instruction format\n"); - return 0; - } /* end(handle an instruction) */ - } - break; - } /* end switch(section type) */ - } /* end if (op is a symbol) */ - } - - /* Only thing left is an implied .WORD directive */ - - free(label); - - return do_word(stack, tr, cp, 2); -} - -/* assemble_stack assembles the input stack. It returns the error - count. */ - -static int assemble_stack(STACK *stack, TEXT_RLD *tr) -{ - int res; - int count = 0; - - while((res = assemble(stack, tr)) >= 0) - { - list_flush(); - if(res == 0) - count++; /* Count an error */ - } - - return count; -} - -/* write_globals writes out the GSD prior to the second assembly pass */ - -static void write_globals(FILE *obj) -{ - GSD gsd; - SYMBOL *sym; - SECTION *psect; - SYMBOL_ITER sym_iter; - int isect; - - if(obj == NULL) - return; /* Nothing to do if no OBJ file. */ - - gsd_init(&gsd, obj); - - gsd_mod(&gsd, module_name); - - if(ident) - gsd_ident(&gsd, ident); - - /* write out each PSECT with it's global stuff */ - /* Sections must be written out in the order that they - appear in the assembly file. */ - for(isect = 0; isect < sector; isect++) - { - psect = sections[isect]; - - gsd_psect(&gsd, psect->label, psect->flags, psect->size); - psect->sector = isect; /* Assign it a sector */ - psect->pc = 0; /* Reset it's PC for second pass */ - - sym = first_sym(&symbol_st, &sym_iter); - while(sym) - { - if((sym->flags & GLOBAL) && - sym->section == psect) - { - gsd_global(&gsd, sym->label, - (sym->flags & DEFINITION ? GLOBAL_DEF : 0) | - ((sym->flags & WEAK) ? GLOBAL_WEAK : 0) | - ((sym->section->flags & PSECT_REL) ? GLOBAL_REL : 0) | - 0100, /* Looks undefined, but add it in anyway */ - sym->value); - } - sym = next_sym(&symbol_st, &sym_iter); - } - } - - /* Now write out the transfer address */ - if(xfer_address->type == EX_LIT) - { - gsd_xfer(&gsd, ". ABS.", xfer_address->data.lit); - } - else - { - SYMBOL *sym; - unsigned offset; - if(!express_sym_offset(xfer_address, &sym, &offset)) - { - report(NULL, "Illegal program transfer address\n"); - } - else - { - gsd_xfer(&gsd, sym->section->label, sym->value + offset); - } - } - - gsd_flush(&gsd); - - gsd_end(&gsd); -} - -/* add_symbols adds all the internal symbols. */ - -static void add_symbols(SECTION *current_section) -{ - current_pc = add_sym(".", 0, 0, current_section, &symbol_st); - - reg_sym[0] = add_sym("R0", 0, 0, ®ister_section, &system_st); - reg_sym[1] = add_sym("R1", 1, 0, ®ister_section, &system_st); - reg_sym[2] = add_sym("R2", 2, 0, ®ister_section, &system_st); - reg_sym[3] = add_sym("R3", 3, 0, ®ister_section, &system_st); - reg_sym[4] = add_sym("R4", 4, 0, ®ister_section, &system_st); - reg_sym[5] = add_sym("R5", 5, 0, ®ister_section, &system_st); - reg_sym[6] = add_sym("SP", 6, 0, ®ister_section, &system_st); - reg_sym[7] = add_sym("PC", 7, 0, ®ister_section, &system_st); - - add_sym(".ASCII", P_ASCII, 0, &pseudo_section, &system_st); - add_sym(".ASCIZ", P_ASCIZ, 0, &pseudo_section, &system_st); - add_sym(".ASECT", P_ASECT, 0, &pseudo_section, &system_st); - add_sym(".BLKB", P_BLKB, 0, &pseudo_section, &system_st); - add_sym(".BLKW", P_BLKW, 0, &pseudo_section, &system_st); - add_sym(".BYTE", P_BYTE, 0, &pseudo_section, &system_st); - add_sym(".CSECT", P_CSECT, 0, &pseudo_section, &system_st); - add_sym(".DSABL", P_DSABL, 0, &pseudo_section, &system_st); - add_sym(".ENABL", P_ENABL, 0, &pseudo_section, &system_st); - add_sym(".END", P_END, 0, &pseudo_section, &system_st); - add_sym(".ENDC", P_ENDC, 0, &pseudo_section, &system_st); - add_sym(".ENDM", P_ENDM, 0, &pseudo_section, &system_st); - add_sym(".ENDR", P_ENDR, 0, &pseudo_section, &system_st); - add_sym(".EOT", P_EOT, 0, &pseudo_section, &system_st); - add_sym(".ERROR", P_ERROR, 0, &pseudo_section, &system_st); - add_sym(".EVEN", P_EVEN, 0, &pseudo_section, &system_st); - add_sym(".FLT2", P_FLT2, 0, &pseudo_section, &system_st); - add_sym(".FLT4", P_FLT4, 0, &pseudo_section, &system_st); - add_sym(".GLOBL", P_GLOBL, 0, &pseudo_section, &system_st); - add_sym(".IDENT", P_IDENT, 0, &pseudo_section, &system_st); - add_sym(".IF", P_IF, 0, &pseudo_section, &system_st); - add_sym(".IFDF", P_IFDF, 0, &pseudo_section, &system_st); - add_sym(".IFNDF", P_IFDF, 0, &pseudo_section, &system_st); - add_sym(".IFF", P_IFF, 0, &pseudo_section, &system_st); - add_sym(".IFT", P_IFT, 0, &pseudo_section, &system_st); - add_sym(".IFTF", P_IFTF, 0, &pseudo_section, &system_st); - add_sym(".IIF", P_IIF, 0, &pseudo_section, &system_st); - add_sym(".IRP", P_IRP, 0, &pseudo_section, &system_st); - add_sym(".IRPC", P_IRPC, 0, &pseudo_section, &system_st); - add_sym(".LIMIT", P_LIMIT, 0, &pseudo_section, &system_st); - add_sym(".LIST", P_LIST, 0, &pseudo_section, &system_st); - add_sym(".MCALL", P_MCALL, 0, &pseudo_section, &system_st); - add_sym(".MEXIT", P_MEXIT, 0, &pseudo_section, &system_st); - add_sym(".NARG", P_NARG, 0, &pseudo_section, &system_st); - add_sym(".NCHR", P_NCHR, 0, &pseudo_section, &system_st); - add_sym(".NLIST", P_NLIST, 0, &pseudo_section, &system_st); - add_sym(".NTYPE", P_NTYPE, 0, &pseudo_section, &system_st); - add_sym(".ODD", P_ODD, 0, &pseudo_section, &system_st); - add_sym(".PACKE", P_PACKED, 0, &pseudo_section, &system_st); - add_sym(".PAGE", P_PAGE, 0, &pseudo_section, &system_st); - add_sym(".PRINT", P_PRINT, 0, &pseudo_section, &system_st); - add_sym(".PSECT", P_PSECT, 0, &pseudo_section, &system_st); - add_sym(".RADIX", P_RADIX, 0, &pseudo_section, &system_st); - add_sym(".RAD50", P_RAD50, 0, &pseudo_section, &system_st); - add_sym(".REM", P_REM, 0, &pseudo_section, &system_st); - add_sym(".REPT", P_REPT, 0, &pseudo_section, &system_st); - add_sym(".RESTO", P_RESTORE, 0, &pseudo_section, &system_st); - add_sym(".SAVE", P_SAVE, 0, &pseudo_section, &system_st); - add_sym(".SBTTL", P_SBTTL, 0, &pseudo_section, &system_st); - add_sym(".TITLE", P_TITLE, 0, &pseudo_section, &system_st); - add_sym(".WORD", P_WORD, 0, &pseudo_section, &system_st); - add_sym(".MACRO", P_MACRO, 0, &pseudo_section, &system_st); - add_sym(".WEAK", P_WEAK, 0, &pseudo_section, &system_st); - - add_sym("ADC", I_ADC, OC_1GEN, &instruction_section, &system_st); - add_sym("ADCB", I_ADCB, OC_1GEN, &instruction_section, &system_st); - add_sym("ADD", I_ADD, OC_2GEN, &instruction_section, &system_st); - add_sym("ASH", I_ASH, OC_ASH, &instruction_section, &system_st); - add_sym("ASHC", I_ASHC, OC_ASH, &instruction_section, &system_st); - add_sym("ASL", I_ASL, OC_1GEN, &instruction_section, &system_st); - add_sym("ASLB", I_ASLB, OC_1GEN, &instruction_section, &system_st); - add_sym("ASR", I_ASR, OC_1GEN, &instruction_section, &system_st); - add_sym("ASRB", I_ASRB, OC_1GEN, &instruction_section, &system_st); - add_sym("BCC", I_BCC, OC_BR, &instruction_section, &system_st); - add_sym("BCS", I_BCS, OC_BR, &instruction_section, &system_st); - add_sym("BEQ", I_BEQ, OC_BR, &instruction_section, &system_st); - add_sym("BGE", I_BGE, OC_BR, &instruction_section, &system_st); - add_sym("BGT", I_BGT, OC_BR, &instruction_section, &system_st); - add_sym("BHI", I_BHI, OC_BR, &instruction_section, &system_st); - add_sym("BHIS", I_BHIS, OC_BR, &instruction_section, &system_st); - add_sym("BIC", I_BIC, OC_2GEN, &instruction_section, &system_st); - add_sym("BICB", I_BICB, OC_2GEN, &instruction_section, &system_st); - add_sym("BIS", I_BIS, OC_2GEN, &instruction_section, &system_st); - add_sym("BISB", I_BISB, OC_2GEN, &instruction_section, &system_st); - add_sym("BIT", I_BIT, OC_2GEN, &instruction_section, &system_st); - add_sym("BITB", I_BITB, OC_2GEN, &instruction_section, &system_st); - add_sym("BLE", I_BLE, OC_BR, &instruction_section, &system_st); - add_sym("BLO", I_BLO, OC_BR, &instruction_section, &system_st); - add_sym("BLOS", I_BLOS, OC_BR, &instruction_section, &system_st); - add_sym("BLT", I_BLT, OC_BR, &instruction_section, &system_st); - add_sym("BMI", I_BMI, OC_BR, &instruction_section, &system_st); - add_sym("BNE", I_BNE, OC_BR, &instruction_section, &system_st); - add_sym("BPL", I_BPL, OC_BR, &instruction_section, &system_st); - add_sym("BPT", I_BPT, OC_NONE, &instruction_section, &system_st); - add_sym("BR", I_BR, OC_BR, &instruction_section, &system_st); - add_sym("BVC", I_BVC, OC_BR, &instruction_section, &system_st); - add_sym("BVS", I_BVS, OC_BR, &instruction_section, &system_st); - add_sym("CALL", I_CALL, OC_1GEN, &instruction_section, &system_st); - add_sym("CALLR", I_CALLR, OC_1GEN, &instruction_section, &system_st); - add_sym("CCC", I_CCC, OC_NONE, &instruction_section, &system_st); - add_sym("CLC", I_CLC, OC_NONE, &instruction_section, &system_st); - add_sym("CLN", I_CLN, OC_NONE, &instruction_section, &system_st); - add_sym("CLR", I_CLR, OC_1GEN, &instruction_section, &system_st); - add_sym("CLRB", I_CLRB, OC_1GEN, &instruction_section, &system_st); - add_sym("CLV", I_CLV, OC_NONE, &instruction_section, &system_st); - add_sym("CLZ", I_CLZ, OC_NONE, &instruction_section, &system_st); - add_sym("CMP", I_CMP, OC_2GEN, &instruction_section, &system_st); - add_sym("CMPB", I_CMPB, OC_2GEN, &instruction_section, &system_st); - add_sym("COM", I_COM, OC_1GEN, &instruction_section, &system_st); - add_sym("COMB", I_COMB, OC_1GEN, &instruction_section, &system_st); - add_sym("DEC", I_DEC, OC_1GEN, &instruction_section, &system_st); - add_sym("DECB", I_DECB, OC_1GEN, &instruction_section, &system_st); - add_sym("DIV", I_DIV, OC_ASH, &instruction_section, &system_st); - add_sym("EMT", I_EMT, OC_MARK, &instruction_section, &system_st); - add_sym("FADD", I_FADD, OC_1REG, &instruction_section, &system_st); - add_sym("FDIV", I_FDIV, OC_1REG, &instruction_section, &system_st); - add_sym("FMUL", I_FMUL, OC_1REG, &instruction_section, &system_st); - add_sym("FSUB", I_FSUB, OC_1REG, &instruction_section, &system_st); - add_sym("HALT", I_HALT, OC_NONE, &instruction_section, &system_st); - add_sym("INC", I_INC, OC_1GEN, &instruction_section, &system_st); - add_sym("INCB", I_INCB, OC_1GEN, &instruction_section, &system_st); - add_sym("IOT", I_IOT, OC_NONE, &instruction_section, &system_st); - add_sym("JMP", I_JMP, OC_1GEN, &instruction_section, &system_st); - add_sym("JSR", I_JSR, OC_JSR, &instruction_section, &system_st); - add_sym("MARK", I_MARK, OC_MARK, &instruction_section, &system_st); - add_sym("MED6X", I_MED6X, OC_NONE, &instruction_section, &system_st); - add_sym("MED74C", I_MED74C, OC_NONE, &instruction_section, &system_st); - add_sym("MFPD", I_MFPD, OC_1GEN, &instruction_section, &system_st); - add_sym("MFPI", I_MFPI, OC_1GEN, &instruction_section, &system_st); - add_sym("MFPS", I_MFPS, OC_1GEN, &instruction_section, &system_st); - add_sym("MOV", I_MOV, OC_2GEN, &instruction_section, &system_st); - add_sym("MOVB", I_MOVB, OC_2GEN, &instruction_section, &system_st); - add_sym("MTPD", I_MTPD, OC_1GEN, &instruction_section, &system_st); - add_sym("MTPI", I_MTPI, OC_1GEN, &instruction_section, &system_st); - add_sym("MTPS", I_MTPS, OC_1GEN, &instruction_section, &system_st); - add_sym("MUL", I_MUL, OC_ASH, &instruction_section, &system_st); - add_sym("NEG", I_NEG, OC_1GEN, &instruction_section, &system_st); - add_sym("NEGB", I_NEGB, OC_1GEN, &instruction_section, &system_st); - add_sym("NOP", I_NOP, OC_NONE, &instruction_section, &system_st); - add_sym("RESET", I_RESET, OC_NONE, &instruction_section, &system_st); - add_sym("RETURN", I_RETURN, OC_NONE, &instruction_section, &system_st); - add_sym("ROL", I_ROL, OC_1GEN, &instruction_section, &system_st); - add_sym("ROLB", I_ROLB, OC_1GEN, &instruction_section, &system_st); - add_sym("ROR", I_ROR, OC_1GEN, &instruction_section, &system_st); - add_sym("RORB", I_RORB, OC_1GEN, &instruction_section, &system_st); - add_sym("RTI", I_RTI, OC_NONE, &instruction_section, &system_st); - add_sym("RTS", I_RTS, OC_1REG, &instruction_section, &system_st); - add_sym("RTT", I_RTT, OC_NONE, &instruction_section, &system_st); - add_sym("SBC", I_SBC, OC_1GEN, &instruction_section, &system_st); - add_sym("SBCB", I_SBCB, OC_1GEN, &instruction_section, &system_st); - add_sym("SCC", I_SCC, OC_NONE, &instruction_section, &system_st); - add_sym("SEC", I_SEC, OC_NONE, &instruction_section, &system_st); - add_sym("SEN", I_SEN, OC_NONE, &instruction_section, &system_st); - add_sym("SEV", I_SEV, OC_NONE, &instruction_section, &system_st); - add_sym("SEZ", I_SEZ, OC_NONE, &instruction_section, &system_st); - add_sym("SOB", I_SOB, OC_SOB, &instruction_section, &system_st); - add_sym("SPL", I_SPL, OC_1REG, &instruction_section, &system_st); - add_sym("SUB", I_SUB, OC_2GEN, &instruction_section, &system_st); - add_sym("SWAB", I_SWAB, OC_1GEN, &instruction_section, &system_st); - add_sym("SXT", I_SXT, OC_1GEN, &instruction_section, &system_st); - add_sym("TRAP", I_TRAP, OC_MARK, &instruction_section, &system_st); - add_sym("TST", I_TST, OC_1GEN, &instruction_section, &system_st); - add_sym("TSTB", I_TSTB, OC_1GEN, &instruction_section, &system_st); - add_sym("WAIT", I_WAIT, OC_NONE, &instruction_section, &system_st); - add_sym("XFC", I_XFC, OC_NONE, &instruction_section, &system_st); - add_sym("XOR", I_XOR, OC_JSR, &instruction_section, &system_st); - add_sym("MFPT", I_MFPT, OC_NONE, &instruction_section, &system_st); - - add_sym("ABSD", I_ABSD, OC_1GEN, &instruction_section, &system_st); - add_sym("ABSF", I_ABSF, OC_1GEN, &instruction_section, &system_st); - add_sym("ADDD", I_ADDD, OC_1FIS, &instruction_section, &system_st); - add_sym("ADDF", I_ADDF, OC_1FIS, &instruction_section, &system_st); - add_sym("CFCC", I_CFCC, OC_NONE, &instruction_section, &system_st); - add_sym("CLRD", I_CLRD, OC_1GEN, &instruction_section, &system_st); - add_sym("CLRF", I_CLRF, OC_1GEN, &instruction_section, &system_st); - add_sym("CMPD", I_CMPD, OC_1FIS, &instruction_section, &system_st); - add_sym("CMPF", I_CMPF, OC_1FIS, &instruction_section, &system_st); - add_sym("DIVD", I_DIVD, OC_1FIS, &instruction_section, &system_st); - add_sym("DIVF", I_DIVF, OC_1FIS, &instruction_section, &system_st); - add_sym("LDCDF", I_LDCDF, OC_1FIS, &instruction_section, &system_st); - add_sym("LDCID", I_LDCID, OC_1FIS, &instruction_section, &system_st); - add_sym("LDCIF", I_LDCIF, OC_1FIS, &instruction_section, &system_st); - add_sym("LDCLD", I_LDCLD, OC_1FIS, &instruction_section, &system_st); - add_sym("LDCLF", I_LDCLF, OC_1FIS, &instruction_section, &system_st); - add_sym("LDD", I_LDD, OC_1FIS, &instruction_section, &system_st); - add_sym("LDEXP", I_LDEXP, OC_1FIS, &instruction_section, &system_st); - add_sym("LDF", I_LDF, OC_1FIS, &instruction_section, &system_st); - add_sym("LDFPS", I_LDFPS, OC_1GEN, &instruction_section, &system_st); - add_sym("MODD", I_MODD, OC_1FIS, &instruction_section, &system_st); - add_sym("MODF", I_MODF, OC_1FIS, &instruction_section, &system_st); - add_sym("MULD", I_MULD, OC_1FIS, &instruction_section, &system_st); - add_sym("MULF", I_MULF, OC_1FIS, &instruction_section, &system_st); - add_sym("NEGD", I_NEGD, OC_1GEN, &instruction_section, &system_st); - add_sym("NEGF", I_NEGF, OC_1GEN, &instruction_section, &system_st); - add_sym("SETD", I_SETD, OC_NONE, &instruction_section, &system_st); - add_sym("SETF", I_SETF, OC_NONE, &instruction_section, &system_st); - add_sym("SETI", I_SETI, OC_NONE, &instruction_section, &system_st); - add_sym("SETL", I_SETL, OC_NONE, &instruction_section, &system_st); - add_sym("STA0", I_STA0, OC_NONE, &instruction_section, &system_st); - add_sym("STB0", I_STB0, OC_NONE, &instruction_section, &system_st); - add_sym("STCDF", I_STCDF, OC_2FIS, &instruction_section, &system_st); - add_sym("STCDI", I_STCDI, OC_2FIS, &instruction_section, &system_st); - add_sym("STCDL", I_STCDL, OC_2FIS, &instruction_section, &system_st); - add_sym("STCFD", I_STCFD, OC_2FIS, &instruction_section, &system_st); - add_sym("STCFI", I_STCFI, OC_2FIS, &instruction_section, &system_st); - add_sym("STCFL", I_STCFL, OC_2FIS, &instruction_section, &system_st); - add_sym("STD", I_STD, OC_2FIS, &instruction_section, &system_st); - add_sym("STEXP", I_STEXP, OC_2FIS, &instruction_section, &system_st); - add_sym("STF", I_STF, OC_2FIS, &instruction_section, &system_st); - add_sym("STFPS", I_STFPS, OC_1GEN, &instruction_section, &system_st); - add_sym("STST", I_STST, OC_1GEN, &instruction_section, &system_st); - add_sym("SUBD", I_SUBD, OC_1FIS, &instruction_section, &system_st); - add_sym("SUBF", I_SUBF, OC_1FIS, &instruction_section, &system_st); - add_sym("TSTD", I_TSTD, OC_1GEN, &instruction_section, &system_st); - add_sym("TSTF", I_TSTF, OC_1GEN, &instruction_section, &system_st); - - /* FIXME: The CIS instructions are missing! */ - - add_sym(current_section->label, 0, 0, current_section, §ion_st); -} - -/* dump_all_macros is a diagnostic function that's currently not - used. I used it while debugging, and I haven't removed it. */ - -static void dump_all_macros(void) -{ - MACRO *mac; - SYMBOL_ITER iter; - - for(mac = (MACRO *)first_sym(¯o_st, &iter); - mac != NULL; mac = (MACRO *)next_sym(¯o_st, &iter)) - { - dumpmacro(mac, lstfile); - - printf("\n\n"); - } -} - -/* sym_hist is a diagnostic function that prints a histogram of the - hash table useage of a symbol table. I used this to try to tune - the hash function for better spread. It's not used now. */ - -static void sym_hist(SYMBOL_TABLE *st, char *name) -{ - int i; - SYMBOL *sym; - fprintf(lstfile, "Histogram for symbol table %s\n", name); - for(i = 0; i < 1023; i++) - { - fprintf(lstfile, "%4d: ", i); - for(sym = st->hash[i]; sym != NULL; sym = sym->next) - fputc('#', lstfile); - fputc('\n', lstfile); - } -} +#include "assemble_globals.h" +#include "assemble.h" +#include "assemble_aux.h" +#include "listing.h" +#include "object.h" +#include "symbols.h" /* enable_tf is called by command argument parsing to enable and disable named options. */ -static void enable_tf(char *opt, int tf) +static void enable_tf( + char *opt, + int tf) { - if(strcmp(opt, "AMA") == 0) - enabl_ama = tf; - else if(strcmp(opt, "GBL") == 0) - enabl_gbl = tf; - else if(strcmp(opt, "ME") == 0) - list_me = tf; - else if(strcmp(opt, "BEX") == 0) - list_bex = tf; - else if(strcmp(opt, "MD") == 0) - list_md = tf; + if (strcmp(opt, "AMA") == 0) + opt_enabl_ama = tf; + else if (strcmp(opt, "GBL") == 0) + enabl_gbl = tf; /* Unused in pass 2 */ + else if (strcmp(opt, "ME") == 0) + list_me = tf; + else if (strcmp(opt, "BEX") == 0) + list_bex = tf; + else if (strcmp(opt, "MD") == 0) + list_md = tf; } -int main(int argc, char *argv[]) +/*JH:*/ +static void print_version( + FILE *strm) { - char *fnames[32]; - int nr_files = 0; - FILE *obj = NULL; - static char line[1024]; - TEXT_RLD tr; - char *macname = NULL; - char *objname = NULL; - char *lstname = NULL; - int arg; - int i; - STACK stack; - int count; - - for(arg = 1; arg < argc; arg++) - { - if(*argv[arg] == '-') - { - char *cp; - cp = argv[arg] + 1; - switch(tolower(*cp)) - { - case 'v': - fprintf(stderr, - "macro11 Copyright 2001 Richard Krehbiel\n" - "Version 0.2 July 15, 2001\n"); - break; - - case 'e': - /* Followed by options to enable */ - /* Since /SHOW and /ENABL option names don't overlap, - I consolidate. */ - upcase(argv[++arg]); - enable_tf(argv[arg], 1); - break; - - case 'd': - /* Followed by an option to disable */ - upcase(argv[++arg]); - enable_tf(argv[arg], 0); - break; - - case 'm': - /* Macro library */ - /* This option gives the name of an RT-11 compatible - macro library from which .MCALLed macros can be - found. */ - arg++; - mlbs[nr_mlbs] = mlb_open(argv[arg]); - if(mlbs[nr_mlbs] == NULL) - { - fprintf(stderr, - "Unable to register macro library %s\n", - argv[arg]); - exit(EXIT_FAILURE); - } - nr_mlbs++; - break; - - case 'p': /* P for search path */ - /* The -p option gives the name of a directory in - which .MCALLed macros may be found. */ - { - char *env = getenv("MCALL"); - char *temp; - - if(env == NULL) - env = ""; - - temp = memcheck(malloc(strlen(env) + - strlen(argv[arg+1]) + 8)); - strcpy(temp, "MCALL="); - strcat(temp, env); - strcat(temp, PATHSEP); - strcat(temp, argv[arg+1]); - putenv(temp); - arg++; - } - break; - - case 'o': - /* The -o option gives the object file name (.OBJ) */ - ++arg; - objname = argv[arg]; - break; - - case 'l': - /* The option -l gives the listing file name (.LST) */ - /* -l - enables listing to stdout. */ - lstname = argv[++arg]; - if(strcmp(lstname, "-") == 0) - lstfile = stdout; - else - lstfile = fopen(lstname, "w"); - break; - - case 'x': - /* The -x option invokes macro11 to expand the - contents of the registered macro libraries (see -m) - into individual .MAC files in the current - directory. No assembly of input is done. This - must be the last command line option. */ - { - int i; - for(i = 0; i < nr_mlbs; i++) - mlb_extract(mlbs[i]); - return EXIT_SUCCESS; - } - - default: - fprintf(stderr, "Unknown argument %s\n", argv[arg]); - exit(EXIT_FAILURE); - } - } - else - { - fnames[nr_files++] = argv[arg]; - } - } - - if(objname) - { - obj = fopen(objname, "wb"); - if(obj == NULL) - return EXIT_FAILURE; - } - - add_symbols(&blank_section); - - text_init(&tr, NULL, 0); - - module_name = memcheck(strdup("")); - - xfer_address = new_ex_lit(1); /* The undefined transfer address */ - - stack_init(&stack); - /* Push the files onto the input stream in reverse order */ - for(i = nr_files-1; i >= 0; --i) - { - STREAM *str = new_file_stream(fnames[i]); - if(str == NULL) - { - report(NULL, "Unable to open file %s\n", fnames[i]); - exit(EXIT_FAILURE); - } - stack_push(&stack, str); - } - - DOT = 0; - current_pc->section = &blank_section; - last_dot_section = NULL; - pass = 0; - stmtno = 0; - lsb = 0; - last_lsb = -1; - last_locsym = 32767; - last_cond = -1; - sect_sp = -1; - suppressed = 0; - - assemble_stack(&stack, &tr); - -#if 0 - if(enabl_debug) - dump_all_macros(); -#endif - - assert(stack.top == NULL); - - migrate_implicit(); /* Migrate the implicit globals */ - write_globals(obj); /* Write the global symbol dictionary */ - -#if 0 - sym_hist(&symbol_st, "symbol_st"); /* Draw a symbol table histogram */ -#endif - - - text_init(&tr, obj, 0); - - stack_init(&stack); /* Superfluous... */ - /* Re-push the files onto the input stream in reverse order */ - for(i = nr_files-1; i >= 0; --i) - { - STREAM *str = new_file_stream(fnames[i]); - if(str == NULL) - { - report(NULL, "Unable to open file %s\n", fnames[i]); - exit(EXIT_FAILURE); - } - stack_push(&stack, str); - } - - DOT = 0; - current_pc->section = &blank_section; - last_dot_section = NULL; - - pass = 1; - stmtno = 0; - lsb = 0; - last_lsb = -1; - last_locsym = 32767; - pop_cond(-1); - sect_sp = -1; - suppressed = 0; - - count = assemble_stack(&stack, &tr); - - text_flush(&tr); - - while(last_cond >= 0) - { - report(NULL, "%s:%d: Unterminated conditional\n", - conds[last_cond].file, conds[last_cond].line); - pop_cond(last_cond - 1); - count++; - } - - for(i = 0; i < nr_mlbs; i++) - mlb_close(mlbs[i]); - - write_endmod(obj); - - if(obj != NULL) - fclose(obj); - - if(count > 0) - fprintf(stderr, "%d Errors\n", count); - - if(lstfile && strcmp(lstname, "-") != 0) - fclose(lstfile); - - return count > 0 ? EXIT_FAILURE : EXIT_SUCCESS; + fprintf(strm, "macro11 - portable MACRO11 assembler for DEC PDP-11\n"); + fprintf(strm, " Version %s\n", VERSIONSTR); + fprintf(strm, " Copyright 2001 Richard Krehbiel,\n"); + fprintf(strm, " modified 2009 by Joerg Hoppe,\n"); + fprintf(strm, " modified 2015-2017,2020-2021 by Olaf 'Rhialto' Seibert.\n"); +} + +static void append_env( + char *envname, + char *value) +{ + char *env = getenv(envname); + char *temp; + + if (env == NULL) + env = ""; + + temp = memcheck(malloc(strlen(envname) + + 1 + + strlen(env) + + 1 + + strlen(value) + + 1)); + strcpy(temp, envname); + strcat(temp, "="); + strcat(temp, env); + strcat(temp, PATHSEP); + strcat(temp, value); + putenv(temp); +} + +/*JH:*/ +static void print_help( + void) +{ + printf("\n"); + print_version(stdout); + printf("\n"); + printf("Usage:\n"); + printf(" macro11 [-o ] [-l []] \n"); + printf(" [-h] [-v][-e