From 210a5159e7c38632f7a16b7ba44b0bce08ef55a4 Mon Sep 17 00:00:00 2001 From: John Forecast Date: Tue, 14 Jan 2025 19:14:37 -0500 Subject: [PATCH] Various os/8 and rt11 fixes. Add support for Unix V7. --- converters/fsio/Changes | 33 + converters/fsio/Makefile | 17 +- converters/fsio/dos11.c | 3 +- converters/fsio/dosmt.c | 36 +- converters/fsio/fsio-dos11.1 | 6 +- converters/fsio/fsio-dosmt.1 | 6 +- converters/fsio/fsio-os8.1 | 9 +- converters/fsio/fsio-rt11.1 | 3 +- converters/fsio/fsio-unixv7.1 | 74 + converters/fsio/fsio.1 | 167 +- converters/fsio/fsio.c | 118 +- converters/fsio/fsio.h | 10 +- converters/fsio/fsio.txt | 70 +- converters/fsio/fsioSimh.txt | 30 + converters/fsio/local.c | 3 +- converters/fsio/os8.c | 52 +- converters/fsio/os8.h | 3 +- converters/fsio/rt11.c | 25 +- converters/fsio/tape.c | 4 +- converters/fsio/unixv7.c | 3080 +++++++++++++++++++++++++++++++++ converters/fsio/unixv7.h | 258 +++ 21 files changed, 3847 insertions(+), 160 deletions(-) create mode 100644 converters/fsio/fsio-unixv7.1 create mode 100644 converters/fsio/unixv7.c create mode 100644 converters/fsio/unixv7.h diff --git a/converters/fsio/Changes b/converters/fsio/Changes index 338a1f9..c353d15 100644 --- a/converters/fsio/Changes +++ b/converters/fsio/Changes @@ -33,3 +33,36 @@ Changes Since Alpha Release: - Merged change from tvrusso to fix compilation on FreeBSD +20-Aug-20 + +- Fixed bug in OS/8 file deletion which incorrectly reduced the directory + file count by 1 + +- Fix ASCII mode read in OS/8 where it would read an entire block into + memory rather than a single line. When copying to local:, this would + result in the destination file having unexpected characters. + +15-Feb-21 + +- Added support Research Unix V7 file systems of RK05, RL01 and RL02 drives + using file system type "unixv7" + +3-Mar-23 + +- Added support for RK06 and RK07 Unix V7 drives +- Added support for creating arbitrary sized Unix V7 file systems + +26-Sep-24 + +- Fixed display of System ID during RT-11 mount if there is non-zero data + following the System ID on the disk + +13-Nov-24 + +- Fixed OS/8 ASCII file read/write to to handle ^Z marking the end of file + +28-Nov-24 + +- Fixed RT11 scan to find the size of the partition. The old code would + incorrectly terminate on an EMPTY marker rather than END-OF-SEGMENT. + diff --git a/converters/fsio/Makefile b/converters/fsio/Makefile index 9fa9330..eb07835 100644 --- a/converters/fsio/Makefile +++ b/converters/fsio/Makefile @@ -7,14 +7,15 @@ INSTALL=install CC=gcc EXECUTABLE=fsio -SOURCES=fsio.c declib.c tape.c dos11.c rt11.c dosmt.c local.c os8.c -INCLUDES=fsio.h declib.h tape.h dos11.h rt11.h dosmt.h os8.h +SOURCES=fsio.c declib.c tape.c dos11.c rt11.c dosmt.c local.c os8.c unixv7.c +INCLUDES=fsio.h declib.h tape.h dos11.h rt11.h dosmt.h os8.h unixv7.h LIBS=-lreadline MANPAGE=fsio.1 MANPAGE_DOS=fsio-dos11.1 MANPAGE_RT=fsio-rt11.1 MANPAGE_DOSMT=fsio-dosmt.1 MANPAGE_OS8=fsio-os8.1 +MANPAGE_UNIXV7=fsio-unixv7.1 ARCHIVE=fsio.tgz RELEASEFILES=$(BIN)/$(EXECUTABLE) @@ -23,8 +24,16 @@ RELEASEFILES+=$(MAN)/$(MANPAGE_DOS) RELEASEFILES+=$(MAN)/$(MANPAGE_RT) RELEASEFILES+=$(MAN)/$(MANPAGE_DOSMT) RELEASEFILES+=$(MAN)/$(MANPAGE_OS8) +RELEASEFILES+=$(MAN)/$(MANPAGE_UNIXV7) RELEASEFILES+=./fsio.txt ./fsioSimh.txt +MANPAGES=$(MANPAGE) +MANPAGES+=$(MANPAGE_DOS) +MANPAGES+=$(MANPAGE_RT) +MANPAGES+=$(MANPAGE_DOSMT) +MANPAGES+=$(MANPAGE_OS8) +MANPAGES+=$(MANPAGE_UNIXV7) + $(EXECUTABLE): $(SOURCES) $(INCLUDES) Makefile $(CC) $(CFLAGS) $(DEFINES) -o $(EXECUTABLE) $(SOURCES) $(LIBS) @@ -33,7 +42,7 @@ $(EXECUTABLE): $(SOURCES) $(INCLUDES) Makefile clean: rm -f $(EXECUTABLE) -install: $(EXECUTABLE) $(MANPAGE) $(MANPAGE_DOS) $(MANPAGE_RT) +install: $(EXECUTABLE) $(MANPAGES) $(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) @@ -41,6 +50,7 @@ install: $(EXECUTABLE) $(MANPAGE) $(MANPAGE_DOS) $(MANPAGE_RT) $(INSTALL) -p -m u=r,g=r,o=r $(MANPAGE_RT) $(MAN) $(INSTALL) -p -m u=r,g=r,o=r $(MANPAGE_DOSMT) $(MAN) $(INSTALL) -p -m u=r,g=r,o=r $(MANPAGE_OS8) $(MAN) + $(INSTALL) -p -m u=r,g=r,o=r $(MANPAGE_UNIXV7) $(MAN) uninstall: rm -f $(BIN)/$(EXECUTABLE) @@ -49,6 +59,7 @@ uninstall: rm -f $(MAN)/$(MANPAGE_RT) rm -f $(MAN)/$(MANPAGE_DOSMT) rm -f $(MAN)/$(MANPAGE_OS8) + rm -f $(MAN)/$(MANPAGE_UNIXV7) # This assumes that fsio has been "installed" on the current system archive: $(RELEASEFILES) diff --git a/converters/fsio/dos11.c b/converters/fsio/dos11.c index 96e44bb..8e96c1a 100644 --- a/converters/fsio/dos11.c +++ b/converters/fsio/dos11.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2018 John Forecast. All Rights Reserved. + * Copyright (C) 2018 - 2025 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: @@ -2473,6 +2473,7 @@ struct FSdef dos11FS = { dos11Umount, dos11Size, dos11Newfs, + NULL, dos11Set, dos11Info, dos11Dir, diff --git a/converters/fsio/dosmt.c b/converters/fsio/dosmt.c index 03ac002..4758df4 100644 --- a/converters/fsio/dosmt.c +++ b/converters/fsio/dosmt.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2018 John Forecast. All Rights Reserved. + * Copyright (C) 2018 - 2025 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: @@ -385,9 +385,21 @@ static int dosmtReadBytes( break; } } - *buf++ = file->buf[file->nextb++]; - len--; - count++; + if (SWISSET('a')) { + char ch = file->buf[file->nextb++] & 0177; + + if ((ch != 0) && (ch != 0177)) { + *buf++ = ch; + count++; + } + len--; + if (ch == '\n') + break; + } else { + *buf++ = file->buf[file->nextb++]; + len--; + count++; + } } return count; } @@ -982,6 +994,7 @@ static void *dosmtOpenFileW( file->nextb = 0; file->tm = 0; file->error = 0; + memset(file->buf, 0, DOSMTRCLNT); } return file; } @@ -1015,8 +1028,8 @@ static off_t dosmtFileSize( 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 + * Compute the length of this file. The estimate may be larger than the + * actual size of the file since we don't know the actual EOF position * within the last block of the file. */ do { @@ -1097,13 +1110,21 @@ static void dosmtCloseFile( data->eot = tapeGetPosition(mount->container); - if (tapeWriteEOM(mount->container, 1) == 0) + /* + * Write 2 tape marks indicating the end of tape (DOS/BATCH requires + * 3 TM in a row, 1 for the EOF and the last 2 to indicate EOT) and then + * backup over the last 2 TMs. + */ + if ((tapeWriteTM(mount->container) == 0) || + (tapeWriteTM(mount->container) == 0)) file->error = 1; if (file->error != 0) { ERROR("Panic: Error writing on \"%s\"\n", mount->name); exit(3); } + + tapeSetPosition(mount->container, data->eot); } free(file); @@ -1304,6 +1325,7 @@ struct FSdef dosmtFS = { dosmtUmount, NULL, NULL, + NULL, dosmtSet, dosmtInfo, dosmtDir, diff --git a/converters/fsio/fsio-dos11.1 b/converters/fsio/fsio-dos11.1 index b4665d1..fc4e9d9 100644 --- a/converters/fsio/fsio-dos11.1 +++ b/converters/fsio/fsio-dos11.1 @@ -1,4 +1,4 @@ -.TH FSIO-DOS11 1 "Jun 21,2019" "FFS I/O - DOS-11" +.TH FSIO-DOS11 1 "Feb 14,2021" "FFS I/O - DOS-11" .SH NAME fsio-dos11 \- Foreign File System I/O - DOS-11 .br @@ -14,6 +14,9 @@ group number and user number. Wildcard characters are only valid with the .SH NEWFS OPERATION \fInewfs\fP creates a blank RK05 image (2.5MB, 4800 blocks) with no UFD entries. +.SH COPY OPERATION +In ASCII mode ('-a'), characters will be converted to 7-bits and NULL and +delete characters will not be copied. .SH SET OPERATION The following \fIset\fP commands are supported: .br @@ -46,6 +49,7 @@ Creates an empty UFD and sets default UIC for file access. .BR fsio-rt11 (1) .BR fsio-dosmt (1) .BR fsio-os8 (1) +.BR fsio-unixv7 (1) .SH AUTHOR John Forecast, .br diff --git a/converters/fsio/fsio-dosmt.1 b/converters/fsio/fsio-dosmt.1 index cf263e3..1ad1f16 100644 --- a/converters/fsio/fsio-dosmt.1 +++ b/converters/fsio/fsio-dosmt.1 @@ -1,4 +1,4 @@ -.TH FSIO-DOSMT 1 "Jun 25,2019" "FFS I/O - DOS-11 magtape" +.TH FSIO-DOSMT 1 "Feb 14,2021" "FFS I/O - DOS-11 magtape" .SH NAME fsio-dosmt \- Foreign File System I/O - DOS-11 magtape .br @@ -31,6 +31,9 @@ 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 COPY OPERATION +In ASCII mode ('-a'), characters will be converted to 7-bits and NULL and +delete characters will not be copied. .SH SET OPERATION The following \fIset\fP commands are supported: .br @@ -106,6 +109,7 @@ determine the current tape position). .BR fsio-dos11 (1) .BR fsio-rt11 (1) .BR fsio-os8 (1) +.BR fsio-unixv7 (1) .SH AUTHOR John Forecast, .br diff --git a/converters/fsio/fsio-os8.1 b/converters/fsio/fsio-os8.1 index 724886b..74b93e0 100644 --- a/converters/fsio/fsio-os8.1 +++ b/converters/fsio/fsio-os8.1 @@ -1,4 +1,4 @@ -.TH FSIO-OS8 1 "Sep 218,2019" "FFS I/O - OS/8" +.TH FSIO-OS8 1 "Feb 14,2021" "FFS I/O - OS/8" .SH NAME fsio-os8 \- Foreign File System I/O - OS/8 .br @@ -15,16 +15,16 @@ place multiple file systems on each physical device. .br OS/8 does not write any type of signature on the device and each device type has it's own partitioning scheme so the \fImount\fP command must use the -"\fI-f type\fP switch so that \fBfsio\fP can determine the file system +"\fI-t type\fP switch so that \fBfsio\fP can determine the file system layout. \fBfsio\fP uses a set of heuristics to verify the integrity of the OS/8 file system(s) but it is quite possible for a random disk to pass these tests and later crash \fBfsio\fP. .SH MOUNT OPERATION -\fImount\fP requires the "\fI-f type\fP" switch so that it can determine the +\fImount\fP requires the "\fI-t type\fP" switch so that it can determine the type of the underlying disk (See NEWFS OPERATION below for details). .SH NEWFS OPERATION \fInewfs\fP creates an RK05 disk image with 2 file systems. If the -"\fI-f type\fP" switch is present a different container file will be created +"\fI-t type\fP" switch is present a different container file will be created depending on the type of the device specified: .br .RS @@ -75,6 +75,7 @@ the default state after mount. .BR fsio-dos11 (1) .BR fsio-dosmt (1) .BR fsio-rt11 (1) +.BR fsio-unixv7 (1) .SH AUTHOR John Forecast, .br diff --git a/converters/fsio/fsio-rt11.1 b/converters/fsio/fsio-rt11.1 index 2dce4a0..a5be715 100644 --- a/converters/fsio/fsio-rt11.1 +++ b/converters/fsio/fsio-rt11.1 @@ -1,4 +1,4 @@ -.TH FSIO-RT11 1 "Jun 25,2019" "FFS I/O - RT-11" +.TH FSIO-RT11 1 "Feb 14,2021" "FFS I/O - RT-11" .SH NAME fsio-rt11 \- Foreign File System I/O - RT-11 .br @@ -84,6 +84,7 @@ No \fIset\fP commands are currently supported. .BR fsio-dos11 (1) .BR fsio-dosmt (1) .BR fsio-os8 (1) +.BR fsio-unixv7 (1) .SH AUTHOR John Forecast, .br diff --git a/converters/fsio/fsio-unixv7.1 b/converters/fsio/fsio-unixv7.1 new file mode 100644 index 0000000..e4d47a3 --- /dev/null +++ b/converters/fsio/fsio-unixv7.1 @@ -0,0 +1,74 @@ +.TH FSIO-UNIXV7 1 "Mar 2,2023" "FFS I/O - UNIX V7" +.SH NAME +fsio-unixv7 \- Foreign File System I/O - Unix V7 +.br +.SH DESCRIPTION +\fBfsio\fP allows access to Unix V7 file systems using the file system type +"\fIunixv7\fP". Note that you must use "\fI/etc/rawfs\fP" on Ultrix-11 3.0/3.1 +to access files on disks created by \fBfsio\fP. +.br +.SH UNIX V7 PHYSICAL DISKS +UNIX V7 uses a logical block size of 512 bytes. \fBfsio\fP supports access to +disks which have their super block stored at block 2. Unix V7 does not write +any type of signature on the device so the \fImount\fP command uses a set of +heuristics to determine if the file system is valid. +.SH MOUNT OPERATION +\fImount\fP accepts the optional "\fI-t type\fP" switch to provide an additonal +check on the integrity of the file system (e.g. check for blocks addresses +larger than the specified disk) (See NEWFS OPERATION below for details). +.br + +\fImount\fP also accepts the optional "\fI-o nnn\fP" switch where nnn is the +block offset of the start of the partition to be mounted. Since most, if not +all, UNIX V7 implementations hard code the partition layout for a specific +disk within the device driver, you will have to examine the device driver +code to calculate this offset. +.SH NEWFS OPERATION +\fInewfs\fP requires either the "\fI-b blocks\fP" or the "\fI-t type\fP" switch +to determine the size of the container file to be created: +.br +.RS +.TP +\fIrk05\fP \- RK05 image (2436 blocks, 776 inodes) +.br +.TP +\fIrl01\fP \- RL01 image (10240 blocks, 3272 inodes) +.br +.TP +\fIrl02\fP \- RL02 image (20480 blocks, 6552 inodes) +.br +.TP +\fIrk06\fP \- RK06 image (27126 blocks, 8680 inodes) +.br +.TP +\fIrk07\fP \- RK07 image (53790 blocks, 17208 inodes) +.br +.RE +.br + +If neither switch is present, an RK05-sized disk will be created. +.br + +By default, inodes occupy 4% of the disk space. This can be overridden by +using the "\fI-i num\fP" switch, where \fInum\fP can be in the range 8 - 65500 +and the resulting inode space cannot occupy more than 50% of the disk space +(8 inodes consume 1 disk block). +.br +.SH SET OPERATION +The following \fIset\fP commands are supported: +.br +.TP +.B "\fIuid\fP n" +Sets the default UID for created files and directories (was 0 after mount). +.TP +.B "\fIgid\fP n" +Sets the default GID for created files and directories (was 0 after mount). +.SH SEE ALSO +.BR fsio (1) +.BR fsio-dos11 (1) +.BR fsio-dosmt (1) +.BR fsio-os8 (1) +.BR fsio-rt11 (1) +.SH AUTHOR +John Forecast, +.br diff --git a/converters/fsio/fsio.1 b/converters/fsio/fsio.1 index fb664d1..4f80443 100644 --- a/converters/fsio/fsio.1 +++ b/converters/fsio/fsio.1 @@ -1,4 +1,4 @@ -.TH FSIO 1 "Sep 17,2019" "Foreign File System I/O" +.TH FSIO 1 "Mar 2,2023" "Foreign File System I/O" .SH NAME fsio \- Foreign File System I/O .SH SYNOPSIS @@ -34,54 +34,56 @@ verb [switches] args ... The following verbs are supported: .br -.B "\fImount\fP \- make a container file available to fsio" +.B "\fImount\fP \- Make a container file available to fsio" .br -.B "\fIumount\fP \- remove knowledge of a container file from fsio" +.B "\fIumount\fP \- Remove knowledge of a container file from fsio" .br -.B "\fInewfs\fP \- create and new container and empty file system" +.B "\fInewfs\fP \- Create a new container and empty file system" .br -.B "\fIset\fP \- set parameters on a mounted file system" +.B "\fImkdir\fP \- Create a new empty directory on a mounted file system" .br -.B "\fIinfo\fP \- display information about the container file system" +.B "\fIset\fP \- Set parameters on a mounted file system" .br -.B "\fIdir\fP \- list a directory" +.B "\fIinfo\fP \- Display information about the container file system" .br -.B "\fIdump\fP \- dump a file in hex or octal" +.B "\fIdir\fP \- List a directory" .br -.B "\fIcopy\fP \- copy a single file" +.B "\fIdump\fP \- Dump a file in hex or octal" .br -.B "\fItype\fP \- type a file on the terminal" +.B "\fIcopy\fP \- Copy a single file" .br -.B "\fIdelete\fP \- delete a file" +.B "\fItype\fP \- Type a file on the terminal" .br -.B "\fIstatus\fP \- display currently mounted file systems" +.B "\fIdelete\fP \- Delete a file" .br -.B "\fIdo\fP \- echo and execute commands from a file" +.B "\fIstatus\fP \- Display currently mounted file systems" .br -.B "\fIhelp\fP \- display help on using fsio" +.B "\fIdo\fP \- Echo and execute commands from a file" .br -.B "\fIexit\fP \- terminate fsio (quit is an alias for exit)" +.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" +.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" +.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" +.B "\fIskipf\fP \- Position the tape by skipping forward over files" .br -.B "\fIskipr\fP \- position the tape by skipping backward over files" +.B "\fIskipr\fP \- Position the tape by skipping backward over files" .br .SH COMMANDS .TP -.B "\fImount\fP [-dfrx] [-t type] dev[:] file type" -Make the container file available to fsio. +.B "\fImount\fP [-dfrx] [-t type] [-o nnn] dev[:] file type" +Make the container file available to \fBfsio\fP. .br .RS .RS -.B "\fI\-d\fP \- generate debug output on stdout" +.B "\fI\-d\fP \- Generate debug output on stdout" .br .B " Use environment variable \fIFSioDebugLog\fP to" .br @@ -89,44 +91,62 @@ Make the container file available to fsio. .br .B " Only available if built with DEBUG enabled" .br -.B "\fI\-f\fP \- bypass home block validation (RT-11 only)" +.B "\fI\-f\fP \- Bypass home block validation (RT-11 only)" +.br +.B "\fI\-o nnn\fP \- Specify partition block offset (unixv7 only)" .br -.B "\fI\-r\fP \- mount file system read-only" +.B "\fI\-r\fP \- Mount file system read-only" .br -.B "\fI\-t type\fP \- specify optional disk type" +.B "\fI\-t type\fP \- Specify optional disk type" .br .B "\fI\-x\fP \- dosmt will use extended filenames when writing" .br -.B "\fIdev[:]\fP \- user supplied name for the mount" +.B "\fIdev[:]\fP \- User supplied name for the mount" .br -.B "\fIfile\fP \- name of the container file" +.B "\fIfile\fP \- Name of the container file" .br -.B "\fItype\fP \- type of container file system" +.B "\fItype\fP \- Type of container file system" .br .RE .RE .TP .B "\fIumount\fP dev[:]" -Remove knowledge of the container file from fsio. +Remove knowledge of the container file from \fBfsio\fP. .br .RS .RS -.B "\fIdev[:]\fP \- name supplied on a previous mount" +.B "\fIdev[:]\fP \- Name supplied on a previous mount" .RE .RE .TP -.B "\fInewfs\fP [-t type] [-e count] file type" +.B "\fInewfs\fP [-b blks] [-t type] [-e n] [-i n] 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" +.B "\fI\-b blks\fP \- Specify file system size (unixv7)" +.br +.B "\fI\-t type\fP \- Use alternate, device-dependent size" +.br +.B "\fI\-e n\fP \- Specify extra space for directory entries" .br -.B "\fI\-e n\fP \- Specify extra space for directory entries (RT11, OS/8)" +.B " (RT11, OS/8)" .br -.B "\fIfile\fP \- name of the container file" +.B "\fI\-i n\fP \- Specify inode count (8 to 65500) (UnixV7)" .br -.B "\fItype\fP \- type of container file system" +.B "\fIfile\fP \- Name of the container file" +.br +.B "\fItype\fP \- Type of container file system" +.br +.RE +.RE +.TP +.B "\fImkdir\fP [-p] dev:dirspec" +Create a new empty directory on a mounted file system. +.br +.RS +.RS +.B "\fI\-p\fP \- Create any missing intermediate directories" .br .RE .RE @@ -136,9 +156,9 @@ Set parameters on a mounted file system. .br .RS .RS -.B "\fIdev:\fP \- name supplied on a previous mount" +.B "\fIdev:\fP \- Name supplied on a previous mount" .br -.B "\fIargs ...\fP\- arguments are passed on to the file system" +.B "\fIargs ...\fP\- Arguments are passed on to the file system" .br .RE .RE @@ -148,7 +168,7 @@ Display information about the file system(s) within the container file. .br .RS .RS -.B "\fIdev:\fP \- name supplied on a previous mount" +.B "\fIdev:\fP \- Name supplied on a previous mount" .RE .RE .TP @@ -157,13 +177,13 @@ List the contents of a specific directory. .br .RS .RS -.B "\fI\-f\fP \- display a full (vs. brief) directory" +.B "\fI\-f\fP \- Display a full (vs. brief) directory" .br -.B "\fI\-n\fP \- don't rewind tape before listing directory" +.B "\fI\-n\fP \- Don't rewind tape before listing directory" .br -.B "\fIdev:\fP \- name supplied on a previous mount" +.B "\fIdev:\fP \- Name supplied on a previous mount" .br -.B "\fIdirspec\fP \- filespec to display, may include wildcards" +.B "\fIdirspec\fP \- Filespec to display, may include wildcards" .br .RE .RE @@ -173,45 +193,45 @@ 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" +.B "\fI\-b\fP \- Dump byte (8-bits) at a time" .br -.B "\fI\-c\fP \- dump in character format" +.B "\fI\-c\fP \- Dump in character format" .br -.B "\fI\-d\fP \- dump double-word (32-bits) at a time" +.B "\fI\-d\fP \- Dump double-word (32-bits) at a time" .br -.B "\fI\-w\fP \- dump word (16-bits) at a time" +.B "\fI\-w\fP \- Dump word (16-bits) at a time" .br -.B "\fI\-x\fP \- dump in hex format (default is octal)" +.B "\fI\-x\fP \- Dump in hex format (default is octal)" .br -.B "\fI\-n\fP \- don't rewind magtape before looking for file" +.B "\fI\-n\fP \- Don't rewind magtape before looking for file" .br -.B "\fIdev:\fP \- name supplied on a previous mount" +.B "\fIdev:\fP \- Name supplied on a previous mount" .br -.B "\fIdirspec\fP \- filespec to dump" +.B "\fIdirspec\fP \- Filespec to dump" .br .RE .RE .TP -.B "\fIcopy\fP [-anpc blks] dev1:srcfile dev2:dstfile" +.B "\fIcopy\fP [-anp] [-c blks] dev1:srcfile dev2:dstfile" Copy a file. .br .RS .RS -.B "\fI\-a\fP \- copy in ASCII mode (translates line endings)" +.B "\fI\-a\fP \- Copy in ASCII mode (translates line endings)" .br -.B "\fI\-p\fP \- pad the file with NULLs" +.B "\fI\-p\fP \- Pad the file with NULLs" .br -.B "\fI\-n\fP \- don't rewind magtape before looking for input file" +.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" +.B "\fI\-c blks\fP \- Make contiguous file of specified size" .br -.B "\fIdev1:\fP \- name supplied on a previous mount" +.B "\fIdev1:\fP \- Name supplied on a previous mount" .br -.B "\fIsrcfile\fP \- source file to copy" +.B "\fIsrcfile\fP \- Source file to copy" .br -.B "\fIdev2:\fP \- name supplied on a previous mount" +.B "\fIdev2:\fP \- Name supplied on a previous mount" .br -.B "\fIdstfile\fP \- destination file to copy" +.B "\fIdstfile\fP \- Destination file to copy" .br .RE .RE @@ -221,11 +241,11 @@ Types the contents of the file on stdout. .br .RS .RS -.B "\fI\-n\fP \- don't rewind magtape before looking for file" +.B "\fI\-n\fP \- Don't rewind magtape before looking for file" .br -.B "\fIdev:\fP \- name supplied on a previous mount" +.B "\fIdev:\fP \- Name supplied on a previous mount" .br -.B "\fIfilespec\fP\- filespec to type" +.B "\fIfilespec\fP\- Filespec to type" .br .RE .RE @@ -235,9 +255,9 @@ Deletes the specified file. .br .RS .RS -.B "\fIdev:\fP \- name supplied on a previous mount" +.B "\fIdev:\fP \- Name supplied on a previous mount" .br -.B "\fIfilespec\fP\- filespec to delete" +.B "\fIfilespec\fP\- Filespec to delete" .br .RE .RE @@ -251,9 +271,9 @@ Echo and execute commands from a file. .br .RS .RS -.B "\fI\-q\fP \- don't echo commands as they are read" +.B "\fI\-q\fP \- Don't echo commands as they are read" .br -.B "\fIcmdFile\fP \- file containing fsio commands" +.B "\fIcmdFile\fP \- File containing \fBfsio\fP commands" .br .RE .RE @@ -263,7 +283,7 @@ Displays help text on stdout. .br .TP .B "\fIexit\fP" -Causes fsio to exit (the quit command has the same effect). +Causes \fBfsio\fP to exit (the quit command has the same effect). .br .TP .B "\fIrewind\fP dev:" @@ -271,7 +291,7 @@ Positions the device to the start of the tape. .br .RS .RS -.B "\fIdev:\fP \- name supplied on a previous mount" +.B "\fIdev:\fP \- Name supplied on a previous mount" .br .RE .RE @@ -281,7 +301,7 @@ Positions the device to the end of the tape. .br .RS .RS -.B "\fIdev:\fP \- name supplied on a previous mount" +.B "\fIdev:\fP \- Name supplied on a previous mount" .br .RE .RE @@ -291,7 +311,7 @@ Positions the device by skipping forward over files. .br .RS .RS -.B "\fIdev:\fP \- name supplied on a previous mount" +.B "\fIdev:\fP \- Name supplied on a previous mount" .br .B "\fIn\fP \- # of files to skip (must be > 0)" .br @@ -303,7 +323,7 @@ Positions the device by skipping backward over files. .br .RS .RS -.B "\fIdev:\fP \- name supplied on a previous mount" +.B "\fIdev:\fP \- Name supplied on a previous mount" .br .B "\fIn\fP \- # of files to skip (must be > 0)" .br @@ -331,13 +351,16 @@ This function depends on the value of blks: .br .B "\fIdosmt\fP \- container file in DOS-11 magtape format" .br -.B "\fIos8\fP \- OS/8 on RX01, RX01 or RK05" +.B "\fIos8\fP \- OS/8 on RX01, RX02 or RK05" +.br +.B "\fIunixv7\fP \- Unix V7" .br .SH SEE ALSO .BR fsio-dos11 (1), .BR fsio-rt11 (1) .BR fsio-dosmt (1) .BR fsio-os8 (1) +.BR fsio-unixv7 (1) .SH AUTHOR John Forecast, .br diff --git a/converters/fsio/fsio.c b/converters/fsio/fsio.c index 435888a..99ebe21 100644 --- a/converters/fsio/fsio.c +++ b/converters/fsio/fsio.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2018 John Forecast. All Rights Reserved. + * Copyright (C) 2018 - 2025 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: @@ -85,7 +85,7 @@ * file is open, and this routine should verify that it contains a valid * file system. This routine may print out information about the file * system. Return 1 if the file system is valid, 0 if the file system is - * invalid and -1 if the file system is invalid and an erroir message + * invalid and -1 if the file system is invalid and an error message * has already been printed. * * void (*umount)(struct mountedFS *mount); @@ -97,8 +97,10 @@ * * size_t (*size)(void) * - * Return the number of byte used for the container file. The command - * line switch (-t type) may be used to override the default size. + * Return the number of bytes used for the container file. The command + * line switch (-t type) may be used to override the default size. If + * there is a problem computing the size of a container file (e.g. -t + * switch missing), print an error message and return (size_t)-1. * * int (*newfs)(struct mountedFS *mount, size_t size); * @@ -108,6 +110,15 @@ * by this command. This routine returns 1 if the file system was * successfully created, 0 otherwise. * + * int (*mkdir)(struct mountedFS *mount, uint8_t unit, char *fname); + * + * Create a new empty directory on the mounted file system. This routine + * returns 1 if the directory was successfully created, 0 otherwise. If + * the "-p" switch is present (SWISSET('p')), any missing intermediate + * directories will be created. If the "-p" switch is present and 0 is + * returned, an unknown number of intermediate directories may have + * been created. + * * void (*set)(struct mountedFS *mount, uint8_t unit, uint8_t present); * * This routine is called when processing a "set" command. The arguments @@ -123,7 +134,7 @@ * 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). + * ("present" 0) or all file systems in the container ("present" 1). * * void (*dir)(struct mountedFS *mount, uint8_t unit, char *fname); * @@ -284,6 +295,7 @@ int verbose = 0, quiet = 0; static void doMount(void); static void doUmount(void); static void doNewfs(void); +static void doMkdir(void); static void doSet(void); static void doInfo(void); static void doDir(void); @@ -311,12 +323,13 @@ struct command { cmd_t func; /* Command execution function */ } cmdTable[] = { #ifdef DEBUG - { "mount", OPTIONS("dfrt:x"), 3, 3, 0, doMount }, + { "mount", OPTIONS("dfo:rt:x"), 3, 3, 0, doMount }, #else - { "mount", OPTIONS("frt:x"), 3, 3, 0, doMount }, + { "mount", OPTIONS("fo:rt:x"), 3, 3, 0, doMount }, #endif { "umount", NULL, 1, 1, 0, doUmount }, - { "newfs", OPTIONS("e:t:"), 2, 2, 0, doNewfs }, + { "newfs", OPTIONS("b:e:i:t:"), 2, 2, 0, doNewfs }, + { "mkdir", OPTIONS("p"), 1, 1, 0, doMkdir }, { "set", NULL, 2, MAX_CMDLEN, 0, doSet }, { "info", NULL, 1, 1, 0, doInfo }, { "dir", OPTIONS("fn"), 1, 1, 0, doDir }, @@ -336,6 +349,8 @@ struct command { { NULL, NULL, 0, 0, 0, doExit } }; +char *command = NULL; /* Current command being executed */ + /* * Switches and values */ @@ -406,6 +421,7 @@ extern struct FSdef dos11FS; extern struct FSdef rt11FS; extern struct FSdef dosmtFS; extern struct FSdef os8FS; +extern struct FSdef unixv7FS; #define MAX(a, b) (((a) > (b)) ? (a) : (b)) #define MIN(a, b) (((a) < (b)) ? (a) : (b)) @@ -463,6 +479,9 @@ static void FSioInit(void) os8FS.next = fileSystems; fileSystems = &os8FS; + + unixv7FS.next = fileSystems; + fileSystems = &unixv7FS; } /*++ @@ -609,7 +628,6 @@ static int checkDev( * * 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) @@ -626,7 +644,6 @@ static int checkDev( * --*/ static struct mountedFS *findMount( - char *cmd, char *dev, uint8_t *unit, uint8_t *present, @@ -655,13 +672,15 @@ static struct mountedFS *findMount( unitno = strtoul(&dev[i], &endptr, 8); if (*endptr != '\0') { fprintf(stderr, - "%s: Device \"%s\" unit number invalid\n", cmd, origdev); + "%s: Device \"%s\" unit number invalid\n", + command, origdev); return NULL; } if (unitno > 0377) { fprintf(stderr, - "%s: Device \"%s\" unit number too large\n", cmd, origdev); + "%s: Device \"%s\" unit number too large\n", + command, origdev); return NULL; } dev[i] ='\0'; @@ -670,7 +689,7 @@ static struct mountedFS *findMount( } fprintf(stderr, "%s: Device \"%s\" contains non-alpha character\n", - cmd, origdev); + command, origdev); return NULL; } @@ -683,12 +702,13 @@ static struct mountedFS *findMount( ((fs->filesys->flags & FS_UNITVALID) != 0)) break; fprintf(stderr, - "%s: \"%s\" does not support unit numbers\n", cmd, fs->name); + "%s: \"%s\" does not support unit numbers\n", + command, fs->name); return NULL; } if (fs == NULL) - fprintf(stderr, "%s: Device \"%s\" not mounted\n", cmd, origdev); + fprintf(stderr, "%s: Device \"%s\" not mounted\n", command, origdev); } else { if (getenv("FSioForceLocal") != NULL) { fprintf(stderr, "Local file system access requires use of \"local:\"\n"); @@ -938,11 +958,14 @@ static void doNewfs(void) /* * Get the required container file size. */ - if (filesys->size != NULL) + if (filesys->size != NULL) { size = (*filesys->size)(); + if (size == (size_t)-1) + return; + } if ((mount.container = fopen(words[0], "w+")) != NULL) { - off_t offset = size - 1; + off_t offset = size ? size - 1 : 0; mount.skip = 0; @@ -971,6 +994,39 @@ static void doNewfs(void) } else fprintf(stderr, "newfs: \"%s\" is not a supported file system type\n", words[1]); } +/*++ + * d o M k d i r + * + * Create a new empty directory on a mounted file system. This command is + * only available if the file system supports multiple directories. + * + * Inputs: + * + * None + * + * Outputs: + * + * None + * + * Returns: + * + * None + * + --*/ +static void doMkdir(void) +{ + struct mountedFS *mount; + char *fname; + uint8_t unit, present; + + if ((mount = findMount(words[0], &unit, &present, &fname)) != NULL) { + if (mount->filesys->mkdir != NULL) + (*mount->filesys->mkdir)(mount, unit, fname); + else fprintf(stderr, "mkdir: \"%s\" does not support \"mkdir\" command\n", + mount->filesys->fstype); + } +} + /*++ * d o S e t * @@ -995,7 +1051,7 @@ static void doSet(void) char *fname; uint8_t unit, present; - if ((mount = findMount("set", words[0], &unit, &present, &fname)) != NULL) { + if ((mount = findMount(words[0], &unit, &present, &fname)) != NULL) { if (*fname == '\0') { if (mount->filesys->set != NULL) (*mount->filesys->set)(mount, unit, present); @@ -1029,7 +1085,7 @@ static void doInfo(void) char *fname; uint8_t unit, present; - if ((mount = findMount("info", words[0], &unit, &present, &fname)) != NULL) { + if ((mount = findMount(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"); @@ -1060,7 +1116,7 @@ static void doDir(void) char *fname; uint8_t unit, present; - if ((mount = findMount("dir", words[0], &unit, &present, &fname)) != NULL) { + if ((mount = findMount(words[0], &unit, &present, &fname)) != NULL) { (*mount->filesys->dir)(mount, unit, fname); return; } @@ -1091,7 +1147,7 @@ static void doDump(void) void *file; uint8_t unit, present; - if ((mount = findMount("dump", words[0], &unit, &present, &fname)) != NULL) { + if ((mount = findMount(words[0], &unit, &present, &fname)) != NULL) { if ((file = (*mount->filesys->openFileR)(mount, unit, fname)) != NULL) { unsigned int offset = 0, datasz = 2; uint8_t data1; @@ -1210,8 +1266,8 @@ static void doCopy(void) void *fileSrc, *fileDest; char *endptr; - mountSrc = findMount("copy", words[0], &unitSrc, &presentSrc, &fnameSrc); - mountDest = findMount("copy", words[1], &unitDest, &presentDest, &fnameDest); + mountSrc = findMount(words[0], &unitSrc, &presentSrc, &fnameSrc); + mountDest = findMount(words[1], &unitDest, &presentDest, &fnameDest); if ((mountSrc != NULL) && (mountDest != NULL)) { fsSrc = mountSrc->filesys; @@ -1300,7 +1356,7 @@ static void doType(void) */ SWSET('a'); - if ((mount = findMount("type", words[0], &unit, &present, &fname)) != NULL) { + if ((mount = findMount(words[0], &unit, &present, &fname)) != NULL) { if ((file = (*mount->filesys->openFileR)(mount, unit, fname)) != NULL) { char buf[BFRSIZ]; size_t len, i; @@ -1344,7 +1400,7 @@ static void doDelete(void) void *file; uint8_t unit, present; - if ((mount = findMount("delete", words[0], &unit, &present, &fname)) != NULL) { + if ((mount = findMount(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) @@ -1479,6 +1535,9 @@ static void doHelp(void) "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" + " mkdir [-p] dev:dirspec\n\n" + "Create a new empty directory on a mounted file system. The \"-p\"\n" + "switch may be used to create any missing intermediate directories.\n\n" " set dev: args ...\n\n" "Set parameter(s) on a mounted file system.\n\n" " info dev:\n\n" @@ -1597,7 +1656,7 @@ static void doRewind(void) char *fname; uint8_t unit, present; - if ((mount = findMount("rewind", words[0], &unit, &present, &fname)) != NULL) { + if ((mount = findMount(words[0], &unit, &present, &fname)) != NULL) { if ((mount->filesys->flags & FS_TAPE) == 0) { fprintf(stderr, "rewind: Command only valid on magtapes\n"); return; @@ -1633,7 +1692,7 @@ static void doEOM(void) char *fname; uint8_t unit, present; - if ((mount = findMount("eom", words[0], &unit, &present, &fname)) != NULL) { + if ((mount = findMount(words[0], &unit, &present, &fname)) != NULL) { if ((mount->filesys->flags & FS_TAPE) == 0) { fprintf(stderr, "eom: Command only valid on magtapes\n"); return; @@ -1670,7 +1729,7 @@ static void doSkipForw(void) char *fname; uint8_t unit, present; - if ((mount = findMount("skipf", words[0], &unit, &present, &fname)) != NULL) { + if ((mount = findMount(words[0], &unit, &present, &fname)) != NULL) { if ((mount->filesys->flags & FS_TAPE) == 0) { fprintf(stderr, "skipf: Command only valid on magtapes\n"); return; @@ -1716,7 +1775,7 @@ static void doSkipRev(void) char *fname; uint8_t unit, present; - if ((mount = findMount("skipf", words[0], &unit, &present, &fname)) != NULL) { + if ((mount = findMount(words[0], &unit, &present, &fname)) != NULL) { if ((mount->filesys->flags & FS_TAPE) == 0) { fprintf(stderr, "skipr: Command only valid on magtapes\n"); return; @@ -1869,6 +1928,7 @@ static void FSioExecute( /* * Execute the command. */ + command = cmdTable[idx].name; (*cmdTable[idx].func)(); } else fprintf(stderr, "Unknown command \"%s\"\n", words[0]); } diff --git a/converters/fsio/fsio.h b/converters/fsio/fsio.h index c9f1d60..cbd6a3c 100644 --- a/converters/fsio/fsio.h +++ b/converters/fsio/fsio.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2018 John Forecast. All Rights Reserved. + * Copyright (C) 2018 - 2025 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: @@ -35,12 +35,13 @@ /* * Mode for open files */ -enum openMode { M_RD, M_WR }; +enum openMode { M_RD, M_WR, M_UNKNOWN }; #include "dos11.h" #include "rt11.h" #include "dosmt.h" #include "os8.h" +#include "unixv7.h" /* * All of the supported file systems are natively little endian so we only @@ -81,7 +82,7 @@ enum openMode { M_RD, M_WR }; #endif extern uint32_t swPresent; -extern char *swValue[]; +extern char *command, *swValue[]; #define SWISSET(c) ((swPresent & (1 << (c - 'a'))) != 0) #define SWSET(c) swPresent |= (1 << (c - 'a')) @@ -114,6 +115,7 @@ struct FSdef { void (*umount)(struct mountedFS *); size_t (*size)(void); int (*newfs)(struct mountedFS *, size_t); + int (*mkdir)(struct mountedFS *, uint8_t, char *); void (*set)(struct mountedFS *, uint8_t, uint8_t); void (*info)(struct mountedFS *, uint8_t, uint8_t); void (*dir)(struct mountedFS *, uint8_t, char *); @@ -157,11 +159,13 @@ struct mountedFS { struct RT11data _rt11; struct DOSMTdata _dosmt; struct OS8data _os8; + struct UNIXV7data _unixv7; } FSdata; #define dos11data FSdata._dos11 #define rt11data FSdata._rt11 #define dosmtdata FSdata._dosmt #define os8data FSdata._os8 +#define unixv7data FSdata._unixv7 }; extern int FSioReadBlob(struct mountedFS *, off_t, unsigned int, void *); diff --git a/converters/fsio/fsio.txt b/converters/fsio/fsio.txt index f924c96..cbb11bf 100644 --- a/converters/fsio/fsio.txt +++ b/converters/fsio/fsio.txt @@ -26,6 +26,8 @@ umount - removes knowledge of a container file from fsio newfs - create a new container with an empty file system +mkdir - create a new directory + set - set file system parameters info - display information about the file system @@ -87,19 +89,27 @@ Full command syntax: 1. mount - mount [-dfr] [-t type] dev[:] container type + mount [-dfr] [-o nnn] [-t type] dev[:] container type Make the specified container file available to fsio for I/O. -d Generate debug output if fsio is built with the DEBUG symbol defined + -f Force the mount to happen even if we are unable to completely validate the container file format + -r If present, the file system is only available for read access + -o nnn This is only valid for Unix V7 file systems for specifying + the starting offset of a partition within the container + file. + -t type Specify the type of the container file. This is only required for OS/8 file systems where type should be one of "rx01", - "rx02" or "rk05". + "rx02" or "rk05". It is not required for Unix V7 file systems + but, if supplied, allows for an additional integrity check + before mounting the file system. dev[:] User supplied name to be used for accessing files within the container file. Files within the container are named by using @@ -126,7 +136,7 @@ Full command syntax: 3. newfs - newfs [-t type] container type + newfs [-b size] [-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 @@ -137,7 +147,10 @@ Full command syntax: dos11 RK05 image (2.5MB, 4800 blocks) rt11 MSCP image (32MB, 65535 blocks) dosmt An empty file suitable for use with any magtape controller - os8 An OS/8 file system + os8 RK05 image (2.5MB, 3248 blocks) + unixv7 RK05 image (2.5MB, 4872 blocks) + + -b size Specify # of blocks for the container (only valid for unixv7) -t type Use a different size for the container file. The size used will be file system dependent. @@ -153,11 +166,30 @@ Full command syntax: rx01 RX01 image (2002 sectors of 128 bytes each) rx02 RX02 imgae (2002 sectors of 256 bytes each) + For unixv7, the following disk types are valid: + + rk05 RK05 image (4871 blocks) + rl01 RL01 image (5MB, 10240 blocks) + rl02 RL02 image (10MB, 20480 blocks) + rk06 RK06 image (13MB, 27126 blocks) + rk07 RK07 image (26MB, 53790 blocks) + container The name of the file to create type The type of the file system to create in the container - 4. set + 4. mkdir + + mkdir [-p] dev:dirspec + + Creates a new empty directory on a mounted file system. + + -p If present, create any missing intermediate directories. + + dev: The name of a previosly mounted file system + dirspec Specification of the directory to create using the dev: syntax. + + 5. set set dev: args ... @@ -166,7 +198,7 @@ Full command syntax: dev: The name of a previously mounted file system - 5. info + 6. info info dev: @@ -175,7 +207,7 @@ Full command syntax: dev: The name of a previously mounted file system - 6. dir + 7. dir dir [-fn] dev:dirspec @@ -188,7 +220,7 @@ Full command syntax: dev: The name of a previosly mounted file system dirspec Specification of the directory to list using the dev: syntax. - 7. dump + 8. dump dump [-bcdnwx] dev:src @@ -209,7 +241,7 @@ Full command syntax: dev: The name of a previosly mounted file system src The name of the source file (e.g. dp:input.dat) - 8. copy + 9. copy copy [-anpc blocks] dev1:src dev2:dest @@ -244,7 +276,7 @@ Full command syntax: Note that wildcard naming is not supported and the source and destination file names must be fully specified. - 9. type +10. type type [-n] dev:src @@ -257,7 +289,7 @@ Full command syntax: dev: The name of a previously mounted file system src The name of the source file (e.g. dp:input.txt) -10. delete +11. delete delete dev:file @@ -266,13 +298,13 @@ Full command syntax: dev: The name of a previously mounted file system file The name of the file to be deleted -11. status +12. status status Displays the currently mounted file systems. -12. do +13. do do [-q] cmdFile @@ -280,19 +312,19 @@ Full command syntax: -q By default, command lines are echoed. Use -q to not echo. -13. help +14. help help Display help text on the terminal. -14. exit +15. exit exit Terminates the fsio application. -15. rewind +16. rewind rewind dev: @@ -300,7 +332,7 @@ Full command syntax: dev: The name of a previously mounted file system -16. eom +17. eom eom dev: @@ -309,7 +341,7 @@ Full command syntax: dev: The name of a previously mounted file system -17. skipf +18. skipf skipf dev: n @@ -320,7 +352,7 @@ Full command syntax: dev: The name of a previously mounted file system n Number of files to skip (must be > 0) -18. skipr +19. skipr skipr dev: n diff --git a/converters/fsio/fsioSimh.txt b/converters/fsio/fsioSimh.txt index cc76d33..b7c128c 100644 --- a/converters/fsio/fsioSimh.txt +++ b/converters/fsio/fsioSimh.txt @@ -83,6 +83,36 @@ 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. +Research Unix V7 +---------------- + +For Unix V7, fsio understands the native disk format so we can transfer +directly into the bootable SIMH disk ("unix_v7_rl.dsk" in the examples). Note +that Unix disks may contain multiple partitions but the partition layout was +not typically recorded on the disk, it was hard coded into the device driver. +fsio does support the "-o nnn" switch to mount to specify the block offset of +the partition to be mounted if it is not the first on the disk. The example +below will copy an ASCII file into the root directory of the partition (if +fsio is running on a Unix-like system, the "-a" switch is not needed): + + pi@host:~ $ fsio + fsio> mount dl unix_v7_rl.dsk unixv7 + Pack name: , File system name: , Total blocks: 18000 + fsio> copy -a file.txt dl:file.txt + fsio> quit + pi@host:~ $ + +By default, new files will be created with UID 0, GID 0. To override this use: + + fsio> set dl: uid 1 + fsio> set dl: gid 100 + fsio> copy -a file.txt dl:file.txt + +Research Unix V7 was distributed by DEC as v7m and later as Ultrix-11. +Ultrix-11 V3.0/V3.1 used a modified disk layout with a 1K byte block size +which is incompatible with fsio but it did come with /etc/rawfs which allows +access to disks using a 512 byte block size. + Foreign File System Support --------------------------- diff --git a/converters/fsio/local.c b/converters/fsio/local.c index 46809d7..a33662d 100644 --- a/converters/fsio/local.c +++ b/converters/fsio/local.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2018 John Forecast. All Rights Reserved. + * Copyright (C) 2018 - 2025 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: @@ -356,6 +356,7 @@ struct FSdef localFS = { NULL, NULL, NULL, + NULL, localInfo, localDir, localOpenFileR, diff --git a/converters/fsio/os8.c b/converters/fsio/os8.c index 8e705b7..b85a9d8 100644 --- a/converters/fsio/os8.c +++ b/converters/fsio/os8.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2019 John Forecast. All Rights Reserved. + * Copyright (C) 2019 - 2025 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: @@ -2384,7 +2384,7 @@ static void os8Umount( * * Returns: * - * Size of the container file in block of default file system size + * Size of the container file in blocks of default file system size * --*/ static size_t os8Size(void) @@ -2410,7 +2410,7 @@ static size_t os8Size(void) * Inputs: * * mount - pointer to a mounted file system descriptor - * (nit in the mounted file system list) + * (not in the mounted file system list) * size - the sized (in blocks) of the filesystem * * Outputs: @@ -2440,7 +2440,8 @@ static int os8Newfs( dev++; } - dev = OS8Devices; + fprintf(stderr, "newfs: Unknown device type \"%s\"\n", SWGETVAL('t')); + return 0; } found: mount->skip = dev->skip; @@ -2862,8 +2863,6 @@ static void os8DeleteFile( data->buf[file->offset + OS8_DI_FNAME1] = 0; data->buf[file->offset + OS8_ED_LENGTH] = data->buf[file->offset + file->extra + OS8_DI_LENGTH]; - data->buf[OS8_DH_ENTRIES] = - htole16(os8Value(-(os8Neg(le16toh(data->buf[OS8_DH_ENTRIES])) - 1))); os8SlideUp(mount, file->offset + OS8_ED_SIZE, file->offset + file->entrysz, file->entrysz, file->remain); @@ -2905,6 +2904,12 @@ static void os8CloseFile( struct os8OpenFile *file = filep; if (file->mode == M_WR) { + if (SWISSET('a')) { + char ch ='\032'; + + os8WriteBytes(file, &ch, 1); + } + if (file->current != 0) /* * Flush the current buffer. @@ -2946,7 +2951,41 @@ static size_t os8ReadFile( ) { struct os8OpenFile *file = filep; + char *bufr = buf; + + if (SWISSET('a')) { + char ch; + size_t count = 0; + + if (file->eof != 0) + return 0; + + /* + * Read a full or partial line from the open file. + */ + while ((buflen != 0) && (os8ReadBytes(file, &ch, 1) == 1)) { + if (ch == '\032') { + /* + * ^Z indicating EOF + */ + file->eof = 1; + break; + } + /* + * Ignore NULL bytes in ASCII mode + */ + if (ch != '\0') { + ch &= 0177; + bufr[count++] = ch; + buflen--; + if (ch == '\n') + break; + } + } + return count; + } + return os8ReadBytes(file, buf, buflen); } @@ -2997,6 +3036,7 @@ struct FSdef os8FS = { os8Umount, os8Size, os8Newfs, + NULL, os8Set, os8Info, os8Dir, diff --git a/converters/fsio/os8.h b/converters/fsio/os8.h index 4346503..e9237d5 100644 --- a/converters/fsio/os8.h +++ b/converters/fsio/os8.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2019 John Forecast. All Rights Reserved. + * Copyright (C) 2019 - 2025 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: @@ -217,6 +217,7 @@ struct os8OpenFile { uint16_t wordpos; /* Current word offset */ uint8_t bytepos; /* Current byte position */ off_t written; /* # of bytes written to the file */ + uint8_t eof; /* EOF seen on last read */ }; /* diff --git a/converters/fsio/rt11.c b/converters/fsio/rt11.c index 94c1e48..dd1947e 100644 --- a/converters/fsio/rt11.c +++ b/converters/fsio/rt11.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2018 John Forecast. All Rights Reserved. + * Copyright (C) 2018 - 2025 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: @@ -1565,7 +1565,7 @@ static int validate( position += le16toh(data->buf[off + RT11_DI_LENGTH]); - if ((status & RT11_E_MPTY) != 0) + if ((status & RT11_E_EOS) != 0) break; /* @@ -1656,8 +1656,8 @@ int rt11ReadBytes( * Inputs: * * file - pointer to an open file descriptor - * buf - pointer to a buffer to receive the data - * len - # of bytes of data to read + * buf - pointer to a buffer with the data to be written + * len - # of bytes of data to write * * Outputs: * @@ -1881,9 +1881,15 @@ static int rt11Mount( } printf("%s%o:\n", mount->name, i); - if (version != NULL) - printf(" Version: %s, System ID: %12s\n", - version, (char *)&data->buf[RT11_HB_SYSID]); + if (version != NULL) { + char sysid[16]; + + memset(sysid, 0, sizeof(sysid)); + strncpy(sysid, (char *)&data->buf[RT11_HB_SYSID], + strlen(RT11_SYSID)); + + printf(" Version: %s, System ID: %s\n", version, sysid); + } printf(" Total blocks: %5d, Free blocks: %5d\n" " Directory segments: %2d (Highest in use: %d)\n" " Extra bytes/directory entry: %d\n", @@ -1958,7 +1964,8 @@ static size_t rt11Size(void) } if (size == ((RT11_MAXPARTSZ - 1) * RT11_BLOCKSIZE)) fprintf(stderr, - "newfs: Invalid device type \"%s\", using default\n", type); + "%s: Invalid device type \"%s\", using default\n", + command, type); } return size; } @@ -2027,7 +2034,6 @@ static int rt11Newfs( * Remove possible first track */ size = (size - mount->skip) / RT11_BLOCKSIZE; - // size = ((size * RT11_BLOCKSIZE) - mount->skip) / RT11_BLOCKSIZE; /* * Mark partition 0 as valid @@ -2570,6 +2576,7 @@ struct FSdef rt11FS = { rt11Size, rt11Newfs, NULL, + NULL, rt11Info, rt11Dir, rt11OpenFileR, diff --git a/converters/fsio/tape.c b/converters/fsio/tape.c index 59f0b8a..5797d87 100644 --- a/converters/fsio/tape.c +++ b/converters/fsio/tape.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2018 John Forecast. All Rights Reserved. + * Copyright (C) 2018 - 2025 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: @@ -568,7 +568,7 @@ int tapeWriteEOM( * * Returns: * - * 1 if EOM record successfully written, 0 otherwise + * 1 if TM record successfully written, 0 otherwise * --*/ int tapeWriteTM( diff --git a/converters/fsio/unixv7.c b/converters/fsio/unixv7.c new file mode 100644 index 0000000..7260ac1 --- /dev/null +++ b/converters/fsio/unixv7.c @@ -0,0 +1,3080 @@ +/* + + Copyright (c) 2025, John Forecast + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + JOHN FORECAST BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Except as contained in this notice, the name of John Forecast shall not + be used in advertising or otherwise to promote the sale, use or other dealings + in this Software without prior written authorization from John Forecast. + +*/ + +/* + * Support routines for handling Unix V7 file systems under fsio + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "fsio.h" + +/* + * Table of "set" commands + */ +static char *setCmds[] = { + "uid", + "gid", + NULL +}; +#define UNIXV7SET_UID 0 +#define UNIXV7SET_GID 1 + +int unixv7ReadBlock(struct mountedFS *, unsigned int, void *); +int unixv7WriteBlock(struct mountedFS *, unsigned int, void *); +int unixv7ReadInode(struct mountedFS *, v7_ino_t, struct v7_dinode *); +int unixv7WriteInode(struct mountedFS *, v7_ino_t, struct v7_dinode *); + +static void breli(struct mountedFS *, v7_daddr_t, int); + +extern int args; +extern char **words; +extern int quiet; + +#define MAX(a, b) (((a) >= (b)) ? (a) : (b)) +#define MIN(a, b) (((a) <= (b)) ? (a) : (b)) + +static char *rwx[] = { + "---", "--x", "-w-", "-wx", "r--", "r-x", "rw-", "rwx" +}; + +/*++ + * u n i x v 7 D e v i c e s + * + * List of UNIX V7 device types supported by this program. + * + --*/ +struct UNIXV7device UNIXV7Devices[] = { + /* (Cylinders * Surfaces * Sectors * Sector Size) / 512 */ + { "rk05", (203LL * 2LL * 12LL * 512LL) / 512LL }, + { "rl01", (256LL * 2LL * 40LL * 256LL) / 512LL }, + { "rl02", (512LL * 2LL * 40LL * 256LL) / 512LL }, + { "rk06", (411LL * 3LL * 22LL * 512LL) / 512LL }, + { "rk07", (815LL * 3LL * 22LL * 512LL) / 512LL }, + { NULL, 0 } +}; + +/* + * Internal file system operations. + */ + +/*++ + * b a l l o c + * + * Allocate a free block on the UNIX V7 file system. + * + * Inputs: + * + * mount - pointer to mounted file system descriptor + * + * Outputs: + * + * The super block and/or the on-disk free list may be updated. + * + * Returns: + * + * Allocated block number, 0 if allocation failed + * + --*/ +static v7_daddr_t balloc( + struct mountedFS *mount +) +{ + struct UNIXV7data *data = &mount->unixv7data; + struct v7_filsys *sb = (struct v7_filsys *)&data->superblk; + v7_daddr_t bno; + uint8_t buf[V7_BLOCKSIZE]; + + if (le16toh(sb->s_nfree) > 0) { + sb->s_nfree = htole16(le16toh(sb->s_nfree) - 1); + if ((bno = V7_LONG(le32toh(sb->s_free[le16toh(sb->s_nfree)]))) != 0) { + /* + * If we have just exhausted the free block cache in the super block, + * replenish it from the on-disk free block list. + */ + if (le16toh(sb->s_nfree) <= 0) { + struct v7_fblk *fb = (struct v7_fblk *)buf; + + if (unixv7ReadBlock(mount, bno, buf) == 0) + return 0; + + sb->s_nfree = fb->df_nfree; + memcpy(sb->s_free, fb->df_free, sizeof(fb->df_free)); + } + sb->s_fmod = 1; + + /* + * Zero out the newly allocated block + */ + memset(buf, 0, sizeof(buf)); + if (unixv7WriteBlock(mount, bno, buf) == 0) + return 0; + return bno; + } + } + return 0; +} + +/*++ + * b f r e e + * + * Release a block back to the free list on a UNIX V7 file system. + * + * Inputs: + * + * mount - pointer to mounted file system descriptor + * bno - block number to be released + * + * Outputs: + * + * The super block and/or the on-disk free list may be updated. + * + * Returns: + * + * None + * + --*/ +static void bfree( + struct mountedFS *mount, + v7_daddr_t bno +) +{ + struct UNIXV7data *data = &mount->unixv7data; + struct v7_filsys *sb = (struct v7_filsys *)&data->superblk; + + if (le16toh(sb->s_nfree) <= 0) + sb->s_nfree = 0; + + if (le16toh(sb->s_nfree) >= V7_NICFREE) { + uint8_t buf[V7_BLOCKSIZE]; + struct v7_fblk * fb = (struct v7_fblk *)buf; + + fb->df_nfree = sb->s_nfree; + memcpy(fb->df_free, sb->s_free, sizeof(sb->s_free)); + if (unixv7WriteBlock(mount, bno, buf) == 0) + return; + sb->s_nfree = 0; + } + sb->s_free[le16toh(sb->s_nfree)] = htole32(V7_LONG(bno)); + sb->s_nfree = htole16(le16toh(sb->s_nfree) + 1); + sb->s_fmod = 1; +} + +/*++ + * b m a p + * + * Map a file logical block number into a physical block number within a UNIX + * V7 file system for read access. Note that 0 is an invalid block address + * and is used to indicate an error. + * + * Inputs: + * + * mount - pointer to a mounted file system descriptor + * inode - pointer to a copy of an on-disk inode + * ino - inode number for "inode" + * lbn - logical block number within the file + * rwflg - Read (0) / Write (1) indicator + * + * + * Outputs: + * + * None + * + * Returns: + * + * Physical block number within the UNIX V7 file system, 0 if error + * + --*/ +static uint32_t bmap( + struct mountedFS *mount, + struct v7_dinode *inode, + v7_ino_t ino, + uint32_t lbn, + int rwflg +) +{ + struct UNIXV7data *data = &mount->unixv7data; + uint8_t *cp = (uint8_t *)inode->di_addr, buf[V7_BLOCKSIZE]; + uint32_t nb, *bap, addr[NADDR]; + int i, j, sh; + + /* + * Convert the 3-byte block addresses in the inode into 4-byte values + */ + for (i = 0; i < NADDR; i++, cp += 3) + V7_GET3ADDR(cp, addr[i]); + + /* + * Blocks 0..NADDR-4 are direct blocks + */ + if (lbn < NADDR - 3) { + if ((nb = addr[lbn]) == 0) { + if ((rwflg == V7_READ) || ((nb = balloc(mount)) == 0)) + return 0; + + if (unixv7WriteBlock(mount, nb, data->zero) == 0) + return 0; + + V7_PUT3ADDR(&inode->di_addr[lbn * 3], nb); + if (unixv7WriteInode(mount, ino, inode) == 0) + return 0; + } + return nb; + } + + /* + * Addresses NADDR-3, NADDR-2 and NADDR-1 have single, double and triple + * indirect blocks. First determine how many levels of indirection. + */ + sh = 0; + nb = 1; + lbn -= NADDR - 3; + + for (j = 3; j > 0; j--) { + sh += V7_NSHIFT; + nb <<= V7_NSHIFT; + if (lbn < nb) + break; + lbn -= nb; + } + if (j == 0) + return 0; + + /* + * Fetch the address from the inode + */ + if ((nb = addr[NADDR - j]) == 0) { + if ((rwflg == V7_READ) || ((nb = balloc(mount)) == 0)) + return 0; + + if (unixv7WriteBlock(mount, nb, data->zero) == 0) + return 0; + + V7_PUT3ADDR(&inode->di_addr[(NADDR - j) * 3], nb); + if (unixv7WriteInode(mount, ino, inode) == 0) + return 0; + } + + /* + * Fetch through the indirect blocks + */ + for (;j <= 3; j++) { + uint32_t nbsave = nb; + + if (unixv7ReadBlock(mount, nb, buf) == 0) + return 0; + bap = (uint32_t *)buf; + sh -= V7_NSHIFT; + i = (lbn >> sh) & V7_NMASK; + if ((nb = V7_ULONG(bap[i])) == 0) { + if ((rwflg == V7_READ) || ((nb = balloc(mount)) == 0)) + return 0; + + if (unixv7WriteBlock(mount, nb, data->zero) == 0) + return 0; + + bap[i] = V7_ULONG(nb); + if (unixv7WriteBlock(mount, nbsave, buf) == 0) + return 0; + } + } + return nb; +} + +/*++ + * b r e l + * + * Release all blocks associated with an inode. + * + * Inputs: + * + * mount - pointer to mounted file system descriptor + * inode - pointer to the inode + * + * Outputs: + * + * The super block and/or the on-disk free block chain may be updated. + * + * Returns: + * + * None + * + --*/ +static void brel( + struct mountedFS *mount, + struct v7_dinode *inode +) +{ + int i; + uint8_t *cp; + + for (i = 0, cp = (uint8_t *)inode->di_addr; i < NADDR; i++, cp += 3) { + v7_daddr_t bn; + + V7_GET3ADDR(cp, bn); + + if (bn != 0) { + switch (i) { + default: + bfree(mount, bn); + break; + + case NADDR-3: + breli(mount, bn, 0); + break; + + case NADDR-2: + breli(mount, bn, 1); + break; + + case NADDR-1: + breli(mount, bn, 3); + break; + } + } + } +} + +/*++ + * b r e l i + * + * Release all blocks in an indirect block. + * + * Inputs: + * + * mount - pointer to mounted file system descriptor + * bno - block number of the indirect block + * depth - indirect block depth + * + * Outputs: + * + * The super block and/or the on-disk free block chain may be updated. + * + * Returns: + * + * None + * + --*/ +static void breli( + struct mountedFS *mount, + v7_daddr_t bno, + int depth +) +{ + uint8_t buf[V7_BLOCKSIZE]; + int i; + + if (unixv7ReadBlock(mount, bno, buf)) { + for (i = 0; i < V7_NINDIR; i++) { + v7_daddr_t bn; + + V7_GET4ADDR(&buf[i * 4], bn) + + if (bn != 0) { + if (depth != 0) + breli(mount, bn, depth >> 1); + else bfree(mount, bn); + } + } + } + bfree(mount, bno); +} + +/*++ + * i a l l o c + * + * Allocate an unused inode on the UNIX V7 file system. + * + * Inputs: + * + * mount - pointer to mounted file system descriptor + * di - buffer to receive content of on-disk inode + * mode - value for V7IFMT field of di_mode + * (Used to mark inode as allocated) + * + * Outputs: + * + * The super block and/or the on-disk inode space may be updated. + * + * Returns: + * + * Allocated inode number, 0 if allocation failed + * + --*/ +static v7_ino_t ialloc( + struct mountedFS *mount, + struct v7_dinode *dip, + v7_ushort mode +) +{ + struct UNIXV7data *data = &mount->unixv7data; + struct v7_filsys *sb = (struct v7_filsys *)&data->superblk; + struct v7_dinode *dp; + v7_ino_t ino; + uint8_t buf[V7_BLOCKSIZE]; + uint16_t i, j; + + sb->s_fmod = 1; + + loop: + if (le16toh(sb->s_ninode) > 0) { + sb->s_ninode = htole16(le16toh(sb->s_ninode) - 1); + if ((ino = le16toh(sb->s_inode[le16toh(sb->s_ninode)])) <= V7_ROOTINO) + goto loop; + + if (unixv7ReadInode(mount, ino, dip) == 0) + return 0; + + if (dip->di_mode != 0) + goto loop; + + dip->di_mode = htole16(mode); + if (unixv7WriteInode(mount, ino, dip) == 0) + return 0; + + return ino; + } + + /* + * The super block cache of inodes is exhausted. Scan the inode area of + * the file system to replenish it. + */ + ino = 1; + + for (i = 2; (i != le16toh(sb->s_isize)) && + (le16toh(sb->s_ninode) < V7_NICINOD); i++) { + if (unixv7ReadBlock(mount, i, buf) == 0) + return 0; + + for (j = 0, dp = (struct v7_dinode *)buf; j < V7_INOPB; j++, dp++) { + if (dp->di_mode == 0) { + sb->s_inode[le16toh(sb->s_ninode)] = htole16(ino); + sb->s_ninode = htole16(le16toh(sb->s_ninode) + 1); + if (le16toh(sb->s_ninode) >= V7_NICINOD) + break; + } + ino++; + } + } + if (le16toh(sb->s_ninode) > 0) + goto loop; + return 0; +} + +/*++ + * i f r e e + * + * Mark an inode as free on a UNIX V7 file system. + * + * Inputs: + * + * mount - pointer to mounted file system descriptor + * ino - inode number to be released + * + * Outputs: + * + * The super block and/or the on-disk inode space may be updated. + * + * Returns: + * + * None + * + --*/ + +static void ifree( + struct mountedFS *mount, + v7_ino_t ino +) +{ + struct UNIXV7data *data = &mount->unixv7data; + struct v7_filsys *sb = (struct v7_filsys *)&data->superblk; + struct v7_dinode di; + + memset(&di, 0, sizeof(di)); + + unixv7WriteInode(mount, ino, &di); + + if (le16toh(sb->s_ninode) < V7_NICINOD) { + sb->s_inode[le16toh(sb->s_ninode)] = htole16(ino); + sb->s_ninode = htole16(le16toh(sb->s_ninode) + 1); + sb->s_fmod = 1; + } +} +/*++ + * u p d a t e + * + * Update the on-disk copy of the super block if it has been marked as + * "modified". + * + * Inputs: + * + * mount - pointer to a mounted file system descriptor + * + * Outputs: + * + * None + * + * Returns: + * + * None + * + --*/ +static void update( + struct mountedFS *mount +) +{ + struct UNIXV7data *data = &mount->unixv7data; + struct v7_filsys *sb = (struct v7_filsys *)&data->superblk; + + if (sb->s_fmod != 0) { + sb->s_fmod = 0; + if (unixv7WriteBlock(mount, V7_SUPERBLK, sb) == 0) + sb->s_fmod = 1; + } +} + +/*++ + * a d d C o m p o n e n t + * + * Add a component to a directory. Note that the caller must check that + * the component does not already exist before calling this routine. + * + * Inputs: + * + * mount - pointer to a mounted file system descriptor + * dino - inode number of the specified directory + * ino - inode number for the component to be added + * name - pointer to component name + * isdirect - 1 if component is a directory, 0 otherwise + * + * Outputs: + * + * None + * + * Returns: + * + * 1 is successful, 0 on failure + * + --*/ +static int addComponent( + struct mountedFS *mount, + v7_ino_t dino, + v7_ino_t ino, + char *name, + int isdirect +) +{ + struct v7_dinode inode; + char cname[V7_DIRSIZ]; + + memset(cname, 0, sizeof(cname)); + strncpy(cname, name, V7_DIRSIZ); + + if (unixv7ReadInode(mount, dino, &inode)) { + if ((le16toh(inode.di_mode) & V7_IFMT) == V7_IFDIR) { + off_t offset = 0; + uint32_t lbn = 0xFFFFFFFF, pbn; + + for (;;) { + uint8_t buf[V7_BLOCKSIZE]; + struct v7_direct *dp; + int upd = 0; + + if (lbn != (offset >> V7_BSHIFT)) { + lbn = offset >> V7_BSHIFT; + if ((pbn = bmap(mount, &inode, ino, lbn, V7_WRITE)) == 0) { + ERROR("%s: Error mapping lbn %u on inode %u\n", command, lbn, ino); + return 0; + } + if (unixv7ReadBlock(mount, pbn, buf) == 0) + return 0; + } + dp = (struct v7_direct *)&buf[offset & V7_BMASK]; + + if (dp->d_ino == 0) { + dp->d_ino = htole16(ino); + memcpy(dp->d_name, cname, V7_DIRSIZ); + offset += sizeof(struct v7_direct); + + if (unixv7WriteBlock(mount, pbn, buf) == 0) + return 0; + + if (offset > V7_LONG(le32toh(inode.di_size))) { + inode.di_size = htole32(V7_LONG(offset)); + upd = 1; + } + + if (isdirect) { + inode.di_nlink = htole16(le16toh(inode.di_nlink) + 1); + upd = 1; + } + + if (upd) { + if (unixv7WriteInode(mount, dino, &inode) == 0) + return 0; + } + return 1; + } + offset += sizeof(struct v7_direct); + } + } else ERROR("%s: inode %u is not a directory\n", command, ino); + } + return 0; +} + +/*++ + * e m p t y D i r e c t o r y + * + * Allocate a disk block and create an empty directory on it. + * + * Inputs: + * + * mount - pointer to a mounted file system descriptor + * ino - inode number of the directory + * pino - inode number of the directory's parent + * + * Outputs: + * + * The empty directory will be written in an available disk block. + * + * Returns: + * + * Disk block allocated for the directory, 0 on failure + * + --*/ +static v7_daddr_t emptyDirectory( + struct mountedFS *mount, + v7_ino_t ino, + v7_ino_t pino +) +{ + uint8_t buf[V7_BLOCKSIZE]; + struct v7_direct *dip = (struct v7_direct *)buf; + v7_daddr_t bno; + + if ((bno = balloc(mount)) != 0) { + memset(buf, 0, sizeof(buf)); + + dip->d_ino = htole16(ino); + strcpy(dip->d_name, "."); + dip++; + dip->d_ino = htole16(pino); + strcpy(dip->d_name, ".."); + + if (unixv7WriteBlock(mount, bno, buf) == 0) + return 0; + } + return bno; +} + +/*++ + * l o o k u p C o m p o n e n t + * + * Lookup a component (directory or file) in the specified directory. This + * routine does not support wildcards. + * + * Inputs: + * + * mount - pointer to a mounted file system descriptor + * ino - inode number of the specified directory + * name - pointer to component name + * cino - return inode number of component here + * + * Outputs: + * + * None + * + * Returns: + * + * 1 if component found + * 0 if component not found + * -1 if error during lookup + * + --*/ +static int lookupComponent( + struct mountedFS *mount, + v7_ino_t ino, + char *name, + v7_ino_t *cino +) +{ + struct v7_dinode inode; + char cname[V7_DIRSIZ]; + + memset(cname, 0, sizeof(cname)); + strncpy(cname, name, V7_DIRSIZ); + + if (unixv7ReadInode(mount, ino, &inode)) { + if ((le16toh(inode.di_mode) & V7_IFMT) == V7_IFDIR) { + off_t offset = 0; + uint32_t lbn = 0xFFFFFFFF, pbn; + + while (offset < V7_LONG(le32toh(inode.di_size))) { + uint8_t buf[V7_BLOCKSIZE]; + struct v7_direct *dp; + + if (lbn != (offset >> V7_BSHIFT)) { + lbn = offset >> V7_BSHIFT; + if ((pbn = bmap(mount, &inode, ino, lbn, V7_READ)) == 0) { + ERROR("%s: Error mapping lbn %u on inode %u\n", command, lbn, ino); + return -1; + } + if (unixv7ReadBlock(mount, pbn, buf) == 0) + return -1; + } + dp = (struct v7_direct *)&buf[offset & V7_BMASK]; + + if (dp->d_ino != 0) { + if (memcmp(cname, dp->d_name, V7_DIRSIZ) == 0) { + *cino = le16toh(dp->d_ino); + return 1; + } + } + offset += sizeof(struct v7_direct); + } + return 0; + } else ERROR("%s: inode %u is not a directory\n", command, ino); + } + return -1; +} + +/*++ + * i s D i r e c t o r y E m p t y + * + * Check if a directory is empty (i.e. contains only ". and ".." entries). + * + * Inputs: + * + * mount - pointer to a mounted file system descriptor + * inode - pointer to the on-disk inode for the directory + * ino - inode number of the specified directory + * + * Outputs: + * + * None + * + * Returns: + * + * 1 if directory is empty, 0 otherwise + * + --*/ +static int isDirectoryEmpty( + struct mountedFS *mount, + struct v7_dinode *inode, + v7_ino_t ino +) +{ + off_t offset = 0; + uint32_t lbn = 0xFFFFFFFF, pbn; + + while (offset < V7_LONG(le32toh(inode->di_size))) { + uint8_t buf[V7_BLOCKSIZE]; + struct v7_direct *dp; + + if (lbn != (offset >> V7_BSHIFT)) { + lbn = offset >> V7_BSHIFT; + if ((pbn = bmap(mount, inode, ino, lbn, V7_READ)) == 0) { + ERROR("%s: Error mapping lbn %u on inode %u\n", command, lbn, ino); + return 0; + } + if (unixv7ReadBlock(mount, pbn, buf) == 0) + return 0; + } + dp = (struct v7_direct *)&buf[offset & V7_BMASK]; + + if (dp->d_ino != 0) { + if ((strcmp(dp->d_name, ".") != 0) && (strcmp(dp->d_name, "..") != 0)) + return 0; + } + offset += sizeof(struct v7_direct); + } + return 1; +} + +/*++ + * u n l i n k C o m p o n e n t + * + * Lookup a component in the specified directory, remove the directory + * entry, release any resources (disk block(s) and inode) and update any + * reference counts. If the component is a directory and has more than just + * the "." and ".." entries present fail the operation. + * + * Inputs: + * + * mount - pointer to a mounted file system descriptor + * ino - inode number of the specified directory + * name - pointer to component name + * + * Outputs: + * + * None + * + * Returns: + * + * 1 if component found and removed + * 0 if component not found + * -1 if error during lookup + * + --*/ +static int unlinkComponent( + struct mountedFS *mount, + v7_ino_t ino, + char *name +) +{ + struct v7_dinode inode; + char cname[V7_DIRSIZ]; + + memset(cname, 0, sizeof(cname)); + strncpy(cname, name, V7_DIRSIZ); + + if (unixv7ReadInode(mount, ino, &inode)) { + if ((le16toh(inode.di_mode) & V7_IFMT) == V7_IFDIR) { + off_t offset = 0; + uint32_t lbn = 0xFFFFFFFF, pbn; + + while (offset < V7_LONG(le32toh(inode.di_size))) { + uint8_t buf[V7_BLOCKSIZE]; + struct v7_direct *dp; + + if (lbn != (offset >> V7_BSHIFT)) { + lbn = offset >> V7_BSHIFT; + if ((pbn = bmap(mount, &inode, ino, lbn, V7_READ)) == 0) { + ERROR("%s: Error mapping lbn %u on inode %u\n", command, lbn, ino); + return -1; + } + if (unixv7ReadBlock(mount, pbn, buf) == 0) + return -1; + } + dp = (struct v7_direct *)&buf[offset & V7_BMASK]; + + if (dp->d_ino != 0) { + if (memcmp(cname, dp->d_name, V7_DIRSIZ) == 0) { + struct v7_dinode inode2; + + if (unixv7ReadInode(mount, le16toh(dp->d_ino), &inode2) == 0) + return -1; + + switch (le16toh(inode2.di_mode) & V7_IFMT) { + case V7_IFDIR: + if (!isDirectoryEmpty(mount, &inode2, le16toh(dp->d_ino))) + return -1; + + inode.di_nlink = htole16(le16toh(inode.di_nlink) - 1); + if (unixv7WriteInode(mount, ino, &inode) == 0) + return -1; + /* FALLTHROUGH */ + + case V7_IFREG: + brel(mount, &inode2); + /* FALLTHROUGH */ + + default: + inode2.di_nlink = htole16(le16toh(inode2.di_nlink) - 1); + if (inode2.di_nlink == 0) { + memset(&inode2, 0, sizeof(inode2)); + if (unixv7WriteInode(mount, le16toh(dp->d_ino), &inode2) == 0) + return -1; + + ifree(mount, le16toh(dp->d_ino)); + + dp->d_ino = 0; + if (unixv7WriteBlock(mount, pbn, buf) == 0) + return -1; + } + break; + } + return 1; + } + } + offset += sizeof(struct v7_direct); + } + return 0; + } else ERROR("%s: inode %u is not a directory\n", command, ino); + } + return -1; +} + +/*++ + * w a l k T r e e + * + * Recusively walk a UNIX V7 file tree starting at the root. When a match + * is found, a supplied action routine is called. The tree walk may support + * regex-style wildcard characters so the action routine may be called more + * than once for each call to walkTree(). + * + * Inputs: + * + * mount - pointer to a mounted file system descriptor + * spec - pointer to the file specifcation block + * idx - current index in "spec" - start at 0 + * ino - directory inode number to start walk + * action - action routine to be called when a full match is + * found + * arg - argument to be supplied to action() + * + * Outputs: + * + * None + * + * Returns: + * + * None + * + --*/ +static void walkTree( + struct mountedFS *mount, + struct unixv7FileSpec *spec, + int idx, + v7_ino_t ino, + void (*action)(struct mountedFS *, struct unixv7FileSpec *, char *, v7_ino_t, v7_ino_t, uintptr_t), + uintptr_t arg +) +{ + struct v7_dinode inode; + + if (unixv7ReadInode(mount, ino, &inode)) { + if ((le16toh(inode.di_mode) & V7_IFMT) == V7_IFDIR) { + off_t offset = 0; + uint32_t lbn = 0xFFFFFFFF, pbn; + + /* + * If we expect to create a new file, call the action routine now to + * allow it to create the final component. + */ + if ((spec->access == UNIXV7_A_NEW) && (idx == (spec->depth - 1))) { + (*action)(mount, spec, spec->comp[idx], ino, 0, arg); + return; + } + + while (offset < V7_LONG(le32toh(inode.di_size))) { + uint8_t buf[V7_BLOCKSIZE]; + struct v7_direct *dp; + + if (lbn != (offset >> V7_BSHIFT)) { + lbn = offset >> V7_BSHIFT; + if ((pbn = bmap(mount, &inode, ino, lbn, V7_READ)) == 0) { + ERROR("Error mapping lbn %u on inode %u\n", lbn, ino); + return; + } + if (unixv7ReadBlock(mount, pbn, buf) == 0) + return; + } + dp = (struct v7_direct *)&buf[offset & V7_BMASK]; + + if (dp->d_ino != 0) { + char name[V7_DIRSIZ + 1]; + v7_ino_t fino = le16toh(dp->d_ino); + + memset(name, 0, sizeof(name)); + memcpy(name, dp->d_name, V7_DIRSIZ); + + switch (spec->wildcard) { + case UNIXV7_M_NONE: + if (strcmp(spec->comp[idx], name) == 0) { + if (idx == (spec->depth - 1)) + (*action)(mount, spec, name, ino, fino, arg); + else walkTree(mount, spec, idx + 1, fino, action, arg); + } + break; + + case UNIXV7_M_ALLOW: + if (fnmatch(spec->comp[idx], name, FNM_PERIOD) == 0) { + if (idx == (spec->depth - 1)) + (*action)(mount, spec, name, ino, fino, arg); + else walkTree(mount, spec, idx + 1, fino, action, arg); + } + break; + } + } + offset += sizeof(struct v7_direct); + } + } + } +} + +/*++ + * u n i x v 7 P a r s e F i l e s p e c + * + * Parse a character string representing a UNIX V7 file specification. The + * file specification is split into individual components separated by one + * of more '/' characters. If wildcard characters are not allowed, each + * component may not exceed 14 characters in length. + * + * Inputs: + * + * ptr - pointer to the file specification string + * spec - pointer to the file specifcation block + * wildcard - wildcard processing options: + * 0 (UNIXV7_M_NONE) - wildcards not allowed + * 1 (UNIXV7_M_ALLOW) - wildcards allowed + * access - access mode options: + * 0 (UNIXV7_A_EXIST) - expects an existin file + * 1 (UNIXV7_A_NEW) - expects to create a new file + * + * Outputs: + * + * The file specification block will be filled with the file information. + * + * Returns: + * + * 1 if parse successful, 0 otherwise + * + --*/ +int unixv7ParseFilespec( + char *ptr, + struct unixv7FileSpec *spec, + char wildcard, + char access +) +{ + char *p; + + memset(spec, 0, sizeof(struct unixv7FileSpec)); + + spec->wildcard = wildcard; + spec->access = access; + + while ((ptr != NULL) && (*ptr != '\0')) { + /* + * Remove any leading '/' characters. + */ + while (*ptr == '/') + ptr++; + + if ((p = strchr(ptr, '/')) != NULL) + *p++ = '\0'; + + if (wildcard == UNIXV7_M_NONE) { + if (strlen(ptr) > V7_DIRSIZ) + return 0; + } + + spec->comp[spec->depth++] = ptr; + ptr = p; + } + return 1; +} + +/*++ + * u n i x v 7 P r o t e c t i o n + * + * Build a protection string based on the mode value from an inode. + * + * Inputs: + * + * prot - pointer to buffer to receive the protection string + * type - type of file (first character of protection) + * mode - mode value from inode + * + * Outputs: + * + * The protection string is modified + * + * Returns: + * + * None + * + --*/ +void unixv7Protection( + char *prot, + char type, + v7_ushort mode +) +{ + prot[0] = type; + prot[1] = '\0'; + + strcat(prot, rwx[(mode & (V7_IREAD|V7_IWRITE|V7_IEXEC)) >> 6]); + strcat(prot, rwx[(mode & ((V7_IREAD|V7_IWRITE|V7_IEXEC) >> 3)) >> 3]); + strcat(prot, rwx[(mode & ((V7_IREAD|V7_IWRITE|V7_IEXEC) >> 6))]); + + /* + * Check for special values + */ + if (((mode & V7_IEXEC) != 0) && ((mode & V7_ISUID) != 0)) + prot[3] = 's'; + if (((mode & (V7_IEXEC >> 3)) != 0) && ((mode & V7_ISGID) != 0)) + prot[6] = 's'; + if ((mode & V7_ISVTX) != 0) + prot[9] = 't'; +} + +/*++ + * u n i x v 7 D e v i c e + * + * Build a device major/minor number based on the address in an inode. + * + * Inputs: + * + * device - pointer to buffer to receive the device string + * inode - pointer to the on-disk format inode + * + * Outputs: + * + * The device string is modified + * + * Returns: + * + * None + * + --*/ +void unixv7Device( + char *device, + struct v7_dinode *inode +) +{ + uint8_t major = (uint8_t)inode->di_addr[2]; + uint8_t minor = (uint8_t)inode->di_addr[1]; + + sprintf(device, "%3u,%-3u", major, minor); +} + +/*++ + * u n i x v 7 T i m e s t a m p + * + * The Unix V7 filesystem uses a 32-bit signed timestamp which will overflow + * in Jan 2038. Return a suitable timestamp for the current date/time unless + * the timestamp would overflow in which case the maximum value will be + * returned. This is made more complicated because it depends on the + * capabilities of the system running this code which may use 64-bit signed, + * 32-bit signed or 32-bit unsigned time values. + * + * Inputs: + * + * None + * + * Outputs: + * + * ... + * + * Returns: + * + * A suitable 32-bit positive signed timestamp value + * + --*/ +v7_time_t unixv7Timestamp(void) +{ + time_t now = time(NULL); + + if (sizeof(time_t) == 8) { + if (now > 0x7FFFFFFF) + return 0x7FFFFFFF; + return now & 0xFFFFFFFF; + } + + if (sizeof(time_t) == 4) { + if ((uint32_t)now > 0x7FFFFFFF) + return now & 0x7FFFFFFF; + return now; + } + return 0; +} + +/*++ + * d u m p D i r e c t o r y + * + * Dump internal information about the contents of a Unix V7 directory. + * + * Inputs: + * + * mount - pointer to a mounted file system descriptor + * ino - inode number for the directory + * indent - indent level for output + * + * Outputs: + * + * None + * + * Returns: + * + * None + * + --*/ +static void dumpDirectory( + struct mountedFS *mount, + v7_ino_t ino, + int indent +) +{ + struct v7_dinode inode; + + if (unixv7ReadInode(mount, ino, &inode)) { + if ((le16toh(inode.di_mode) & V7_IFMT) == V7_IFDIR) { + off_t offset = 0; + uint32_t lbn = 0xFFFFFFFF, pbn; + + while (offset < V7_LONG(le32toh(inode.di_size))) { + uint8_t buf[V7_BLOCKSIZE]; + struct v7_direct *dp; + + if (lbn != (offset >> V7_BSHIFT)) { + lbn = offset >> V7_BSHIFT; + if ((pbn = bmap(mount, &inode, ino, lbn, V7_READ)) == 0) { + ERROR("Error mapping lbn %u on inode %u\n", lbn, ino); + return; + } + if (unixv7ReadBlock(mount, pbn, buf) == 0) + return; + } + dp = (struct v7_direct *)&buf[offset & V7_BMASK]; + + if (dp->d_ino != 0) { + struct v7_dinode finode; + + if (unixv7ReadInode(mount, le16toh(dp->d_ino), &finode)) { + char prot[16], name[V7_DIRSIZ + 1], other[32]; + + memset(name, 0, sizeof(name)); + memcpy(name, dp->d_name, V7_DIRSIZ); + + other[0] = '\0'; + + switch (le16toh(finode.di_mode) & V7_IFMT) { + case V7_IFCHR: + unixv7Protection(prot, 'c', le16toh(finode.di_mode)); + unixv7Device(other, &finode); + break; + + case V7_IFBLK: + unixv7Protection(prot, 'b', le16toh(finode.di_mode)); + unixv7Device(other, &finode); + break; + + case V7_IFMPC: + unixv7Protection(prot, 'm', le16toh(finode.di_mode)); + break; + + case V7_IFMPB: + unixv7Protection(prot, 'M', le16toh(finode.di_mode)); + break; + + case V7_IFDIR: + unixv7Protection(prot, 'd', le16toh(finode.di_mode)); + break; + + case V7_IFLNK: + unixv7Protection(prot, 'l', le16toh(finode.di_mode)); + break; + + case V7_IFREG: + unixv7Protection(prot, '-', le16toh(finode.di_mode)); + sprintf(other, "%u", V7_LONG(le32toh(finode.di_size))); + break; + + default: + printf("Unknown mode %o\n", le16toh(finode.di_mode)); + return; + } + printf("%*s%-6u %s %-6d %-14s %s\n", + indent, "", le16toh(dp->d_ino), prot, + le16toh(finode.di_nlink), name, other); + + /* + * Recursively display directories. + */ + if ((le16toh(finode.di_mode) & V7_IFMT) == V7_IFDIR) { + if ((strcmp(name, ".") != 0) && (strcmp(name, "..") != 0)) + dumpDirectory(mount, le16toh(dp->d_ino), indent + 2); + } + } else { + ERROR("Error reading file inode (%u)\n", le16toh(dp->d_ino)); + return; + } + } + offset += sizeof(struct v7_direct); + } + } + return; + } + ERROR("Error reading directory inode (%u)\n", ino); +} + +/*++ + * b a d b l o c k + * + * Perform some sanity checks on a data block address: + * + * - Check for duplicate block address (optional) + * - Check if block address is after the i-node space and + * within the disk size as specified in the superblock + * + * Inputs: + * + * sb - pointer to the super block + * block - logical block address being checked + * map - if not NULL, pointer to bitmap to check for + * duplicate block addresses + * + * Outputs: + * + * None + * + * Returns: + * + * 1 if block does not pass sanity checks, 0 otherwise + * + --*/ +static int badblock( + struct v7_filsys *sb, + uint32_t block, + uint32_t *map +) +{ + /* + * The -f switch may be used to bypass the duplicate block check. + */ + if (!SWISSET('f') && (map != NULL)) { + int offset = block / 32, bit = block & 31; + + if ((map[offset] & (1 << bit)) != 0) { + ERROR("Duplicate free block (%u)\n", block); + return 1; + } + map[offset] |= 1 << bit; + } + + if ((block < le16toh(sb->s_isize)) || + (block >= V7_ULONG(le32toh(sb->s_fsize)))) { + ERROR("Invalid free block (%u)\n", block); + return 1; + } + return 0; +} + +/*++ + * v a l i d a t e F r e e B l o c k + * + * Validate a free block and, if it is part of the free list, any block that + * it points to are valid. + * + * Inputs: + * + * mount - pointer to a mounted file system descriptor + * sb - pointer to the super block + * map - bitmap to detect duplicate free blocks + * block - logical block address being checked + * onlist - 1 if this block is part of the free list, 0 otherwise + * + * Outputs: + * + * None + * + * Returns: + * + * 1 if this block and all blocks it point at are valid, 0 otherwise + * + --*/ +static int validateFreeBlock( + struct mountedFS *mount, + struct v7_filsys *sb, + uint32_t *map, + uint32_t block, + int onlist +) +{ + uint8_t buf[V7_BLOCKSIZE]; + struct v7_fblk *fb = (struct v7_fblk *)buf; + uint32_t nxtblock; + + if (!badblock(sb, block, map)) { + if (onlist) { + if (unixv7ReadBlock(mount, block, buf) != 0) { + int16_t i; + + if (le16toh(fb->df_nfree) > 0) + for (i = le16toh(fb->df_nfree) - 1; i >= 0; i--) { + if ((nxtblock = V7_ULONG(le32toh(fb->df_free[i]))) == 0) + break; + + if (validateFreeBlock(mount, sb, map, nxtblock, i == 0) == 0) + return 0; + } + return 1; + } + } else return 1; + } + return 0; +} + +/*++ + * v a l i d a t e + * + * Validate the integrity of a Unix V7 file system. Unix V7 file systems do + * not include any form of signature on the disk so we have to run a set of + * heuristics to verify the integrity of the file system. + * + * Inputs: + * + * mount - pointer to a mounted file system descriptor + * dev - pointer to specific device type (NULL if none) + * + * Outputs: + * + * The mount point specific buffer will be overwritten. + * + * Returns: + * + * 1 if file system is valid, 0 otherwise + * + --*/ +static int validate( + struct mountedFS *mount, + struct UNIXV7device *dev +) +{ + struct UNIXV7data *data = &mount->unixv7data; + struct v7_filsys *sb = (struct v7_filsys *)&data->superblk; + struct v7_dinode inode; + uint32_t pbn, block, *map = NULL; + uint8_t buf[V7_BLOCKSIZE]; + struct v7_direct *dp = (struct v7_direct *)buf; + + if (unixv7ReadBlock(mount, V7_SUPERBLK, sb) != 0) { + /* + * If an explicit device is provided we can perform an extra check on the + * super block, otherwise we just have to trust the super block to provide + * the size of the device. + */ + if (dev != NULL) + if (V7_ULONG(le32toh(sb->s_fsize)) > dev->diskSize) { + ERROR("Superblock disk size too large for \"%s\"\n", dev->name); + return 0; + } + + /* + * Make sure the values in the superblock are valid among themselves. + */ + if (le16toh(sb->s_isize) < V7_ULONG(le32toh(sb->s_fsize))) { + if (le16toh(sb->s_nfree) <= V7_NICFREE) { + int16_t i; + + /* + * s_nfree <= 0 indicates that all available blocks are in use + * so we have nothing to check. + */ + if (le16toh(sb->s_nfree) > 0) { + size_t mapsize = (V7_ULONG(le32toh(sb->s_fsize)) + 31) / 8; + + if ((map = malloc(mapsize)) == NULL) { + ERROR("Panic: Memory allocation failure\n"); + exit(4); + } + memset(map, 0, mapsize); + + for (i = le16toh(sb->s_nfree) - 1; i >= 0; i--) { + if ((block = V7_LONG(le32toh(sb->s_free[i]))) == 0) + break; + + if (validateFreeBlock(mount, sb, map, block, i == 0) == 0) + goto invalid; + } + } + + /* + * Check the free inodes in the superblock. + */ + if (le16toh(sb->s_ninode) > 0) { + for (i = le16toh(sb->s_ninode); i >= 0; i--) { + if (le16toh(sb->s_inode[i]) >= + ((le16toh(sb->s_isize) - 2) * V7_INOPB)) { + ERROR("Bad inode number in superblock (%u)\n", + le16toh(sb->s_inode[i])); + return 0; + } + if (le16toh(sb->s_inode[i]) == V7_ROOTINO) { + ERROR("Root inode is free in superblock\n"); + return 0; + } + } + } + + /* + * Check root inode for validity + */ + if (unixv7ReadInode(mount, V7_ROOTINO, &inode) == 0) { + ERROR("Unable to read root inode (2)\n"); + return 0; + } + + if ((inode.di_mode & V7_IFMT) != V7_IFDIR) { + ERROR("Root inode is not a directory\n"); + return 0; + } + + /* + * Read the first block of the root directory and check that the + * first 2 entries are for "." and "..". + */ + V7_GET3ADDR(&inode.di_addr[0], pbn); + + if (pbn == 0) { + ERROR("Root directory starts at block 0\n"); + return 0; + } + + if (unixv7ReadBlock(mount, pbn, buf) == 0) { + ERROR("Unable to read first block of root directory\n"); + return 0; + } + + if ((le16toh(dp->d_ino) != V7_ROOTINO) || + (strcmp(dp->d_name, ".") != 0)) { + ERROR("Malformed first directory entry\n"); + return 0; + } + + dp++; + + if ((le16toh(dp->d_ino) != V7_ROOTINO) || + (strcmp(dp->d_name, "..") != 0)) { + ERROR("Malformed second directory entry\n"); + return 0; + } + data->blocks = V7_ULONG(le32toh(sb->s_fsize)); + + if (!quiet) { + char fpack[8], fname[8]; + + memset(fpack, 0, sizeof(fpack)); + memset(fname, 0, sizeof(fname)); + memcpy(fpack, sb->s_fpack, sizeof(sb->s_fpack)); + memcpy(fname, sb->s_fname, sizeof(sb->s_fname)); + + printf("Pack name: %6s, File system name: %6s, Total blocks: %u\n", + fpack, fname, data->blocks); + } + if (map != NULL) + free(map); + return 1; + } else ERROR("Too many free blocks\n"); + } else ERROR("Superblock i-list larger than disk\n"); + } + invalid: + if (map != NULL) + free(map); + + return 0; +} + +/*++ + * u n i x v 7 D i s p l a y D i r + * + * Display information about a file or directory. + * + * Inputs: + * + * mount - pointer to a mounted file system descriptor + * spec - pointer to the file specification block (unused) + * name - last component of file/directory name + * dino - inode number of parent directory (unused) + * ino - inode number of the file/directory + * arg - unused argument + * + * Outputs: + * + * None + * + * Returns: + * + * None + * + --*/ +static void unixv7DisplayDir( + struct mountedFS *mount, + struct unixv7FileSpec *UNUSED(spec), + char *name, + v7_ino_t UNUSED(dino), + v7_ino_t ino, + uintptr_t UNUSED(arg) +) +{ + struct v7_dinode inode; + char prot[16], fname[V7_DIRSIZ + 1], other[32]; + + if (unixv7ReadInode(mount, ino, &inode)) { + printf("%s:\n", name); + + if ((le16toh(inode.di_mode) & V7_IFMT) == V7_IFDIR) { + off_t offset = 0; + uint32_t lbn = 0xFFFFFFFF, pbn; + + while (offset < V7_LONG(le32toh(inode.di_size))) { + uint8_t buf[V7_BLOCKSIZE]; + struct v7_direct *dp; + + if (lbn != (offset >> V7_BSHIFT)) { + lbn = offset >> V7_BSHIFT; + if ((pbn = bmap(mount, &inode, ino, lbn, V7_READ)) == 0) { + ERROR("Error mapping lbn %u on inode %u\n", lbn, ino); + return; + } + if (unixv7ReadBlock(mount, pbn, buf) == 0) + return; + } + dp = (struct v7_direct *)&buf[offset & V7_BMASK]; + + if (dp->d_ino != 0) { + memset(fname, 0, sizeof(fname)); + memcpy(fname, dp->d_name, V7_DIRSIZ); + + other[0] = '\0'; + + if ((strcmp(fname, ".") != 0) && (strcmp(fname, "..") != 0)) { + if (SWISSET('f')) { + struct v7_dinode finode; + + if (unixv7ReadInode(mount, le16toh(dp->d_ino), &finode)) { + switch (le16toh(finode.di_mode) & V7_IFMT) { + case V7_IFCHR: + unixv7Protection(prot, 'c', le16toh(finode.di_mode)); + unixv7Device(other, &finode); + break; + + case V7_IFBLK: + unixv7Protection(prot, 'b', le16toh(finode.di_mode)); + unixv7Device(other, &finode); + break; + + case V7_IFMPC: + unixv7Protection(prot, 'm', le16toh(finode.di_mode)); + break; + + case V7_IFMPB: + unixv7Protection(prot, 'M', le16toh(finode.di_mode)); + break; + + case V7_IFDIR: + unixv7Protection(prot, 'd', le16toh(finode.di_mode)); + break; + + case V7_IFLNK: + unixv7Protection(prot, 'l', le16toh(finode.di_mode)); + break; + + case V7_IFREG: + unixv7Protection(prot, '-', le16toh(finode.di_mode)); + sprintf(other, "%u", V7_LONG(le32toh(finode.di_size))); + break; + + default: + printf("Unknown mode %o\n", le16toh(finode.di_mode)); + return; + } + printf(" %s %-6d %-14s %s\n", prot, le16toh(finode.di_nlink), + fname, other); + } + } else printf("%s\n", fname); + } + } + offset += sizeof(struct v7_direct); + } + } else { + if (SWISSET('f')) { + switch (le16toh(inode.di_mode) & V7_IFMT) { + case V7_IFCHR: + unixv7Protection(prot, 'c', le16toh(inode.di_mode)); + unixv7Device(other, &inode); + break; + + case V7_IFBLK: + unixv7Protection(prot, 'b', le16toh(inode.di_mode)); + unixv7Device(other, &inode); + break; + + case V7_IFMPC: + unixv7Protection(prot, 'm', le16toh(inode.di_mode)); + break; + + case V7_IFMPB: + unixv7Protection(prot, 'M', le16toh(inode.di_mode)); + break; + + case V7_IFDIR: + unixv7Protection(prot, 'd', le16toh(inode.di_mode)); + break; + + case V7_IFLNK: + unixv7Protection(prot, 'l', le16toh(inode.di_mode)); + break; + + case V7_IFREG: + unixv7Protection(prot, '-', le16toh(inode.di_mode)); + sprintf(other, "%u", V7_LONG(le32toh(inode.di_size))); + break; + + default: + printf("Unknown mode %o\n", le16toh(inode.di_mode)); + return; + } + printf(" %s %-6d %-14s %s\n", prot, le16toh(inode.di_nlink), + name, other); + } else printf("%s\n", name); + } + printf("\n"); + } +} + +/*++ + * u n i x v 7 O p e n R + * + * Callback routine from walkTree() when opening a file for reading. + * + * Inputs: + * + * mount - pointer to a mounted file system descriptor + * spec - pointer to the file specification block (unused) + * name - last component of file/directory name + * dino - inode number of parent directory + - inode number of the file/directory + * arg - open file descriptor passed here + * + * Outputs: + * + * None + * + * Returns: + * + * None + * + --*/ +static void unixv7OpenR( + struct mountedFS *mount, + struct unixv7FileSpec *UNUSED(spec), + char *name, + v7_ino_t dino, + v7_ino_t ino, + uintptr_t arg +) +{ + struct unixv7OpenFile *file = (struct unixv7OpenFile *)arg; + + if (unixv7ReadInode(mount, ino, &file->inode)) { + /* + * Allocate local buffer space for the file + */ + if ((file->buffer = malloc(V7_BLOCKSIZE)) != NULL) { + strcpy(file->name, name); + file->parent = dino; + file->ino = ino; + file->mode = M_RD; + file->mount = mount; + file->block = 0xFFFFFFFF; + } + } +} + +/*++ + * u n i x v 7 O p e n W + * + * Callback routine from walkTree() when opening a file for writing. + * + * Inputs: + * + * mount - pointer to a mounted file system descriptor + * spec - pointer to the file specification block + * name - last component of file/directory name + * dino - inode number of parent directory + * ino - inode number of the file/directory + * arg - open file descriptor passed here + * + * Outputs: + * + * None + * + * Returns: + * + * None + * + --*/ +static void unixv7OpenW( + struct mountedFS *mount, + struct unixv7FileSpec *UNUSED(spec), + char *name, + v7_ino_t dino, + v7_ino_t UNUSED(ino), + uintptr_t arg +) +{ + struct unixv7OpenFile *file = (struct unixv7OpenFile *)arg; + v7_ino_t temp; + + /* + * We do not support superceding an existing file so check if the target + * file already exists. + */ + switch (lookupComponent(mount, dino, name, &temp)) { + case 1: + ERROR("%s: \"%s\" already exists\n", command, name); + return; + + case 0: + break; + + default: + return; + } + + /* + * Allocate local buffer space for the file + */ + if ((file->buffer = malloc(V7_BLOCKSIZE)) != NULL) { + if ((file->ino = ialloc(mount, &file->inode, V7_IFREG)) != 0) { + strcpy(file->name, name); + file->parent = dino; + file->mode = M_WR; + file->mount = mount; + file->block = 0xFFFFFFFF; + } else { + ERROR("%s: Failed to allocate an inode\n", command); + free(file->buffer); + } + } else ERROR("%s: Failed to allocate file buffer\n", command); +} + +/*++ + * u n i x v 7 R e a d B y t e s + * + * Read a sequence of bytes from an open file. + * + * Inputs: + * + * file - pointer to open file descriptor + * buf - pointer to 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 unixv7ReadBytes( + struct unixv7OpenFile *file, + char *buf, + int len +) +{ + struct mountedFS *mount = file->mount; + int count = 0, cpylen, bufoffset; + + if (file->offset < V7_LONG(le32toh(file->inode.di_size))) { + off_t available = V7_LONG(le32toh(file->inode.di_size)) - file->offset; + + if (available < len) + len = available; + + while (len) { + if (file->block != (file->offset >> V7_BSHIFT)) { + uint32_t pbn; + + file->block = file->offset >> V7_BSHIFT; + if ((pbn = bmap(mount, &file->inode, file->ino, file->block, V7_READ)) == 0) { + ERROR("%s: Error mapping lbn %u on inode %u for read\n", + command, file->block, file->ino); + return 0; + } + if (unixv7ReadBlock(mount, pbn, file->buffer) == 0) + return 0; + } + bufoffset = file->offset & (V7_BLOCKSIZE - 1); + cpylen = V7_BLOCKSIZE - bufoffset; + if (len < cpylen) + cpylen = len; + + memcpy(buf, &file->buffer[bufoffset], cpylen); + count += cpylen; + buf += cpylen; + len -= cpylen; + file->offset += cpylen; + } + return count; + } + return 0; +} + +/*++ + * u n i x v 7 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 a buffer with the data to be written + * len - # of bytes of data to write + * + * Outputs: + * + * None + * + * Returns: + * + * # of bytes written to the file (may be less than "len"), 0 if error + * + --*/ +int unixv7WriteBytes( + struct unixv7OpenFile *file, + char *buf, + int len +) +{ + struct mountedFS *mount = file->mount; + int count = 0, cpylen, bufoffset; + + while (len) { + file->block = file->offset >> V7_BSHIFT; + + bufoffset = file->offset & (V7_BLOCKSIZE - 1); + cpylen = V7_BLOCKSIZE - bufoffset; + + if (len < cpylen) + cpylen = len; + + memcpy(&file->buffer[bufoffset], buf, cpylen); + count += cpylen; + buf += cpylen; + len -= cpylen; + file->offset += cpylen; + + if ((file->offset & (V7_BLOCKSIZE - 1)) == 0) { + uint32_t pbn; + + /* + * The current buffer is full, write the block out to disk. + */ + if ((pbn = bmap(mount, &file->inode, file->ino, file->block, V7_WRITE)) == 0) { + ERROR("%s: Error mapping lbn %u on inode %u for write\n", + command, file->block, file->ino); + return 0; + } + if (unixv7WriteBlock(mount, pbn, file->buffer) == 0) + return 0; + } + } + return count; +} + +/*++ + * u n i x v 7 R e a d B l o c k + * + * Read a block from a UNIX V7 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 unixv7ReadBlock( + struct mountedFS *mount, + unsigned int block, + void *buf +) +{ + struct UNIXV7data *data = &mount->unixv7data; + void *buffer = buf == NULL ? data->buf : buf; + int status; + + if (block >= data->blocks) { + ERROR("Attempt to read block (%u) outside file system \"%s\"\n", + block, mount->name); + return 0; + } + +#ifdef DEBUG + if ((mount->flags & FS_DEBUG) != 0) + fprintf(DEBUGout, ">> %s: (unixv7) Reading logical block %o\n", + mount->name, block); +#endif + + status = FSioReadBlock(mount, data->offset + block, buffer); + + if (status == 0) + ERROR("I/O error on \"%s\"\n", mount->name); + + return status; +} + +/*++ + * u n i x v 7 W r i t e B l o c k + * + * Write a block to a UNIX V7 file system. + * + * Inputs: + * + * mount - pointer to a mounted file system descriptor + * block - logical block # in the range 0 - N + * buf - buffer containing data, if NULL use the mount + * point specific buffer + * + * Outputs: + * + * None + * + * Returns: + * + * 1 if successful, 0 otherwise + * + --*/ +int unixv7WriteBlock( + struct mountedFS *mount, + unsigned int block, + void *buf +) +{ + struct UNIXV7data *data = &mount->unixv7data; + void *buffer = buf == NULL ? data->buf : buf; + int status; + + if (block >= data->blocks) { + ERROR("Attempt to write block (%u) outside file system \"%s\"\n", + block, mount->name); + return 0; + } + +#ifdef DEBUG + if ((mount->flags & FS_DEBUG) != 0) + fprintf(DEBUGout, ">> %s: (unixv7) Writing logical block %o\n", + mount->name, block); +#endif + + status = FSioWriteBlock(mount, data->offset + block, buffer); + + if (status == 0) + ERROR("I/O error on \"%s\"\n", mount->name); + + return status; +} + +/*++ + * u n i x v 7 R e a d I n o d e + * + * Read an inode from a UNIX V7 file system. + * + * Inputs: + * + * mount - pointer to a mounted file system descriptor + * ino - the inode number to be read + * buf - pointer to buffer to receive the data + * + * Outputs: + * + * The buffer will be overwritten by the inode data. + * + * Returns: + * + * 1 if read was successful, 0 otherwise + * + --*/ +int unixv7ReadInode( + struct mountedFS *mount, + v7_ino_t ino, + struct v7_dinode *buf +) +{ + struct UNIXV7data *data = &mount->unixv7data; + off_t offset = (itoff(ino) * sizeof(struct v7_dinode)) + + (data->offset * V7_BLOCKSIZE); + + return FSioReadBlob(mount, offset, sizeof(struct v7_dinode), buf); +} + +/*++ + * u n i x v 7 W r i t e I n o d e + * + * Write an inode to a UNIX V7 file system. + * + * Inputs: + * + * mount - pointer to a mounted file system descriptor + * ino - the inode number to be read + * buf - pointer to buffer with the data + * + * Outputs: + * + * None + * + * Returns: + * + * 1 if write was successful, 0 otherwise + * + --*/ +int unixv7WriteInode( + struct mountedFS *mount, + v7_ino_t ino, + struct v7_dinode *buf +) +{ + struct UNIXV7data *data = &mount->unixv7data; + off_t offset = (itoff(ino) * sizeof(struct v7_dinode)) + + (data->offset * V7_BLOCKSIZE); + + return FSioWriteBlob(mount, offset, sizeof(struct v7_dinode), buf); +} + +/*++ + * u n i x v 7 M o u n t + * + * Verify that the open container file is a UNIX V7 file system. Since UNIX V7 + * does not write an explicit signatture on the disk we can only verify some + * of the super-block entries. + * + * Inputs: + * + * mount - pointer to a mounted file system descriptor + * (not in the mounted file system list) + * + * Outputs: + * + * None + * + * Returns: + * + * -1 if problem with command input values, + * 1 if a valid UNIX V7 file system, + * 0 otherwise + * + --*/ +static int unixv7Mount( + struct mountedFS *mount +) +{ + struct UNIXV7data *data = &mount->unixv7data; + struct stat stat; + unsigned int offset = 0; + + if (fstat(fileno(mount->container), &stat) == 0) { + if (SWISSET('o')) { + char *endptr; + + offset = strtoul(SWGETVAL('o'), &endptr, 10); + + if (offset >= (stat.st_size / V7_BLOCKSIZE)) { + fprintf(stderr, "mount: offset past end of file\n"); + return -1; + } + } + + data->offset = offset; + data->blocks = (int)(stat.st_size / V7_BLOCKSIZE) - offset; + + if (SWISSET('t')) { + struct UNIXV7device *dev = UNIXV7Devices; + char *type = SWGETVAL('t'); + + while (dev->name != NULL) { + if (strcmp(type, dev->name) == 0) + return validate(mount, dev); + dev++; + } + fprintf(stderr, "mount: unknown disk type - \"%s\"\n", type); + return -1; + } + return validate(mount, NULL); + } + return 0; +} + +/*++ + * u n i x v 7 U m o u n t + * + * Unmount the UNIX V7 file system, releasing any storage allocated. + * + * Inputs: + * + * mount - pointer to the mounted file system descriptor + * + * Outputs: + * + * None + * + * Returns: + * + * None + * + --*/ +static void unixv7Umount( + struct mountedFS *UNUSED(mount) +) +{ +} + +/*++ + * u n i x v 7 S i z e + * + * Return the size of a UNIX V7 container file. + * + * Inputs: + * + * None + * + * Outputs: + * + * None + * + * Returns: + * + * Size of the container file in blocks. + * + --*/ +static size_t unixv7Size(void) +{ + struct UNIXV7device *dev = UNIXV7Devices; + + if (SWISSET('t')) { + while (dev->name != NULL) { + if (strcmp(dev->name, SWGETVAL('t')) == 0) + return dev->diskSize; + dev++; + } + } + + if (SWISSET('b')) + return strtoul(SWGETVAL('b'), NULL, 10); + + return UNIXV7Devices[0].diskSize; +} + +/*++ + * u n i x v 7 N e w f s + * + * Create an empty UNIX V7 file system. + * + * Inputs: + * + * mount - pointer to a mounted file system descriptor + * (not in mounted file system list) + * size - the size (in bytes) of the file system (unused) + * + * Outputs: + * + * None + * + * Returns: + * + * 1 if the file system was successfully created, 0 otherwise + * + --*/ +static int unixv7Newfs( + struct mountedFS *mount, + size_t size +) +{ + struct UNIXV7data *data = &mount->unixv7data; + struct UNIXV7device *dev = UNIXV7Devices; + char *type = SWGETVAL('t'); + v7_ushort inodeBlocks; + uint8_t buf[V7_BLOCKSIZE], blk[V7_BLOCKSIZE]; + struct v7_dinode *dp = (struct v7_dinode *)buf; + struct v7_fblk *fp = (struct v7_fblk *)blk; + struct v7_filsys *filsys = (struct v7_filsys *)&data->superblk; + uint16_t inodes, i; + int32_t tfree = 0; + v7_time_t now = unixv7Timestamp(); + v7_daddr_t nextblk, nextfree, rootdir; + + if (SWISSET('t')) { + while (dev->name != NULL) { + if (strcmp(type, dev->name) == 0) + goto found; + + dev++; + } + fprintf(stderr, "newfs: Unknown device type \"%s\"\n", type); + return 0; + } + + if (SWISSET('b')) { + size = strtoul(SWGETVAL('b'), NULL, 10); + goto found2; + } + + found: + size = dev->diskSize; + found2: + inodeBlocks = size / 25; + + data->blocks = size; + + /* + * The '-i' switch allows for overriding the number of blocks to + * be used for inodes. + */ + if (SWISSET('i')) { + char *endptr; + uint16_t n = strtoul(SWGETVAL('i'), &endptr, 10); + + if ((n < V7_INOPB) || (n > 65500)) { + fprintf(stderr, + "newfs: -i parameter must be in the range %d - 65500\n", V7_INOPB); + return 0; + + if ((n / V7_INOPB) > (size / 2)) { + fprintf(stderr, + "newfs: inodes cannot occupy more than 50%% of the disk\n"); + return 0; + } + inodeBlocks = (n + V7_INOPB - 1) / V7_INOPB; + } + } + + inodes = inodeBlocks * V7_INOPB; + + memset(buf, 0, sizeof(buf)); + memset(blk, 0, sizeof(blk)); + + /* + * Write the last block in the container file + */ + if (unixv7WriteBlock(mount, size - 1, blk) == 0) + return 0; + + filsys->s_isize = htole16(inodeBlocks + 2); + filsys->s_fsize = htole32(V7_LONG(size)); + + filsys->s_m = V7_STEPSIZE; + filsys->s_n = V7_CYLSIZE; + + /* + * Note becuase of the above checks we can always assume that there + * more than enough free blocks to fill s_free[]. + */ + nextblk = nextfree = le16toh(filsys->s_isize); + filsys->s_nfree = htole16(V7_NICFREE); + for (i = 0; i < V7_NICFREE; i++) { + filsys->s_free[i] = htole32(V7_LONG(nextfree)); + tfree++; + nextfree++; + } + + /* + * Build the on-disk free block list. + */ + while (nextfree < (v7_daddr_t)size) { + fp->df_free[le16toh(fp->df_nfree)] = htole32(V7_LONG(nextfree)); + fp->df_nfree = htole16(le16toh(fp->df_nfree) + 1); + tfree++; + nextfree++; + if (le16toh(fp->df_nfree) == V7_NICFREE) { + if (unixv7WriteBlock(mount, nextblk, blk) == 0) + return 0; + fp->df_nfree = 0; + nextblk = V7_LONG(le32toh(fp->df_free[0])); + } + } + + /* + * Write any partial free list entry + */ + if (fp->df_nfree != 0) { + if (unixv7WriteBlock(mount, nextblk, blk) == 0) + return 0; + nextblk = V7_LONG(le32toh(fp->df_free[0])); + } + + /* + * Terminate the list with a zero entry + */ + fp->df_nfree = htole16(1); + fp->df_free[0] = 0; + if (unixv7WriteBlock(mount, nextblk, blk) == 0) + return 0; + + filsys->s_tfree = V7_LONG(htole32(tfree)); + + /* + * Zero out all of the inodes (marking them as free) and build the + * super block cache starting at inode ROOTINO + 1. + */ + for (i = 2; i != le16toh(filsys->s_isize); i++) { + if (unixv7WriteBlock(mount, i, buf) == 0) + return 0; + filsys->s_tinode = htole16(le16toh(filsys->s_tinode) + V7_INOPB); + } + filsys->s_ninode = htole16(MIN(V7_NICINOD, inodes)); + for (i = 0; i < le16toh(filsys->s_ninode); i++) + filsys->s_inode[i] = htole16(i + 3); + + filsys->s_fmod = 1; + + /* + * Hand build the first 2 inodes: + * 1. A zero length regular file with zero links + * 2. The root directory + */ + filsys->s_tinode = htole16(le16toh(filsys->s_tinode) - 2); + + if ((rootdir = emptyDirectory(mount, V7_ROOTINO, V7_ROOTINO)) == 0) + return 0; + + dp->di_mode = htole16(V7_IFREG); + dp->di_atime = dp->di_mtime = dp->di_ctime = htole32(V7_LONG(now)); + + dp++; + dp->di_mode = htole16(V7_IFDIR | 0777); + dp->di_nlink = htole16(2); + dp->di_size = htole32(V7_ULONG(2 * sizeof(struct v7_direct))); + V7_PUT3ADDR(&dp->di_addr[0], rootdir); + dp->di_atime = dp->di_mtime = dp->di_ctime = htole32(V7_LONG(now)); + if (unixv7WriteBlock(mount, 2, buf) == 0) + return 0; + + filsys->s_time = htole32(V7_LONG(now)); + + if (SWISSET('f') && (SWGETVAL('f') != NULL)) + strncpy(filsys->s_fname, SWGETVAL('f'), sizeof(filsys->s_fname)); + + if (SWISSET('p') && (SWGETVAL('p') != NULL)) + strncpy(filsys->s_fpack, SWGETVAL('p'), sizeof(filsys->s_fpack)); + + /* + * Write out the super block. + */ + update(mount); + return 1; +} + +/*++ + * u n i x v 7 M k d i r + * + * Create a new empty directory. + * + * Inputs: + * + * mount - pointer to the mounted file system descriptor + * unit - partition number (unused) + * fname - pointer to filename string + * + * Outputs: + * + * None + * + * Returns: + * + * 1 if directory was successfully created, 0 otherwise + * + --*/ +static int unixv7Mkdir( + struct mountedFS *mount, + uint8_t UNUSED(unit), + char *fname +) +{ + struct UNIXV7data *data = &mount->unixv7data; + struct unixv7FileSpec spec; + v7_ino_t ino = V7_ROOTINO; + int i; + + if (unixv7ParseFilespec(fname, &spec, UNIXV7_M_NONE, UNIXV7_A_NEW) == 0) { + fprintf(stderr, "mkdir: syntax error in directory specification \"%s\"\n", + fname); + return 0; + } + + if (spec.depth == 0) { + fprintf(stderr, "mkdir: directory specification missing\n"); + return 0; + } + + for (i = 0; i < spec.depth; i++) { + struct v7_dinode inode; + v7_ino_t next; + + if (strlen(spec.comp[i]) > V7_DIRSIZ) { + fprintf(stderr, "mkdir: name component too long \"%s\"\n", spec.comp[i]); + return 0; + } + + switch (lookupComponent(mount, ino, spec.comp[i], &next)) { + /* + * Error detected, bail out now. + */ + case -1: + return 0; + + case 0: + /* + * The component does not exist, see if we should create it + */ + if (SWISSET('p') || (i == (spec.depth - 1))) { + struct v7_dinode di; + v7_daddr_t blk; + + if ((next = ialloc(mount, &di, V7_IFDIR)) == 0) + goto fail; + + if ((blk = emptyDirectory(mount, next, ino)) == 0) + goto fail; + + di.di_nlink = htole16(2); + di.di_mode = htole16(le16toh(di.di_mode) | 0755); + di.di_uid = htole16(data->user); + di.di_gid = htole16(data->group); + di.di_size = htole32(V7_ULONG(2 * sizeof(struct v7_direct))); + V7_PUT3ADDR(&di.di_addr[0], blk); + di.di_atime = di.di_mtime = di.di_ctime = htole32(unixv7Timestamp()); + + if (unixv7WriteInode(mount, next, &di) == 0) + goto fail; + + if (addComponent(mount, ino, next, spec.comp[i], 1) == 0) + goto fail; + } else { + fprintf(stderr, "mkdir: missing intermediate directory \"%s\"\n", + spec.comp[i]); + goto fail; + } + break; + + case 1: + /* + * The component already exists, make sure that it is a directory + */ + if (unixv7ReadInode(mount, next, &inode) == 0) + goto fail; + if ((le16toh(inode.di_mode) & V7_IFMT) != V7_IFDIR) { + fprintf(stderr, "mkdir: \"%s\" exists and is not a directory\n", + spec.comp[i]); + goto fail; + } + break; + } + ino = next; + } + update(mount); + return 1; + fail: + update(mount); + return 0; +} + +/*++ + * u n i x v 7 S e t + * + * Set mount poinmt 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 unixv7Set( + struct mountedFS *mount, + uint8_t UNUSED(unit), + uint8_t UNUSED(present) +) +{ + struct UNIXV7data *data = &mount->unixv7data; + int idx = 0; + uint16_t id; + + while (setCmds[idx] != NULL) { + if (strcmp(words[1], setCmds[idx]) == 0) { + switch (idx) { + case UNIXV7SET_UID: + if (args == 3) { + if (sscanf(words[2], "%hu", &id) == 1) { + data->user = id; + } else fprintf(stderr, + "unixv7: UID syntax error \"%s\"\n", words[2]); + } else fprintf(stderr, "unixv7: Invalid syntax for \"set uid\"\n"); + return; + + case UNIXV7SET_GID: + if (args == 3) { + if (sscanf(words[2], "%hu", &id) == 1) { + data->group = id; + } else fprintf(stderr, + "unixv7: GID syntax error \"%s\"\n", words[2]); + } else fprintf(stderr, "unixv7: Invalid syntax for \"set gid\"\n"); + return; + + default: + fprintf(stderr, "unixv7: \"%s\" not implemented\n", words[1]); + return; + } + } + idx++; + } + fprintf(stderr, "unixv7: Unknown set command \"%s\"\n", words[1]); +} + +/*++ + * u n i x v 7 I n f o + * + * Display information about the internal structure of the Unix V7 file + * system. + * + * 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 unixv7Info( + struct mountedFS *mount, + uint8_t UNUSED(unit), + uint8_t UNUSED(present) +) +{ + printf("/\n"); + dumpDirectory(mount, V7_ROOTINO, 0); +} + +/*++ + * u n i x v 7 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 unixv7Dir( + struct mountedFS *mount, + uint8_t UNUSED(unit), + char *fname +) +{ + struct unixv7FileSpec spec; + + if (unixv7ParseFilespec(fname, &spec, UNIXV7_M_ALLOW, UNIXV7_A_EXIST) == 0) { + fprintf(stderr, "dir: syntax error in file spec \"%s\"\n", fname); + return; + } + + if (spec.depth == 0) + unixv7DisplayDir(mount, &spec, "/", V7_ROOTINO, V7_ROOTINO, 0); + else walkTree(mount, &spec, 0, V7_ROOTINO, unixv7DisplayDir, 0); +} + +/*++ + * u n i x v 7 O p e n F i l e R + * + * Open a UNIX V7 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 *unixv7OpenFileR( + struct mountedFS *mount, + uint8_t UNUSED(unit), + char *fname +) +{ + struct unixv7OpenFile *file; + struct unixv7FileSpec spec; + + if (unixv7ParseFilespec(fname, &spec, UNIXV7_M_NONE, UNIXV7_A_EXIST) == 0) { + fprintf(stderr, "Failed to parse filename \"%s\"\n", fname); + return NULL; + } + + if ((file = malloc(sizeof(struct unixv7OpenFile))) != NULL) { + memset(file, 0, sizeof(struct unixv7OpenFile)); + + file->mode = M_UNKNOWN; + walkTree(mount, &spec, 0, V7_ROOTINO, unixv7OpenR, (uintptr_t)file); + + if (file->mode == M_UNKNOWN) { + fprintf(stderr, "%s: Failed for open \"%s\" for reading\n", + command, fname); + free(file); + return NULL; + } + } + return file; +} + +/*++ + * u n i x v 7 O p e n F i l e W + * + * Open a UNIX V7 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) (unused) + * + * Outputs: + * + * None + * + * Returns: + * + * Pointer to open file descriptor, NULL if open fails + * + --*/ +static void *unixv7OpenFileW( + struct mountedFS *mount, + uint8_t UNUSED(unit), + char *fname, + off_t UNUSED(size) +) +{ + struct unixv7OpenFile *file; + struct unixv7FileSpec spec; + + if (unixv7ParseFilespec(fname, &spec, UNIXV7_M_NONE, UNIXV7_A_NEW) == 0) { + fprintf(stderr, "Failed to parse filename \"%s\"\n", fname); + return NULL; + } + + if ((file = malloc(sizeof(struct unixv7OpenFile))) != NULL) { + memset(file, 0, sizeof(struct unixv7OpenFile)); + + file->mode = M_UNKNOWN; + walkTree(mount, &spec, 0, V7_ROOTINO, unixv7OpenW, (uintptr_t)file); + + if (file->mode == M_UNKNOWN) { + fprintf(stderr, "%s: Failed for open \"%s\" for writing\n", + command, fname); + free(file); + return NULL; + } + } + return file; +} + +/*++ + * u n i x v 7 F i l e S i z e + * + * Return an estimate of the size of a currently open file. This routine + * return the size stored in the file's inode. + * + * Inputs: + * + * filep - pointer to open file descriptor + * + * Outputs: + * + * None + * + * Returns: + * + * The file's current size. + * + --*/ +static off_t unixv7FileSize( + void *filep +) +{ + struct unixv7OpenFile *file = filep; + + return V7_LONG(le32toh(file->inode.di_size)); +} + +/*++ + * u n i x v 7 C l o s e F i l e + * + * Close an open UNIX V7 file. + * + * Inputs: + * + * filep - pointer to open file descriptor + * + * Outputs: + * + * None + * + * Returns: + * + * None + * + --*/ +static void unixv7CloseFile( + void *filep +) +{ + struct unixv7OpenFile *file = filep; + struct mountedFS *mount = file->mount; + struct UNIXV7data *data = &mount->unixv7data; + + if (file->mode == M_WR) { + if ((file->offset & (V7_BLOCKSIZE - 1)) != 0) { + uint32_t pbn; + + /* + * Partial block is in memory, flush it out to disk + */ + file->block = file->offset >> V7_BSHIFT; + if ((pbn = bmap(mount, &file->inode, file->ino, file->block, V7_WRITE)) == 0) { + ERROR("%s: Error mapping lbn %u on inode %u flushing last block\n", + command, file->block, file->ino); + goto done; + } + if (unixv7WriteBlock(mount, pbn, file->buffer) == 0) + goto done; + } + + file->inode.di_mode = htole16(le16toh(file->inode.di_mode) | 0644); + file->inode.di_nlink = htole16(1); + file->inode.di_uid = htole16(data->user); + file->inode.di_gid = htole16(data->group); + file->inode.di_size = htole32(V7_LONG(file->offset)); + file->inode.di_atime = + file->inode.di_mtime = + file->inode.di_ctime = htole32(V7_LONG(unixv7Timestamp())); + + if (unixv7WriteInode(mount, file->ino, &file->inode)) + if (addComponent(mount, file->parent, file->ino, file->name, 0)) + update(mount); + } + + done: + if (file->buffer != NULL) + free(file->buffer); + free(file); +} + +/*++ + * u n i x v 7 R e a d F i l e + * + * Read data from a UNIX V7 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 unixv7ReadFile( + void *filep, + void *buf, + size_t buflen +) +{ + struct unixv7OpenFile *file = filep; + char *bufr = buf; + + if (SWISSET('a')) { + char ch; + size_t count = 0; + + /* + * Make sure we always have space for the terminating LF>. + */ + buflen--; + + /* + * Read a full or partial line from the open file. + */ + while ((buflen != 0) && (unixv7ReadBytes(file, &ch, 1) == 1)) { + if (ch == '\n') { + bufr[count++] = '\r'; + bufr[count++] = '\n'; + break; + } + bufr[count++] = ch; + buflen--; + } + return count; + } + return unixv7ReadBytes(file, bufr, buflen); +} + +/*++ + * u n i x v 7 W r i t e F i l e + * + * Write data to a UNIX V7 file. If ASCII mode is active and the buffer + * is terminated with convert it to . + * + * Inputs: + * + * file - pointer to open file descriptor + * buf - pointer to the buffer to be written + * buflen - length of the supplied buffer + * + * Outputs: + * + * None + * + * Returns: + * + * # of bytes of data written, 0 means error + * + --*/ +static size_t unixv7WriteFile( + 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 unixv7WriteBytes(file, buf, buflen); +} + +/*++ + * u n i x v 7 D e l e t e F i l e + * + * Delete a file from a UNIX V7 file system. + * + * Inputs: + * + * filep - pointer to open file descriptor (open for read) + * fname - pointer to filename string (unused) + * + * Outputs: + * + * None + * + * Returns: + * + * None + * + --*/ +static void unixv7DeleteFile( + void *filep, + char *UNUSED(fname) +) +{ + struct unixv7OpenFile *file = filep; + struct mountedFS *mount = file->mount; + + if (unlinkComponent(mount, file->parent, file->name) == 0) + ERROR("%s: \"%s\" not found\n", command, file->name); + + update(mount); + + unixv7CloseFile(file); +} + +/*++ + * u n i x v 7 F S + * + * Descriptor for accessing Unix V7 file systems. + * + --*/ +struct FSdef unixv7FS = { + NULL, + "unixv7", + "unixv7 Unix V7 file system\n", + 0, + V7_BLOCKSIZE, + unixv7Mount, + unixv7Umount, + unixv7Size, + unixv7Newfs, + unixv7Mkdir, + unixv7Set, + unixv7Info, + unixv7Dir, + unixv7OpenFileR, + unixv7OpenFileW, + unixv7FileSize, + unixv7CloseFile, + unixv7ReadFile, + unixv7WriteFile, + unixv7DeleteFile, + NULL, /* No tape support functions */ + NULL, + NULL, + NULL +}; diff --git a/converters/fsio/unixv7.h b/converters/fsio/unixv7.h new file mode 100644 index 0000000..537865b --- /dev/null +++ b/converters/fsio/unixv7.h @@ -0,0 +1,258 @@ +/* + + Copyright (c) 2025, John Forecast + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + JOHN FORECAST BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Except as contained in this notice, the name of John Forecast shall not + be used in advertising or otherwise to promote the sale, use or other dealings + in this Software without prior written authorization from John Forecast. + +*/ +#ifndef __UNIXV7_H__ +#define __UNIXV7_H__ + +/* + * General disk layout: + * + * Block + * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + * 0 | Reserved | + * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + * 1 | Super Block | + * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + * 2 | I nodes | + * | ... | + * | ... | + * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + * | Data Blocks | + * | ... | + * | ... | + * | ... | + * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + * End of Volume + */ + +/* + * Common data types + */ +typedef int16_t v7_short; +typedef uint16_t v7_ushort; +typedef int16_t v7_int; +typedef uint16_t v7_uint; +typedef int32_t v7_long; +typedef uint32_t v7_ulong; + +typedef int32_t v7_daddr_t; +typedef uint16_t v7_ino_t; +typedef int32_t v7_time_t; +typedef int32_t v7_off_t; + +/* + * 32-bit longword values are stored with the high 16-bit word first + */ +#define V7_LONG(v) \ + ((v7_long)((((v) & 0xFFFF) << 16) | (((v) & 0xFFFF0000) >> 16))) +#define V7_ULONG(v) \ + ((v7_ulong)((((v) & 0xFFFF) << 16) | (((v) & 0xFFFF0000) >> 16))) + +#define V7_BLOCKSIZE 512 +#define V7_BSHIFT 9 /* Log2(V7_BLOCKSIZE) */ +#define V7_BMASK 0777 /* V7_BLOCKSIZE - 1 */ +#define V7_NINDIR ((int)(V7_BLOCKSIZE / sizeof(v7_daddr_t))) +#define V7_NSHIFT 7 /* Log2(NINDIR) */ +#define V7_NMASK 0177 /* NINDIR - 1 */ + +#define V7_ROOTINO ((v7_ino_t)2) /* i number of all roots */ +#define V7_SUPERBLK 1 /* Super block address */ +#define V7_DIRSIZ 14 /* Max characters per directory */ +#define V7_NICFREE 50 /* Number of superblock free blocks */ +#define V7_NICINOD 100 /* Number of superblock inodes */ + +#define V7_STEPSIZE 9 /* Default step for freelist spacing */ +#define V7_CYLSIZE 400 /* Default cylinder size for spacing */ + +/* + * Super-block + */ +#pragma pack(push, 1) +struct v7_filsys { + v7_ushort s_isize; /* Size in blocks of i-list */ + v7_daddr_t s_fsize; /* Size in blocks of entire volume */ + v7_short s_nfree; /* Number of addresses in s_free */ + v7_daddr_t s_free[V7_NICFREE];/* Free block list */ + v7_short s_ninode; /* Number of i-nodes in s_free */ + v7_ino_t s_inode[V7_NICINOD];/* Free i-node list */ + char s_flock; /* Lock during free list manip. */ + char s_ilock; /* Lock during i-list manip. */ + char s_fmod; /* Super-block modified flag */ + char s_ronly; /* Mounted read-only flag */ + v7_time_t s_time; /* Last super-block update */ + /* + * Remainder not maintained by this version of the system + */ + v7_daddr_t s_tfree; /* Total free blocks */ + v7_ino_t s_tinode; /* Total free inodes */ + v7_short s_m; /* Interleave factor */ + v7_short s_n; /* " " */ + char s_fname[6]; /* File system name */ + char s_fpack[6]; /* File system pack name */ +}; + +/* + * On-disk free space list at the head of each free block. + */ +struct v7_fblk { + v7_int df_nfree; /* Number of addresses in df_free */ + v7_daddr_t df_free[V7_NICFREE]; +}; + +/* + * On-disk inode structure + */ +struct v7_dinode { + v7_ushort di_mode; /* Mode and type of file */ + v7_short di_nlink; /* Number of links to file */ + v7_short di_uid; /* Owner's user ID */ + v7_short di_gid; /* Owner's group ID */ + v7_off_t di_size; /* Number of bytes in file */ + char di_addr[40]; /* Disk block addresses */ + v7_time_t di_atime; /* Time last accessed */ + v7_time_t di_mtime; /* Time last modified */ + v7_time_t di_ctime; /* Time created */ +}; +#define V7_INOPB 8 /* 8 inodes per block */ +#define itod(x) ((v7_daddr_t)((x + 15) >> 3)) +#define itoo(x) ((v7_uint)((x + 15) & 07)) +#define itoff(x) ((v7_uint)(x + 15)) + +/* + * The 40 address bytes: + * + * 39 used; 13 addresses of 3 bytes each + */ +#define NADDR 13 + +/* + * Mode bits + */ +#define V7_IFMT 0170000 /* Type of file */ +#define V7_IFCHR 0020000 /* Character special */ +#define V7_IFBLK 0060000 /* Block special */ +#define V7_IFMPC 0030000 /* Multiplex character special */ +#define V7_IFMPB 0070000 /* Multiplex block special */ +#define V7_IFDIR 0040000 /* Directory */ +#define V7_IFLNK 0120000 /* Symbolic link */ +#define V7_IFREG 0100000 /* Regular */ +#define V7_ISUID 0004000 /* Set user ID on execution */ +#define V7_ISGID 0002000 /* Set group ID on execution */ +#define V7_ISVTX 0001000 /* Save swapped text even after use */ +#define V7_IREAD 0000400 /* Read permission, owner */ +#define V7_IWRITE 0000200 /* Write permission, owner */ +#define V7_IEXEC 0000100 /* Execute/Search permission, owner */ + +struct v7_direct { + v7_ino_t d_ino; /* Inode number */ + char d_name[V7_DIRSIZ]; /* File name */ +}; +#pragma pack(pop) + +/* + * Disk block addresses are stored on disk in 2 formats: + * + * 1 - In the inode addr array, 3 bytes each which limits disk size to 16GB + * 2 - In the free block list, 4 byte v7_long values + */ +#define V7_GET3ADDR(p, v) \ + { uint8_t *tmp = (uint8_t *)p; \ + v = *tmp++ << 16; v |= *tmp++; v |= *tmp++ << 8; \ + } +#define V7_PUT3ADDR(p, v) \ + { uint8_t *tmp = (uint8_t *)p; \ + *tmp++ = (v >> 16) & 0xFF; *tmp++ = v & 0xFF; *tmp++ = (v >> 8) & 0xFF; \ + } +#define V7_GET4ADDR(p, v) \ + { uint16_t *tmp = (uint16_t *)p; \ + v = le16toh(*tmp++) << 16; v |= le16toh(*tmp); \ + } +#define V7_PUT4ADDR(p, v) \ + { uint16_t *tmp = (uint16_t *)p; \ + *tmp++ = htole16(v) >> 16; *tmp = htole16(v & 0xFFFF); \ + } + +#define V7_READ 0 /* Read operation */ +#define V7_WRITE 1 /* Write operation */ + +/* + * Structure to describe a file/directory name. * and % may be used as wild + * card characters within each component of a name. + */ +#define UNIXV7_COMP 100 /* Maximum name depth */ + +struct unixv7FileSpec { + int depth; /* Actual depth of this name */ + char wildcard; /* Wildcard options */ + char access; /* Access mode (existing/new file) */ + char *comp[UNIXV7_COMP]; +}; +#define UNIXV7_M_NONE 0000 /* Wild cards not allowed */ +#define UNIXV7_M_ALLOW 0001 /* Wild cards allowed */ + +#define UNIXV7_A_EXIST 0000 /* Access an existing file */ +#define UNIXV7_A_NEW 0001 /* Create a new file */ + +/* + * Device descriptor + */ +struct UNIXV7device { + char *name; /* Device name */ + size_t diskSize; /* # of blocks on device */ +}; + +/* + * Structure to define an open file. + */ +struct unixv7OpenFile { + char name[V7_DIRSIZ + 1]; /* file name */ + v7_ino_t parent; /* inode number of parent directory */ + v7_ino_t ino; /* inode number of file */ + struct v7_dinode inode; /* on-disk inode copy */ + enum openMode mode; /* Open mode (read/write) */ + struct mountedFS *mount; /* Mounted file system descriptor */ + off_t offset; /* Current read/write point */ + uint32_t block; /* Current block in buffer */ + char *buffer; /* Private buffer for file I/O */ +}; + +/* + * Unix V7 specific data area. + */ +struct UNIXV7data { + unsigned int blocks; /* # of blocks in the file system */ + unsigned int offset; /* Block offset to partition start */ + uint16_t user; /* Default user ID */ + uint16_t group; /* Default group ID */ + union { + struct v7_filsys sb; + uint8_t super; + } superblk; /* Holds super block */ + uint8_t buf[512]; /* Disk buffer */ + uint8_t zero[512]; /* Always contains 0's */ +}; + +#endif