(* Version 1.10, Nov 1984 *)
DEFINITION MODULE FileSystem;
(*
   File manipulation routines

This implementation is based on the underlying operating
system for file handling. It distinguishes between BINARY
files and TEXT files.

File structure:

After any file operation the result should be checked
for errors, by testing the field 'res' of the file
variable (see type declarations for 'File' and
'Response').

The BOOLEAN field 'eof' in a file variable (variable of
type 'File)' allows to determine the end-of-file. It is
set to TRUE after the first unsuccessful attempt to read
information from the file. This first attempt to read
beyond end-of-file does not set any error condition; the
field 'res' of the file variable still indicates 'done'.
However, the character (or other data) returned is
not valid.

Binary files:

   A file is a sequence of bytes with no other structure
   implied.

   Under some operating systems (e.g. CP/M-86) the file
   may be organized in records (128 bytes each), and
   therefore, the length of a file will always be a
   multiple of this record size.

Text files:

   A file is a sequence of characters. The character code
   32C (Ctrl-Z) indicates end-of-file). All other
   character codes from 0C to 377C are legal. When reading
   a text file, 'eof' becomes TRUE when encountering the
   character 32C, or at the pysical end of the file. When
   closing a text file that has been modified, the
   character 32C is written on the file.

   When reading from a text file (by means of procedure
   'ReadChar'), the character ASCII.EOL is returned for
   the sequence <CR, LF>, or for a single <CR> or <LF>.
   When writing to a text file (by means of procedure
   'WriteChar'), the character ASCII.EOL is changed to
   the sequence <CR,LF>.

Any file is in one of the states 'opened', 'reading',
'writing', or 'modifying'. These states have the following
meaning:

   opened    = Content of file buffer is undefined and not
               associated with a position in the file.
               When starting to read or write from a file
               that is in state open, the state is changed
               implicitly to reading or writing.
   reading   = No writing is allowed.
   writing   = No reading is allowed. Writing always takes
               place at the end-of-file position.
               When writing on an existing file, which is
               (physically) longer than the current write
               position, it is undefined, whether the file
               is truncated upon a close.
   modifying = Reading and writing are allowed. Writing an
               element inside of a file means 'overwriting'
               the value of the element with a new value.
               Upon a close, the file is not truncated.

The state of the file is given by the field 'flags' of
a file variable. By means of the procedures SetRead,
SetWrite, SetModify, and SetOpen, it is possible to change
the status of an open file.

To every file is associated a 'current position'. This
corresponds to the number of the current byte inside the
file, starting with zero for the first byte. The next
reading or writing takes place at the current position.
This position is updated automatically after reading or
writing. It can also be inquired or set through the
procedure GetPos or SetPos.

After the opening of a file (by means of Lookup or Create)
it is state 'opened' and positioned at the beginning
(low = 0, high = 0).

Conventions for filenames:

For the procedures Lookup and Rename, a filename has to be
given, including a medium name (drive name), a file name
and an optional file type. For the procedure Create, a
medium name has to be given. The medium name is up to
three characters long (alphanumeric, starting with a
letter). It is separated from the file name by a colon
(':'). The medium name must always be given explicitly.
The default medium must be denoted by 'DK:'.
Depending on the operating system, the file name may
include a path name, specifying the the directory where
the file exists. The length of the file (and path) name,
and the characters legal for file names, depend on the
operating system.

By default, the mediums (i.e. disk drives) handled by
module 'DiskFiles' are installed.

Derived from the Lilith Modula-2 system developed by the
group of Prof. N. Wirth at ETH Zurich, Switzerland.
*)


FROM SYSTEM IMPORT ADDRESS, WORD, BYTE;

EXPORT QUALIFIED
   File, Response, Command,
   Flag, FlagSet,

   (* basic file operations: *)
   Create, Close, Lookup, Rename, Delete,
   SetRead, SetWrite, SetModify, SetOpen,
   Doio,
   SetPos, GetPos, Length,

   (* stream-like I/O: *)
   Reset, Again,
   ReadWord, ReadChar, ReadByte, ReadNBytes,
   WriteWord, WriteChar, WriteByte, WriteNBytes,

   (* medium handling: *)
   MediumType,
   FileProc, DirectoryProc,
   CreateMedium, RemoveMedium,

   FileNameChar;


TYPE

   MediumHint = CARDINAL;
   (*- medium index used in DiskFiles *)

   MediumType = ARRAY [0..2] OF CHAR;
   (*- medium name (A, B...) *)

   Flag     = (er, ef, rd, wr, ag, txt);
   (*
   - status flag for file operations:

   er = error occured, ef = end-of-file reached,
   rd = in read mode,  wr = in write mode,
   ag = "Again" has been called after last read,
   txt = text-file (the last access to the file was a
   'WriteChar' or 'ReadChar').
   *)

   FlagSet  = SET OF Flag;
   (*- status flag set *)

   Response = (done,
               notdone, notsupported, callerror,
               unknownmedium, unknownfile, paramerror,
               toomanyfiles, eom, userdeverror);
   (*- result of a file operation *)

   Command  = (create, close, lookup, rename, delete,
               setread, setwrite, setmodify, setopen,
               doio, setpos, getpos, length);
   (*- commands passed to module 'DiskFiles' *)

   BuffAdd  = POINTER TO ARRAY [0..0FFFEH] OF CHAR;
   (*- file buffer pointer type *)

   File     = RECORD
                bufa: BuffAdd;
                (*- buffer address *)
                buflength: CARDINAL;
                (*- size of buffer in bytes. In the
                    current release it is always a
                    multiple of 128. *)
                validlength: CARDINAL;
                (*- number of valid bytes in buffer *)
                bufind: CARDINAL;
                (*- byte-index to the buffer of the
                    current position *)
                flags: FlagSet;
                (*- status of the file *)
                eof: BOOLEAN;
                (*- TRUE if last access was past the end
                    of the file *)
                res: Response;
                (*- result of last operation *)
                lastRead: CARDINAL;
                (*- the word or byte (char) last read *)
                mt: MediumType;
                (*- selects the driver that supports this
                    file *)
                fHint: CARDINAL;
                (*- used internally by device driver *)
                mHint: MediumHint;
                (*- used internally by medium handler *)
                CASE com: Command OF
                  lookup: new: BOOLEAN;
                | setpos,
                  getpos,
                  length: highpos, lowpos: CARDINAL;
                END;
              END;
   (*- file structure used for bookkeeping by DiskFiles *)


PROCEDURE Create (VAR f: File; mediumName: ARRAY OF CHAR);
(*
- create a temporary file

in:     mediumName   name of medium to create file on, 
                     in character format

out:    f            initialized file structure

A temporary file is characterized by an empty name. To 
make the file permanent, it has to  be renamed with a
non-empty name before closing it. For subsequent
operations on this file, it is referenced by 'f'.
*)


PROCEDURE Close (VAR f: File);
(*
- Close a file

in:     f       structure referencing an open file

out:    f       the field f.res will be set appropriately.

Terminates the operations on file "f". If "f" is a 
temporary file, it will be destroyed, whereas a file with
a non-empty name remains on its medium and is accessible
through "Lookup". When closing a text-file after writing,
the end-of-file code 32C is written on the file (MS-DOS
and CP/M-86 convention).
*)


PROCEDURE Lookup (VAR f: File; fileName: ARRAY OF CHAR;
                  newFile: BOOLEAN);
(*
- look for a file

in:  filename     drive and name of file to search for
     newFile      TRUE if file should be created if
                  not found

out: f            initialized file structure; f.res will
                  be set appropriately.

Searches the medium specified in "filename" for a file
that matches the name and type given in "filename". If
the file is not found and "newFile" is TRUE, a new
(permanent) file with the given name and type is created.
If it is not  found and "newFile" is FALSE, no action
takes place and "notdone" is returned  in the result
field of "f".
*)


PROCEDURE Rename (VAR f: File; newname: ARRAY OF CHAR);
(*
- rename a file

in:  f        structure referencing an open file
     newname  filename to rename to, with
              device:name.type specified

out: f        file name in f will be changed and the
              field f.res will be set appropriately.

The medium, on which the files reside can not be changed
with this command. The medium name inside "newname" has
to be the old one.
*)


PROCEDURE Delete (name: ARRAY OF CHAR; VAR f: File);
(*
- delete a file

in:     name    name of file to delete, with
                dev:name.type specified

out:    f       the field f.res will be set appropriately.
*)


PROCEDURE ReadWord (VAR f: File; VAR w: WORD);
(*
- Returns the word at the current position in f

in:     f       structure referencing an open file

out:    w       word read from file
        f       the result field f.res will be set
                appropriately.

The file will be positioned at the next word when the
read is done.
*)


PROCEDURE WriteWord (VAR f: File; w: WORD);
(*
- Write one word to a file

in:     f       structure referencing an open file
        w       word to write

out:    f       the field f.res will be set appropriately.

When overwriting, the file will be positioned at the next
word when the write is done.
*)


PROCEDURE ReadChar (VAR f: File; VAR ch: CHAR);
(*
- Read one character from a file

in:     f       structure referencing an open file

out:    ch      character read from file
        f       the field f.res will be set appropriately.

The file will be positioned at the next character when
the read is done.
*)
  

PROCEDURE WriteChar (VAR f: File; ch: CHAR);
(*
- Write one character to a file

in:     f       structure referencing an open file
        ch      character to write

out:    f       the field f.res will be set apporopriately.

When overwriting, the file will be positioned at the next
character when the write is done.
*)


PROCEDURE ReadByte (VAR f: File; VAR b: BYTE);
(*
- Read one byte from a file

in:     f       structure referencing an open file

out:    b       byte read from file
        f       the field f.res will be set appropriately.

The file will be positioned at the next byte when the
read is completed.
*)


PROCEDURE WriteByte (VAR f: File; b: BYTE);
(*
- Write one byte to a file

in:     f       structure referencing an open file 
        b       byte to write

out:    f       the field f.res will be set appropriately.

When overwriting, the file will be positioned at the next
byte when the write is done.
*)


PROCEDURE ReadNBytes (VAR f: File;
                      bufPtr: ADDRESS;
                      requestedBytes: CARDINAL;
                      VAR read: CARDINAL);
(*
- Read a specified number of bytes from a file

in:    f        structure referencing an open file
       bufPtr   pointer to buffer area to read bytes into
       requestedBytes number of bytes to read

out:   bufPtr^  bytes read from file
       f        the field f.res will be set appropriately.
       read     the number of bytes actually read.

The file will be positioned at the next byte after the
requested sequence of bytes.
*)


PROCEDURE WriteNBytes (VAR f: File;
                       bufPtr: ADDRESS;
                       requestedBytes: CARDINAL;
                       VAR written: CARDINAL);
(*
- Write a specified number of bytes to a file

in:    f        structure referencing an open file
       bufPtr   pointer to string of bytes to write
       requestedBytes number of bytes to write
out:   f        the field f.res will be set appropriately.
       written  the number of bytes actually written

When overwriting, the file will be positioned at the next
byte after the requested sequence of bytes.
*)


PROCEDURE Again (VAR f: File);
(*
- returns a character to the buffer to be read again

in:     f       structure referencing an open file

out:    f       the f.res field will be set appropriately. 

This should be called after a read operation only (it has
no effect otherwise). It prevents the subsequent read
from reading the next element; the element just read
before will be returned a second time. Multiple calls to
Again without a read in between have the same effect as
one call to Again. The position in the file is undefined
after a call to Again (it is defined again after the next
read operation).
*)


PROCEDURE SetRead (VAR f: File);
(*
- Sets the file in reading- state, without changing the
  current position.

in:     f       structure referencing an open file

out:    f       f.res will be set appropriately.

Upon calling SetRead, the current position must be before
the eof. In reading state, no writing is allowed.
*)


PROCEDURE SetWrite (VAR f: File);
(*
- Sets the file in writing-state, without changing the
  current position.

in:     f       structure referencing an open file

out:    f       f.res will be set appropriately.

Upon calling SetWrite, the current position must be a
legal position in the file (including eof). In writing
state, no reading is allowed, and a write always takes
place at the eof. The current implementation does not
truncate the file.
*)


PROCEDURE SetModify (VAR f: File);
(*
- Sets the file in modifying-state, without changing the
  current position.

in:     f       structure referencing an open file

out:    f       f.res will be set appropriately.

Upon calling SetModify, the current position must be
before the eof. In modifying-state, reading and writing
are allowed. Writing is done at the current position,
overwriting whatever element is already there. The file
is not truncated.
*)


PROCEDURE SetOpen (VAR f: File);
(*
- Set the file to opened-state, without changing the
  current position.

in:     f       structure referencing an open file

out:    f       f.res will be set appropriately.

The buffer content is written back on the file, if the
file has been in writing or modifying status. The new
buffer content is undefined. In opened-state, neither
reading nor writing is allowed.
*)


PROCEDURE Reset (VAR f: File);
(*
- Set the file to opened state and position it to the
  top of file.

in:     f       structure referencing an open file

out:    f       f.res will be set appropriately.
*)


PROCEDURE SetPos (VAR f: File; high, low: CARDINAL);
(*
- Set the current position in file 

in:     f       structure referencing an open file
        high    high part of the byte offset
        low     low part of the byte offset

out:    f       f.res will be set appropriately.

The file will be positioned (high*2^16 +low) bytes from
top of file.
*)


PROCEDURE GetPos (VAR f: File; VAR high, low: CARDINAL);
(*
- Return the current byte position in file 

in:     f       structure referencing an open file

out:    high    high part of byte offset
        low     low part of byte offset

The actual position is (high*2^16 +low) bytes from  the top of file.
*)

PROCEDURE Length (VAR f: File; VAR high, low: CARDINAL);
(*
- Return the length of the file in bytes.

in:     f       structure referencing an open file.

out:    high    high part of byte offset
        low     low part of byte offset

The actual length is (high*2^16 +low) bytes. Depending on
the operating system, this length may always be a multiple
of some record size reflecting the physical length of the
file and maybe not the true logical file length.
*)


PROCEDURE Doio (VAR f: File);
(*
-  Do various read/write operations on a file 

in:     f       structure referencing an open file

out:    f       f.res will be set appropriately.

The exact effect of this command depends on the state of
the file (flags):

   opened    = NOOP.
   reading   = reads the record that contains the current
               byte from the file. The old content of the
               buffer is not written back.
   writing   = the buffer is written back. It is then
               assigned to the record, that contains the
               current position. Its content is not
               changed.
   modifying = the buffer is written back and the record
               containing the current position is read.

Note that 'Doio' does not need to be used when reading
through the stream-like I/O routines. Its use is limited
to special applications.
*)


PROCEDURE FileNameChar (c: CHAR): CHAR;
(*
- Check the character c for legality in a filename. 

in:     c       charater to check

out:            0C for illegal characters and c otherwise;
                lowercase letters are transformed into
                uppercase letters.

Which characters are leagl in a filename depends on the
host operating system.
*)


TYPE
  FileProc = PROCEDURE (VAR File);
  (*- Procedure type to be used for internal file
      operations 

  A procedure of this type will be called for the following
  functions (see TYPE 'Command'): setread, setwrite,
  setmodify, setopen, doio, setpos, getpos, and length.
  *)

  DirectoryProc = PROCEDURE (VAR File, ARRAY OF CHAR);
  (*- Procedure type to be used for operations on
      entire files

  A procedure of this type will be called for the following
  functions (see TYPE 'Command'): create, close, lookup,
  rename, and delete.
  *)


PROCEDURE CreateMedium (mt: MediumType;
                        fproc: FileProc;
                        dproc: DirectoryProc;
                        VAR done: BOOLEAN);
(*
- Install the medium "mt" in the file system

in:     mt      medium type to install
        fproc   procedure to handle internal file
                operations
        dproc   procedure to handle operations on an
                entire file

out     done    TRUE if medium was installed successfully

Before accessing or creating a file on a medium, this
medium has to be announced to the file system by means
of the routine CreateMedium. FileSystem calls "fproc"
and "dproc" to perform operations on a file of this
medium. Up to 24 mediums can be announced.
*)


PROCEDURE RemoveMedium (mt: MediumType; VAR done: BOOLEAN);
(*
- Remove the medium "mt" from the file system

in:     mt      medium type to remove

out:    done    TRUE if medium was removed successfully

Attempts to access a file on this medium result in an
error (unknownmedium).
*)


END FileSystem.
