diff --git a/converters/fsio/Makefile b/converters/fsio/Makefile new file mode 100644 index 0000000..8e3671a --- /dev/null +++ b/converters/fsio/Makefile @@ -0,0 +1,51 @@ +#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 +INCLUDES=fsio.h declib.h tape.h dos11.h rt11.h dosmt.h +LIBS=-lreadline +MANPAGE=fsio.1 +MANPAGE_DOS=fsio-dos11.1 +MANPAGE_RT=fsio-rt11.1 +MANPAGE_DOSMT=fsio-dosmt.1 +ARCHIVE=fsio.tgz + +RELEASEFILES=$(BIN)/$(EXECUTABLE) +RELEASEFILES+=$(MAN)/$(MANPAGE) +RELEASEFILES+=$(MAN)/$(MANPAGE_DOS) +RELEASEFILES+=$(MAN)/$(MANPAGE_RT) +RELEASEFILES+=$(MAN)/$(MANPAGE_DOSMT) +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) + +uninstall: + rm -f $(BIN)/$(EXECUTABLE) + rm -f $(MAN)/$(MANPAGE) + rm -f $(MAN)/$(MANPAGE_DOS) + rm -f $(MAN)/$(MANPAGE_RT) + rm -f $(MAN)/$(MANPAGE_DOSMT) + +# 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..c68f390 --- /dev/null +++ b/converters/fsio/dos11.c @@ -0,0 +1,2523 @@ +/* + * 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" + +/* + * Tables for the bitmap allocator + */ +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 bye 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, +}; + +/* + * 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 +) +{ + void *buffer = buf == NULL ? mount->dos11data.buf : buf; + int status; + + if (block >= mount->dos11data.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 receive 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 +) +{ + void *buffer = buf == NULL ? mount->dos11data.buf : buf; + int status; + + if (block >= mount->dos11data.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: + * + * Size of the container file in blocks of default file system size + * + --*/ +static size_t dos11Size(void) +{ + return DISKSIZE_RK05; +} + +/*++ + * 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; + 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 dos11OpenFile *file; + struct dos11FileSpec spec; + uint8_t user = mount->dos11data.user; + uint8_t group = mount->dos11data.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 dos11OpenFile *file; + struct dos11FileSpec spec; + unsigned long contig = (size + mount->blocksz - 1) / mount->blocksz; + uint8_t user = mount->dos11data.user; + uint8_t group = mount->dos11data.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..dcc3e7a --- /dev/null +++ b/converters/fsio/fsio-dos11.1 @@ -0,0 +1,50 @@ +.TH FSIO-DOS11 1 "December 28,2018" "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) +.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..cca7c12 --- /dev/null +++ b/converters/fsio/fsio-dosmt.1 @@ -0,0 +1,110 @@ +.TH FSIO-DOSMT 1 "January 28,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) +.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..3964ad1 --- /dev/null +++ b/converters/fsio/fsio-rt11.1 @@ -0,0 +1,42 @@ +.TH FSIO-RT11 1 "December 28,2018" "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 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 +.TP +\fIrl02\fP \- RL02 image (10MB, 20480 blocks) +.br +.TP +\fIrx20\fP \- RX20 image (512KB, 1024 blocks) +.br +.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) +.SH AUTHOR +John Forecast, +.br diff --git a/converters/fsio/fsio.1 b/converters/fsio/fsio.1 new file mode 100644 index 0000000..f9b5fd3 --- /dev/null +++ b/converters/fsio/fsio.1 @@ -0,0 +1,334 @@ +.TH FSIO 1 "December 25,2018" "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 [-drx] 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\-r\fP \- mount file system read-only" +.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] 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 "\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 +.SH SEE ALSO +.BR fsio-dos11 (1), +.BR fsio-rt11 (1) +.BR fsio-dosmt (1) +.SH AUTHOR +John Forecast, +.br diff --git a/converters/fsio/fsio.c b/converters/fsio/fsio.c new file mode 100644 index 0000000..d04d61b --- /dev/null +++ b/converters/fsio/fsio.c @@ -0,0 +1,1983 @@ +/* + * 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 (12/26/2018) + * + * 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 otherwise. + * + * 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 "blocksz" size blocks used for the container file. + * The command line switch (-t type) may be used to determine the + * required 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 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("drx"), 3, 3, 0, doMount }, +#else + { "mount", OPTIONS("rx"), 3, 3, 0, doMount }, +#endif + { "umount", NULL, 1, 1, 0, doUmount }, + { "newfs", OPTIONS("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; + +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; + +#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; +} + +/*++ + * 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; + 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) { + 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; + + /* + * Verify that the container holds a valid file system + */ + if ((*filesys->mount)(mount) != 0) { + mount->next = mounts; + mounts = mount; + return; + } + free(mount); + 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 = filesys->blocksz * (size - 1); + + /* + * If an empty file is valid, we are all done + */ + if ((filesys->flags & FS_EMPTYFILE) != 0) + return; + + if ((fseeko(mount.container, offset, SEEK_SET) == 0) && + (fwrite(&ch, 1, filesys->blocksz, mount.container) == filesys->blocksz)) { + 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, "dump: \"%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] 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\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 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, 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, SEEK_SET) == 0) + return fwrite(buf, mount->blocksz, 1, mount->container); + + return 0; +} diff --git a/converters/fsio/fsio.h b/converters/fsio/fsio.h new file mode 100644 index 0000000..8da3f1f --- /dev/null +++ b/converters/fsio/fsio.h @@ -0,0 +1,166 @@ +/* + * 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" + +/* + * 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(__FreeBSD__) || defined(__NetBSD__) +#include + +#define le16toh(x) letoh16(x) + +#define le32toh(x) letoh32(x) +#elif 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 */ + union { + struct DOS11data _dos11; + struct RT11data _rt11; + struct DOSMTdata _dosmt; + } FSdata; +#define dos11data FSdata._dos11 +#define rt11data FSdata._rt11 +#define dosmtdata FSdata._dosmt +}; + +extern int FSioReadBlock(struct mountedFS *, unsigned int, void *); +extern int FSioWriteBlock(struct mountedFS *, unsigned int, void *); + +#endif diff --git a/converters/fsio/fsio.txt b/converters/fsio/fsio.txt new file mode 100644 index 0000000..7a2ad8b --- /dev/null +++ b/converters/fsio/fsio.txt @@ -0,0 +1,319 @@ +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 [-dr] 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 + -r If present, the file system is only available for read access + + 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 + + -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) + + 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..e7648f8 --- /dev/null +++ b/converters/fsio/fsioSimh.txt @@ -0,0 +1,291 @@ +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). + +WARNING: fsio is alpha-level code. Make sure you make a back-up copy of any +important file system before mounting it read/write. + + +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 +------- + +Use the RTFLX utility to copy the file over to the TOPS-10 file system. When +creating the SIMH file using the "newfs" command use the "-t rx20" switch to +create a floppy image. The RX20 is a double density floppy and is only +available on KS10 systems. + +There is a help file on Tops-10 for RTFLX but no other documentation. The +default TOPS-10 monitor does not include support for the RX20 so a new +system generation will be required. + +I do not currently have a functioning TOPS-10 system to test this on. 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/rt11.c b/converters/fsio/rt11.c new file mode 100644 index 0000000..c2b8c8e --- /dev/null +++ b/converters/fsio/rt11.c @@ -0,0 +1,2306 @@ +/* + * 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[] = { + { "rl02", RT11_RL02SZ }, + { "rx20", RT11_RX20SZ }, + { 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; +} + +/*++ + * 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; + void *buffer = buf == NULL ? data->buf : buf; + int status = 0; + + if (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 + + 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; + void *buffer = buf == NULL ? data->buf : buf; + int status = 0; + + if (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 + + 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 disk 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 = RT11_DSSTART + ((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 = RT11_DSSTART + ((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 anc 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 (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); +} + +/*++ + * v a l i d a t e + * + * Verify that a partition contains a valid RT-11 filesystem. First verify + * that the checksum is correct and then that the ID fields are correct. + * + * Inputs: + * + * mount - pointer to a mounted file system descriptor + * unit - partition number + * + * 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 +) +{ + struct RT11data *data = &mount->rt11data; + uint16_t i, checksum = 0; + + if (rt11ReadBlock(mount, unit, RT11_HOME, NULL) == 0) + return 0; + + /* + * Compute the checksum over the Home block. + */ + for (i = 0; i < 255; i++) + checksum += le16toh(data->buf[i]); + + if (checksum == le16toh(data->buf[255])) { + if ((le16toh(data->buf[RT11_HB_PCS]) == 1) && + (le16toh(data->buf[RT11_HB_FIRST]) == RT11_DSSTART) && + ((strncmp((char *)&data->buf[RT11_HB_SYSID], + RT11_SYSID, strlen(RT11_SYSID))) == 0)) + return 1; + } + + return 0; +} + +/*++ + * 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; + + memset(&data->valid, 0, sizeof(data->valid)); + + if (fstat(fileno(mount->container), &stat) == 0) { + uint16_t i, count, lastsz, validcount; + + data->blocks = stat.st_size / RT11_BLOCKSIZE; + + count = data->blocks / RT11_MAXPARTSZ; + lastsz = data->blocks % RT11_MAXPARTSZ; + + validcount = count; + + /* + * Check each file system for validity. + */ + for (i = 0; i < count; i++) { + /* + * Assume valid + */ + data->valid[i / 16] |= 1 << (i % 16); + data->maxblk[i] = RT11_MAXPARTSZ - 1; + + if (validate(mount, i) == 0) { + data->valid[i / 16] &= ~(1 << (i % 16)); + validcount--; + } + } + + /* + * Check for a small partition to complete the disk + */ + if (lastsz >= RT11_MINPARTSZ) { + data->valid[count / 16] |= 1 << (count % 16); + data->maxblk[count] = lastsz; + + if (validate(mount, count) == 0) + data->valid[count / 16] &= ~(1 << (count % 16)); + else validcount++; + } + + 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 (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) + printf("%s%o:\n" + " Total blocks: %5d, Free blocks: %5d\n" + " Directory segments: %2d (Highest %d)\n" + " Extra bytes/directory entry: %d\n", + mount->name, i, data->maxblk[i] + 1, freeblks, + le16toh(data->buf[RT11_DH_COUNT]), + highest, + le16toh(data->buf[RT11_DH_EXTRA])); + 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 and RT-11 container file. + * + * Inputs: + * + * None + * + * Outputs: + * + * None + * + * Returns: + * + * Size of the container file in blocks of default file system size + * + --*/ +static size_t rt11Size(void) +{ + size_t size = RT11_MAXPARTSZ - 1; + + 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)) + 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 blocks) 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; + + /* + * Mark partition 0 as valid + */ + memset(data->valid, 0, sizeof(data->valid)); + data->valid[0] = 1; + data->maxblk[0] = size; + data->filesystems = 1; + + /* + * Build and write the Home Block. + */ + memset(data->buf, 0, RT11_BLOCKSIZE); + + data->buf[RT11_HB_PCS] = htole16(1); + data->buf[RT11_HB_FIRST] = htole16(RT11_DSSTART); + data->buf[RT11_HB_SYSVER] = htole16(ascR50(RT11_SYSVER)); + strncpy((char *)&data->buf[RT11_HB_VOLID], RT11_VOLID, strlen(RT11_VOLID)); + strncpy((char *)&data->buf[RT11_HB_OWNER], RT11_OWNER, strlen(RT11_OWNER)); + strncpy((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_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 (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 (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 (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 + * + --*/ +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..c8c53a8 --- /dev/null +++ b/converters/fsio/rt11.h @@ -0,0 +1,290 @@ +/* + * 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_SYSVER "V05" +#define RT11_VOLID "RT11A " +#define RT11_OWNER " " +#define RT11_SYSID "DECRT11A " + +/* + * Partition sizes. The last block os a maximum sized partition 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 0000011 /* Min partition size */ + +#define RT11_RL02SZ 20480 /* Size of an RL02 drive */ +#define RT11_RX20SZ 1024 /* Size of an RX20 floppy drive */ + +#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 */ + uint16_t filesystems; /* Max # of filesystems */ + uint16_t valid[16]; /* Valid partitions */ + uint16_t maxblk[256]; /* Max block address */ + uint16_t buf[512]; /* Disk buffer - enough for a */ + /* directory segment */ +}; +#define 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