blob: a0ecebe66a49ec49fc4e7f3b216f385b9f16d9b2 [file] [log] [blame]
(* FIO.mod provides a simple buffered file input/output library.
Copyright (C) 2001-2026 Free Software Foundation, Inc.
Contributed by Gaius Mulley <gaius.mulley@southwales.ac.uk>.
This file is part of GNU Modula-2.
GNU Modula-2 is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3, or (at your option)
any later version.
GNU Modula-2 is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
Under Section 7 of GPL version 3, you are granted additional
permissions described in the GCC Runtime Library Exception, version
3.1, as published by the Free Software Foundation.
You should have received a copy of the GNU General Public License and
a copy of the GCC Runtime Library Exception along with this program;
see the files COPYING3 and COPYING.RUNTIME respectively. If not, see
<http://www.gnu.org/licenses/>. *)
IMPLEMENTATION MODULE FIO ;
(*
Title : FIO
Author : Gaius Mulley
System : UNIX (gm2)
Date : Thu Sep 2 22:07:21 1999
Last edit : Thu Sep 2 22:07:21 1999
Description: a complete reimplememtation of FIO.mod
provides a simple buffered file input/output library.
*)
FROM SYSTEM IMPORT ADR, TSIZE, WORD, COFF_T ;
FROM ASCII IMPORT nl, nul, tab ;
FROM StrLib IMPORT StrLen, StrConCat, StrCopy ;
FROM Storage IMPORT ALLOCATE, DEALLOCATE ;
FROM NumberIO IMPORT CardToStr ;
FROM Indexing IMPORT Index, InitIndex, InBounds, HighIndice, PutIndice, GetIndice ;
FROM M2RTS IMPORT InstallTerminationProcedure ;
FROM libc IMPORT exit, open, creat, read, write, close, lseek, strncpy, memcpy ;
FROM wrapc IMPORT SeekSet, SeekEnd, ReadOnly, WriteOnly ;
CONST
MaxBufferLength = 1024*16 ;
MaxErrorString = 1024* 8 ;
CreatePermissions = 666B;
TYPE
FileUsage = (unused, openedforread, openedforwrite, openedforrandom) ;
FileStatus = (successful, outofmemory, toomanyfilesopen, failed, connectionfailure, endofline, endoffile) ;
NameInfo = RECORD
address: ADDRESS ;
size : CARDINAL ;
END ;
Buffer = POINTER TO buf ;
buf = RECORD
valid : BOOLEAN ; (* are the field valid? *)
bufstart: LONGINT ; (* the position of buffer in file *)
position: CARDINAL ; (* where are we through this buffer *)
address : ADDRESS ; (* dynamic buffer address *)
filled : CARDINAL ; (* length of the buffer filled *)
size : CARDINAL ; (* maximum space in this buffer *)
left : CARDINAL ; (* number of bytes left to read *)
contents: POINTER TO ARRAY [0..MaxBufferLength] OF CHAR ;
END ;
FileDescriptor = POINTER TO fds ;
fds = RECORD
unixfd: INTEGER ;
name : NameInfo ;
state : FileStatus ;
usage : FileUsage ;
output: BOOLEAN ; (* is this file going to write data *)
buffer: Buffer ;
abspos: LONGINT ; (* absolute position into file. *)
END ; (* reflects low level reads which *)
(* means this value will normally *)
(* be further through the file than *)
(* bufstart above. *)
PtrToChar = POINTER TO CHAR ;
VAR
FileInfo: Index ;
Error : File ; (* not stderr, this is an unused file handle
which only serves to hold status values
when we cannot create a new file handle *)
(*
GetUnixFileDescriptor - returns the UNIX file descriptor of a file.
*)
PROCEDURE GetUnixFileDescriptor (f: File) : INTEGER ;
VAR
fd: FileDescriptor ;
BEGIN
IF f#Error
THEN
fd := GetIndice(FileInfo, f) ;
IF fd#NIL
THEN
RETURN( fd^.unixfd )
END
END ;
FormatError1('file %d has not been opened or is out of range\n', f) ;
RETURN( -1 )
END GetUnixFileDescriptor ;
(*
WriteString - writes a string to file, f.
*)
PROCEDURE WriteString (f: File; a: ARRAY OF CHAR) ;
VAR
l: CARDINAL ;
BEGIN
l := StrLen(a) ;
IF WriteNBytes(f, l, ADR(a))#l
THEN
END
END WriteString ;
(*
Max - returns the maximum of two values.
*)
PROCEDURE Max (a, b: CARDINAL) : CARDINAL ;
BEGIN
IF a>b
THEN
RETURN( a )
ELSE
RETURN( b )
END
END Max ;
(*
Min - returns the minimum of two values.
*)
PROCEDURE Min (a, b: CARDINAL) : CARDINAL ;
BEGIN
IF a<b
THEN
RETURN( a )
ELSE
RETURN( b )
END
END Min ;
(*
GetNextFreeDescriptor - returns the index to the FileInfo array indicating
the next free slot.
*)
PROCEDURE GetNextFreeDescriptor () : File ;
VAR
f, h: File ;
fd : FileDescriptor ;
BEGIN
f := Error+1 ;
h := HighIndice(FileInfo) ;
LOOP
IF f<=h
THEN
fd := GetIndice(FileInfo, f) ;
IF fd=NIL
THEN
RETURN( f )
END
END ;
INC(f) ;
IF f>h
THEN
PutIndice(FileInfo, f, NIL) ; (* create new slot *)
RETURN( f )
END
END
END GetNextFreeDescriptor ;
(*
IsNoError - returns a TRUE if no error has occured on file f.
*)
PROCEDURE IsNoError (f: File) : BOOLEAN ;
VAR
fd: FileDescriptor ;
BEGIN
IF f=Error
THEN
RETURN( FALSE )
ELSE
fd := GetIndice(FileInfo, f) ;
RETURN( (fd#NIL) AND ((fd^.state=successful) OR (fd^.state=endoffile) OR (fd^.state=endofline)) )
END
END IsNoError ;
(*
IsError - returns a TRUE if an error has occured on file f.
*)
PROCEDURE IsError (f: File) : BOOLEAN ;
VAR
fd: FileDescriptor ;
BEGIN
IF f=Error
THEN
RETURN( FALSE )
ELSE
fd := GetIndice (FileInfo, f) ;
RETURN( (fd#NIL) AND ((fd^.state#successful) AND (fd^.state#endoffile) AND (fd^.state#endofline)) )
END
END IsError ;
(*
IsActive - returns TRUE if the file, f, is still active.
*)
PROCEDURE IsActive (f: File) : BOOLEAN ;
BEGIN
IF f=Error
THEN
RETURN( FALSE )
ELSE
RETURN( GetIndice(FileInfo, f)#NIL )
END
END IsActive ;
(*
openToRead - attempts to open a file, fname, for reading and
it returns this file.
The success of this operation can be checked by
calling IsNoError.
*)
PROCEDURE openToRead (fname: ADDRESS; flength: CARDINAL) : File ;
VAR
f: File ;
BEGIN
f := GetNextFreeDescriptor() ;
IF f=Error
THEN
SetState(f, toomanyfilesopen)
ELSE
f := InitializeFile(f, fname, flength, successful, openedforread, FALSE, MaxBufferLength) ;
ConnectToUnix(f, FALSE, FALSE)
END ;
RETURN( f )
END openToRead ;
(*
openToWrite - attempts to open a file, fname, for write and
it returns this file.
The success of this operation can be checked by
calling IsNoError.
*)
PROCEDURE openToWrite (fname: ADDRESS; flength: CARDINAL) : File ;
VAR
f: File ;
BEGIN
f := GetNextFreeDescriptor() ;
IF f=Error
THEN
SetState(f, toomanyfilesopen)
ELSE
f := InitializeFile(f, fname, flength, successful, openedforwrite, TRUE, MaxBufferLength) ;
ConnectToUnix(f, TRUE, TRUE)
END ;
RETURN( f )
END openToWrite ;
(*
openForRandom - attempts to open a file, fname, for random access
read or write and it returns this file.
The success of this operation can be checked by
calling IsNoError.
towrite, determines whether the file should be
opened for writing or reading.
*)
PROCEDURE openForRandom (fname: ADDRESS; flength: CARDINAL;
towrite, newfile: BOOLEAN) : File ;
VAR
f: File ;
BEGIN
f := GetNextFreeDescriptor() ;
IF f=Error
THEN
SetState(f, toomanyfilesopen)
ELSE
f := InitializeFile(f, fname, flength, successful, openedforrandom, towrite, MaxBufferLength) ;
ConnectToUnix(f, towrite, newfile)
END ;
RETURN( f )
END openForRandom ;
(*
exists - returns TRUE if a file named, fname exists for reading.
*)
PROCEDURE exists (fname: ADDRESS; flength: CARDINAL) : BOOLEAN ;
VAR
f: File ;
BEGIN
f := openToRead(fname, flength) ;
IF IsNoError(f)
THEN
Close(f) ;
RETURN( TRUE )
ELSE
Close(f) ;
RETURN( FALSE )
END
END exists ;
(*
SetState - sets the field, state, of file, f, to, s.
*)
PROCEDURE SetState (f: File; s: FileStatus) ;
VAR
fd: FileDescriptor ;
BEGIN
fd := GetIndice(FileInfo, f) ;
fd^.state := s
END SetState ;
(*
InitializeFile - initialize a file descriptor
*)
PROCEDURE InitializeFile (f: File; fname: ADDRESS;
flength: CARDINAL; fstate: FileStatus;
use: FileUsage;
towrite: BOOLEAN; buflength: CARDINAL) : File ;
VAR
p : PtrToChar ;
fd: FileDescriptor ;
BEGIN
NEW(fd) ;
IF fd=NIL
THEN
SetState(Error, outofmemory) ;
RETURN( Error )
ELSE
PutIndice(FileInfo, f, fd) ;
WITH fd^ DO
name.size := flength+1 ; (* need to guarantee the nul for C *)
usage := use ;
output := towrite ;
ALLOCATE(name.address, name.size) ;
IF name.address=NIL
THEN
state := outofmemory ;
RETURN( f )
END ;
name.address := strncpy(name.address, fname, flength) ;
(* and assign nul to the last byte *)
p := name.address ;
INC(p, flength) ;
p^ := nul ;
abspos := 0 ;
(* now for the buffer *)
NEW(buffer) ;
IF buffer=NIL
THEN
SetState(Error, outofmemory) ;
RETURN( Error )
ELSE
WITH buffer^ DO
valid := FALSE ;
bufstart := 0 ;
size := buflength ;
position := 0 ;
filled := 0 ;
IF size=0
THEN
address := NIL
ELSE
ALLOCATE(address, size) ;
IF address=NIL
THEN
state := outofmemory ;
RETURN( f )
END
END ;
IF towrite
THEN
left := size
ELSE
left := 0
END ;
contents := address ; (* provides easy access for reading characters *)
END ;
state := fstate
END
END
END ;
RETURN( f )
END InitializeFile ;
(*
ConnectToUnix - connects a FIO file to a UNIX file descriptor.
*)
PROCEDURE ConnectToUnix (f: File; towrite, newfile: BOOLEAN) ;
VAR
fd: FileDescriptor ;
BEGIN
IF f#Error
THEN
fd := GetIndice(FileInfo, f) ;
IF fd#NIL
THEN
WITH fd^ DO
IF towrite
THEN
IF newfile
THEN
unixfd := creat(name.address, CreatePermissions)
ELSE
unixfd := open(name.address, INTEGER (WriteOnly ()), 0)
END
ELSE
unixfd := open(name.address, INTEGER (ReadOnly ()), 0)
END ;
IF unixfd<0
THEN
state := connectionfailure
END
END
END
END
END ConnectToUnix ;
(*
The following functions are wrappers for the above.
*)
PROCEDURE Exists (fname: ARRAY OF CHAR) : BOOLEAN ;
BEGIN
RETURN( exists(ADR(fname), StrLen(fname)) )
END Exists ;
PROCEDURE OpenToRead (fname: ARRAY OF CHAR) : File ;
BEGIN
RETURN( openToRead(ADR(fname), StrLen(fname)) )
END OpenToRead ;
PROCEDURE OpenToWrite (fname: ARRAY OF CHAR) : File ;
BEGIN
RETURN( openToWrite(ADR(fname), StrLen(fname)) )
END OpenToWrite ;
PROCEDURE OpenForRandom (fname: ARRAY OF CHAR;
towrite: BOOLEAN; newfile: BOOLEAN) : File ;
BEGIN
RETURN( openForRandom(ADR(fname), StrLen(fname), towrite, newfile) )
END OpenForRandom ;
(*
Close - close a file which has been previously opened using:
OpenToRead, OpenToWrite, OpenForRandom.
It is correct to close a file which has an error status.
Close has an optional return value:
TRUE signifies that the close was successful and all
state associated with f is deallocated.
FALSE signifies that the close was unsuccessful and no
state associated with f has been deallocated.
*)
PROCEDURE Close (f: File) : [BOOLEAN] ;
VAR
fd: FileDescriptor ;
BEGIN
IF f # Error
THEN
fd := GetIndice (FileInfo, f) ;
(* We allow users to close files which have an error status. *)
IF fd # NIL
THEN
FlushBuffer (f) ;
WITH fd^ DO
IF unixfd >= 0
THEN
IF close (unixfd) # 0
THEN
FormatError1 ('failed to close file (%s)\n', name.address) ;
state := failed ;
RETURN FALSE
END
END ;
IF name.address#NIL
THEN
DEALLOCATE(name.address, name.size)
END ;
IF buffer#NIL
THEN
WITH buffer^ DO
IF address#NIL
THEN
DEALLOCATE(address, size)
END
END ;
DISPOSE(buffer) ;
buffer := NIL
END
END ;
DISPOSE(fd) ;
PutIndice(FileInfo, f, NIL)
END ;
RETURN TRUE
ELSE
RETURN FALSE
END
END Close ;
(*
ReadFromBuffer - attempts to read, nBytes, from file, f.
It firstly consumes the buffer and then performs
direct unbuffered reads. This should only be used
when wishing to read large files.
The actual number of bytes read is returned.
-1 is returned if EOF is reached.
*)
PROCEDURE ReadFromBuffer (f: File; a: ADDRESS; nBytes: CARDINAL) : INTEGER ;
VAR
t : ADDRESS ;
result: INTEGER ;
total,
n : CARDINAL ;
p : POINTER TO BYTE ;
fd : FileDescriptor ;
BEGIN
IF f#Error
THEN
total := 0 ; (* how many bytes have we read *)
fd := GetIndice(FileInfo, f) ;
WITH fd^ DO
(* extract from the buffer first *)
IF (buffer#NIL) AND (buffer^.valid)
THEN
WITH buffer^ DO
IF left>0
THEN
IF nBytes=1
THEN
(* too expensive to call memcpy for 1 character *)
p := a ;
p^ := contents^[position] ;
DEC(left) ; (* remove consumed bytes *)
INC(position) ; (* move onwards n bytes *)
nBytes := 0 ; (* reduce the amount for future direct *)
(* read *)
RETURN( 1 )
ELSE
n := Min(left, nBytes) ;
t := address ;
INC(t, position) ;
p := memcpy(a, t, n) ;
DEC(left, n) ; (* remove consumed bytes *)
INC(position, n) ; (* move onwards n bytes *)
(* move onwards ready for direct reads *)
INC(a, n) ;
DEC(nBytes, n) ; (* reduce the amount for future direct *)
(* read *)
INC(total, n) ;
RETURN( total ) (* much cleaner to return now, *)
END (* difficult to record an error if *)
END (* the read below returns -1 *)
END
END ;
IF nBytes>0
THEN
(* still more to read *)
result := read(unixfd, a, INTEGER(nBytes)) ;
IF result>0
THEN
INC(total, result) ;
INC(abspos, result) ;
(* now disable the buffer as we read directly into, a. *)
IF buffer#NIL
THEN
buffer^.valid := FALSE
END ;
ELSE
IF result=0
THEN
(* eof reached *)
state := endoffile
ELSE
state := failed
END ;
(* indicate buffer is empty *)
IF buffer#NIL
THEN
WITH buffer^ DO
valid := FALSE ;
left := 0 ;
position := 0 ;
IF address#NIL
THEN
contents^[position] := nul
END
END
END ;
RETURN( -1 )
END
END
END ;
RETURN( total )
ELSE
RETURN( -1 )
END
END ReadFromBuffer ;
(*
ReadNBytes - reads nBytes of a file into memory area, dest, returning
the number of bytes actually read.
This function will consume from the buffer and then
perform direct libc reads. It is ideal for large reads.
*)
PROCEDURE ReadNBytes (f: File; nBytes: CARDINAL; dest: ADDRESS) : CARDINAL ;
VAR
n: INTEGER ;
p: POINTER TO CHAR ;
BEGIN
IF f # Error
THEN
CheckAccess (f, openedforread, FALSE) ;
n := ReadFromBuffer (f, dest, nBytes) ;
IF n <= 0
THEN
RETURN 0
ELSE
p := dest ;
INC (p, n-1) ;
SetEndOfLine (f, p^) ;
RETURN n
END
ELSE
RETURN 0
END
END ReadNBytes ;
(*
BufferedRead - will read, nBytes, through the buffer.
Similar to ReadFromBuffer, but this function will always
read into the buffer before copying into memory.
Useful when performing small reads.
*)
PROCEDURE BufferedRead (f: File; nBytes: CARDINAL; dest: ADDRESS) : INTEGER ;
VAR
src : ADDRESS ;
total,
n : INTEGER ;
p : POINTER TO BYTE ;
fd : FileDescriptor ;
BEGIN
IF f#Error
THEN
fd := GetIndice (FileInfo, f) ;
total := 0 ; (* how many bytes have we read *)
IF fd#NIL
THEN
WITH fd^ DO
(* extract from the buffer first *)
IF buffer # NIL
THEN
WITH buffer^ DO
WHILE nBytes > 0 DO
IF (left > 0) AND valid
THEN
IF nBytes = 1
THEN
(* too expensive to call memcpy for 1 character *)
p := dest ;
p^ := contents^[position] ;
DEC (left) ; (* remove consumed byte *)
INC (position) ; (* move onwards n byte *)
INC (total) ;
RETURN( total )
ELSE
n := Min (left, nBytes) ;
src := address ;
INC (src, position) ;
p := memcpy (dest, src, n) ;
DEC (left, n) ; (* remove consumed bytes *)
INC (position, n) ; (* move onwards n bytes *)
(* move onwards ready for direct reads *)
INC (dest, n) ;
DEC (nBytes, n) ; (* reduce the amount for future direct *)
(* read *)
INC (total, n)
END
ELSE
(* refill buffer *)
n := read (unixfd, address, size) ;
IF n >= 0
THEN
valid := TRUE ;
position := 0 ;
left := n ;
filled := n ;
bufstart := abspos ;
INC (abspos, n) ;
IF n = 0
THEN
(* eof reached *)
state := endoffile ;
RETURN( -1 )
END
ELSE
valid := FALSE ;
position := 0 ;
left := 0 ;
filled := 0 ;
state := failed ;
RETURN( total )
END
END
END
END ;
RETURN( total )
END
END
END
END ;
RETURN( -1 )
END BufferedRead ;
(*
HandleEscape - translates \n and \t into their respective ascii codes.
*)
PROCEDURE HandleEscape (VAR dest: ARRAY OF CHAR; src: ARRAY OF CHAR;
VAR i, j: CARDINAL; HighSrc, HighDest: CARDINAL) ;
BEGIN
IF (i+1<HighSrc) AND (src[i]='\') AND (j<HighDest)
THEN
IF src[i+1]='n'
THEN
(* requires a newline *)
dest[j] := nl ;
INC(j) ;
INC(i, 2)
ELSIF src[i+1]='t'
THEN
(* requires a tab (yuck) tempted to fake this but I better not.. *)
dest[j] := tab ;
INC(j) ;
INC(i, 2)
ELSE
(* copy escaped character *)
INC(i) ;
dest[j] := src[i] ;
INC(j) ;
INC(i)
END
END
END HandleEscape ;
(*
Cast - casts a := b
*)
PROCEDURE Cast (VAR a: ARRAY OF BYTE; b: ARRAY OF BYTE) ;
VAR
i: CARDINAL ;
BEGIN
IF HIGH(a)=HIGH(b)
THEN
FOR i := 0 TO HIGH(a) DO
a[i] := b[i]
END
ELSE
FormatError('cast failed')
END
END Cast ;
(*
StringFormat1 - converts string, src, into, dest, together with encapsulated
entity, w. It only formats the first %s or %d with n.
*)
PROCEDURE StringFormat1 (VAR dest: ARRAY OF CHAR; src: ARRAY OF CHAR;
w: ARRAY OF BYTE) ;
VAR
HighSrc,
HighDest,
c, i, j : CARDINAL ;
str : ARRAY [0..MaxErrorString] OF CHAR ;
p : POINTER TO CHAR ;
BEGIN
HighSrc := StrLen(src) ;
HighDest := HIGH(dest) ;
p := NIL ;
c := 0 ;
i := 0 ;
j := 0 ;
WHILE (i<HighSrc) AND (src[i]#nul) AND (j<HighDest) AND (src[i]#'%') DO
IF src[i]='\'
THEN
HandleEscape(dest, src, i, j, HighSrc, HighDest)
ELSE
dest[j] := src[i] ;
INC(i) ;
INC(j)
END
END ;
IF (i+1<HighSrc) AND (src[i]='%') AND (j<HighDest)
THEN
IF src[i+1]='s'
THEN
Cast(p, w) ;
WHILE (j<HighDest) AND (p^#nul) DO
dest[j] := p^ ;
INC(j) ;
INC(p)
END ;
IF j<HighDest
THEN
dest[j] := nul
END ;
j := StrLen(dest) ;
INC(i, 2)
ELSIF src[i+1]='d'
THEN
dest[j] := nul ;
Cast(c, w) ;
CardToStr(c, 0, str) ;
StrConCat(dest, str, dest) ;
j := StrLen(dest) ;
INC(i, 2)
ELSE
dest[j] := src[i] ;
INC(i) ;
INC(j)
END
END ;
(* and finish off copying src into dest *)
WHILE (i<HighSrc) AND (src[i]#nul) AND (j<HighDest) DO
IF src[i]='\'
THEN
HandleEscape(dest, src, i, j, HighSrc, HighDest)
ELSE
dest[j] := src[i] ;
INC(i) ;
INC(j)
END
END ;
IF j<HighDest
THEN
dest[j] := nul
END ;
END StringFormat1 ;
(*
FormatError - provides a orthoganal counterpart to the procedure below.
*)
PROCEDURE FormatError (a: ARRAY OF CHAR) ;
BEGIN
WriteString (StdErr, a)
END FormatError ;
(*
FormatError1 - generic error procedure taking standard format string
and single parameter.
*)
PROCEDURE FormatError1 (a: ARRAY OF CHAR; w: ARRAY OF BYTE) ;
VAR
s: ARRAY [0..MaxErrorString] OF CHAR ;
BEGIN
StringFormat1 (s, a, w) ;
FormatError (s)
END FormatError1 ;
(*
FormatError2 - generic error procedure taking standard format string
and two parameters.
*)
PROCEDURE FormatError2 (a: ARRAY OF CHAR;
w1, w2: ARRAY OF BYTE) ;
VAR
s: ARRAY [0..MaxErrorString] OF CHAR ;
BEGIN
StringFormat1 (s, a, w1) ;
FormatError1 (s, w2)
END FormatError2 ;
(*
CheckAccess - checks to see whether a file f has been
opened for read/write.
*)
PROCEDURE CheckAccess (f: File; use: FileUsage; towrite: BOOLEAN) ;
VAR
fd: FileDescriptor ;
BEGIN
IF f#Error
THEN
fd := GetIndice (FileInfo, f) ;
IF fd=NIL
THEN
IF f#StdErr
THEN
FormatError ('this file has probably been closed and not reopened successfully or alternatively never opened\n')
END ;
HALT
ELSE
WITH fd^ DO
IF (use=openedforwrite) AND (usage=openedforread)
THEN
FormatError1 ('this file (%s) has been opened for reading but is now being written\n',
name.address) ;
HALT
ELSIF (use=openedforread) AND (usage=openedforwrite)
THEN
FormatError1('this file (%s) has been opened for writing but is now being read\n',
name.address) ;
HALT
ELSIF state=connectionfailure
THEN
FormatError1('this file (%s) was not successfully opened\n',
name.address) ;
HALT
ELSIF towrite#output
THEN
IF output
THEN
FormatError1('this file (%s) was opened for writing but is now being read\n',
name.address) ;
HALT
ELSE
FormatError1('this file (%s) was opened for reading but is now being written\n',
name.address) ;
HALT
END
END
END
END
ELSE
FormatError('this file has not been opened successfully\n') ;
HALT
END
END CheckAccess ;
(*
ReadChar - returns a character read from file f.
Sensible to check with IsNoError or EOF after calling
this function.
*)
PROCEDURE ReadChar (f: File) : CHAR ;
VAR
ch: CHAR ;
BEGIN
CheckAccess (f, openedforread, FALSE) ;
IF BufferedRead (f, SIZE (ch), ADR (ch)) = VAL (INTEGER, SIZE (ch))
THEN
SetEndOfLine (f, ch) ;
RETURN ch
ELSE
RETURN nul
END
END ReadChar ;
(*
SetEndOfLine -
*)
PROCEDURE SetEndOfLine (f: File; ch: CHAR) ;
VAR
fd: FileDescriptor ;
BEGIN
CheckAccess(f, openedforread, FALSE) ;
IF f#Error
THEN
fd := GetIndice(FileInfo, f) ;
WITH fd^ DO
IF ch=nl
THEN
state := endofline
ELSE
state := successful
END
END
END
END SetEndOfLine ;
(*
UnReadChar - replaces a character, ch, back into file f.
This character must have been read by ReadChar
and it does not allow successive calls. It may
only be called if the previous read was successful
or end of file was seen.
If the state was previously endoffile then it
is altered to successful.
Otherwise it is left alone.
*)
PROCEDURE UnReadChar (f: File; ch: CHAR) ;
VAR
fd : FileDescriptor ;
n : CARDINAL ;
a, b: ADDRESS ;
BEGIN
CheckAccess(f, openedforread, FALSE) ;
IF f#Error
THEN
fd := GetIndice(FileInfo, f) ;
WITH fd^ DO
IF (state=successful) OR (state=endoffile) OR (state=endofline)
THEN
IF (buffer#NIL) AND (buffer^.valid)
THEN
WITH buffer^ DO
(* we assume that a ReadChar has occurred, we will check just in case. *)
IF state=endoffile
THEN
position := MaxBufferLength ;
left := 0 ;
filled := 0 ;
state := successful
END ;
IF position>0
THEN
DEC(position) ;
INC(left) ;
contents^[position] := ch ;
ELSE
(* position=0 *)
(* if possible make room and store ch *)
IF filled=size
THEN
FormatError1('performing too many UnReadChar calls on file (%d)\n', f)
ELSE
n := filled-position ;
b := ADR(contents^[position]) ;
a := ADR(contents^[position+1]) ;
a := memcpy(a, b, n) ;
INC(filled) ;
contents^[position] := ch ;
END
END
END
END
ELSE
FormatError1('UnReadChar can only be called if the previous read was successful or end of file, error on file (%d)\n', f)
END
END
END
END UnReadChar ;
(*
ReadAny - reads HIGH (a) + 1 bytes into, a. All input
is fully buffered, unlike ReadNBytes and thus is more
suited to small reads.
*)
PROCEDURE ReadAny (f: File; VAR a: ARRAY OF BYTE) ;
BEGIN
CheckAccess(f, openedforread, FALSE) ;
IF BufferedRead (f, HIGH (a) + 1, ADR (a)) = VAL (INTEGER, HIGH (a) + 1)
THEN
SetEndOfLine (f, a[HIGH(a)])
END
END ReadAny ;
(*
EOF - tests to see whether a file, f, has reached end of file.
*)
PROCEDURE EOF (f: File) : BOOLEAN ;
VAR
fd: FileDescriptor ;
BEGIN
CheckAccess(f, openedforread, FALSE) ;
IF f#Error
THEN
fd := GetIndice(FileInfo, f) ;
IF fd#NIL
THEN
RETURN( fd^.state=endoffile )
END
END ;
RETURN( TRUE )
END EOF ;
(*
EOLN - tests to see whether a file, f, is upon a newline.
It does NOT consume the newline.
*)
PROCEDURE EOLN (f: File) : BOOLEAN ;
VAR
ch: CHAR ;
fd: FileDescriptor ;
BEGIN
CheckAccess(f, openedforread, FALSE) ;
(*
we will read a character and then push it back onto the input stream,
having noted the file status, we also reset the status.
*)
IF f#Error
THEN
fd := GetIndice(FileInfo, f) ;
IF fd#NIL
THEN
IF (fd^.state=successful) OR (fd^.state=endofline)
THEN
ch := ReadChar(f) ;
IF (fd^.state=successful) OR (fd^.state=endofline)
THEN
UnReadChar(f, ch)
END ;
RETURN( ch=nl )
END
END
END ;
RETURN( FALSE )
END EOLN ;
(*
WasEOLN - tests to see whether a file, f, has just seen a newline.
*)
PROCEDURE WasEOLN (f: File) : BOOLEAN ;
VAR
fd: FileDescriptor ;
BEGIN
CheckAccess(f, openedforread, FALSE) ;
IF f=Error
THEN
RETURN FALSE
ELSE
fd := GetIndice(FileInfo, f) ;
RETURN( (fd#NIL) AND (fd^.state=endofline) )
END
END WasEOLN ;
(*
WriteLine - writes out a linefeed to file, f.
*)
PROCEDURE WriteLine (f: File) ;
BEGIN
WriteChar(f, nl)
END WriteLine ;
(*
WriteNBytes - writes nBytes from memory area src to a file
returning the number of bytes actually written.
This function will flush the buffer and then
write the nBytes using a direct write from libc.
It is ideal for large writes.
*)
PROCEDURE WriteNBytes (f: File; nBytes: CARDINAL; src: ADDRESS) : CARDINAL ;
VAR
total: INTEGER ;
fd : FileDescriptor ;
BEGIN
CheckAccess(f, openedforwrite, TRUE) ;
FlushBuffer(f) ;
IF f#Error
THEN
fd := GetIndice(FileInfo, f) ;
IF fd#NIL
THEN
WITH fd^ DO
total := write(unixfd, src, INTEGER(nBytes)) ;
IF total<0
THEN
state := failed ;
RETURN( 0 )
ELSE
INC(abspos, CARDINAL(total)) ;
IF buffer#NIL
THEN
buffer^.bufstart := abspos
END ;
RETURN( CARDINAL(total) )
END
END
END
END ;
RETURN( 0 )
END WriteNBytes ;
(*
BufferedWrite - will write, nBytes, through the buffer.
Similar to WriteNBytes, but this function will always
write into the buffer before copying into memory.
Useful when performing small writes.
*)
PROCEDURE BufferedWrite (f: File; nBytes: CARDINAL; src: ADDRESS) : INTEGER ;
VAR
dest : ADDRESS ;
total,
n : INTEGER ;
p : POINTER TO BYTE ;
fd : FileDescriptor ;
BEGIN
IF f # Error
THEN
fd := GetIndice (FileInfo, f) ;
IF fd#NIL
THEN
total := 0 ; (* how many bytes have we read *)
WITH fd^ DO
IF buffer # NIL
THEN
WITH buffer^ DO
WHILE nBytes > 0 DO
(* place into the buffer first *)
IF left > 0
THEN
IF nBytes = 1
THEN
(* too expensive to call memcpy for 1 character *)
p := src ;
contents^[position] := p^ ;
DEC (left) ; (* reduce space *)
INC (position) ; (* move onwards n byte *)
INC (total) ;
RETURN( total )
ELSE
n := Min (left, nBytes) ;
dest := address ;
INC (dest, position) ;
p := memcpy (dest, src, CARDINAL (n)) ;
DEC (left, n) ; (* remove consumed bytes *)
INC (position, n) ; (* move onwards n bytes *)
(* move ready for further writes *)
INC (src, n) ;
DEC (nBytes, n) ; (* reduce the amount for future writes *)
INC (total, n)
END
ELSE
FlushBuffer (f) ;
IF (state#successful) AND (state#endofline)
THEN
nBytes := 0
END
END
END
END ;
RETURN( total )
END
END
END
END ;
RETURN( -1 )
END BufferedWrite ;
(*
FlushBuffer - flush contents of file, f.
*)
PROCEDURE FlushBuffer (f: File) ;
VAR
fd: FileDescriptor ;
BEGIN
IF f#Error
THEN
fd := GetIndice(FileInfo, f) ;
IF fd#NIL
THEN
WITH fd^ DO
IF output AND (buffer#NIL)
THEN
WITH buffer^ DO
IF (position=0) OR (write(unixfd, address, position)=VAL(INTEGER, position))
THEN
INC(abspos, position) ;
bufstart := abspos ;
position := 0 ;
filled := 0 ;
left := size
ELSE
state := failed
END
END
END
END
END
END
END FlushBuffer ;
(*
WriteAny - writes HIGH (a) + 1 bytes onto, file, f. All output
is fully buffered, unlike WriteNBytes and thus is more
suited to small writes.
*)
PROCEDURE WriteAny (f: File; VAR a: ARRAY OF BYTE) ;
BEGIN
CheckAccess (f, openedforwrite, TRUE) ;
IF BufferedWrite (f, HIGH (a) + 1, ADR (a)) = VAL (INTEGER, HIGH (a) + 1)
THEN
END
END WriteAny ;
(*
WriteChar - writes a single character to file, f.
*)
PROCEDURE WriteChar (f: File; ch: CHAR) ;
BEGIN
CheckAccess (f, openedforwrite, TRUE) ;
IF BufferedWrite (f, SIZE (ch), ADR (ch)) = VAL (INTEGER, SIZE (ch))
THEN
END
END WriteChar ;
(*
WriteCardinal - writes a CARDINAL to file, f.
It writes the binary image of the cardinal
to file, f.
*)
PROCEDURE WriteCardinal (f: File; c: CARDINAL) ;
BEGIN
WriteAny(f, c)
END WriteCardinal ;
(*
ReadCardinal - reads a CARDINAL from file, f.
It reads a binary image of a CARDINAL
from a file, f.
*)
PROCEDURE ReadCardinal (f: File) : CARDINAL ;
VAR
c: CARDINAL ;
BEGIN
ReadAny(f, c) ;
RETURN( c )
END ReadCardinal ;
(*
ReadString - reads a string from file, f, into string, a.
It terminates the string if HIGH is reached or
if a newline is seen or an error occurs.
*)
PROCEDURE ReadString (f: File; VAR a: ARRAY OF CHAR) ;
VAR
high,
i : CARDINAL ;
ch : CHAR ;
BEGIN
CheckAccess(f, openedforread, FALSE) ;
high := HIGH(a) ;
i := 0 ;
REPEAT
ch := ReadChar(f) ;
IF i<=high
THEN
IF (ch=nl) OR (NOT IsNoError(f)) OR EOF(f)
THEN
a[i] := nul ;
INC(i)
ELSE
a[i] := ch ;
INC(i)
END
END
UNTIL (ch=nl) OR (i>high) OR (NOT IsNoError(f)) OR EOF(f)
END ReadString ;
(*
SetPositionFromBeginning - sets the position from the beginning of the file.
*)
PROCEDURE SetPositionFromBeginning (f: File; pos: LONGINT) ;
VAR
offset: LONGINT ;
fd : FileDescriptor ;
BEGIN
IF f#Error
THEN
fd := GetIndice(FileInfo, f) ;
IF fd#NIL
THEN
WITH fd^ DO
(* always force the lseek, until we are confident that abspos is always correct,
basically it needs some hard testing before we should remove the OR TRUE. *)
IF (abspos#pos) OR TRUE
THEN
FlushBuffer(f) ;
IF buffer#NIL
THEN
WITH buffer^ DO
IF output
THEN
left := size
ELSE
left := 0
END ;
position := 0 ;
filled := 0
END
END ;
offset := lseek (unixfd, VAL (COFF_T, pos), SeekSet ()) ;
IF (offset>=0) AND (pos=offset)
THEN
abspos := pos
ELSE
state := failed ;
abspos := 0
END ;
IF buffer#NIL
THEN
buffer^.valid := FALSE ;
buffer^.bufstart := abspos
END
END
END
END
END
END SetPositionFromBeginning ;
(*
SetPositionFromEnd - sets the position from the end of the file.
*)
PROCEDURE SetPositionFromEnd (f: File; pos: LONGINT) ;
VAR
offset: LONGINT ;
fd : FileDescriptor ;
BEGIN
IF f#Error
THEN
fd := GetIndice(FileInfo, f) ;
IF fd#NIL
THEN
WITH fd^ DO
FlushBuffer(f) ;
IF buffer#NIL
THEN
WITH buffer^ DO
IF output
THEN
left := size
ELSE
left := 0
END ;
position := 0 ;
filled := 0
END
END ;
offset := lseek (unixfd, VAL (COFF_T, pos), SeekEnd ()) ;
IF offset>=0
THEN
abspos := offset ;
ELSE
state := failed ;
abspos := 0 ;
offset := 0
END ;
IF buffer#NIL
THEN
buffer^.valid := FALSE ;
buffer^.bufstart := offset
END
END
END
END
END SetPositionFromEnd ;
(*
FindPosition - returns the current absolute position in file, f.
*)
PROCEDURE FindPosition (f: File) : LONGINT ;
VAR
fd: FileDescriptor ;
BEGIN
IF f#Error
THEN
fd := GetIndice(FileInfo, f) ;
IF fd#NIL
THEN
WITH fd^ DO
IF (buffer=NIL) OR (NOT buffer^.valid)
THEN
RETURN( abspos )
ELSE
WITH buffer^ DO
RETURN( bufstart+VAL(LONGINT, position) )
END
END
END
END
END ;
RETURN( 0 )
END FindPosition ;
(*
GetFileName - assigns, a, with the filename associated with, f.
*)
PROCEDURE GetFileName (f: File; VAR a: ARRAY OF CHAR) ;
VAR
i : CARDINAL ;
p : POINTER TO CHAR ;
fd: FileDescriptor ;
BEGIN
IF f#Error
THEN
fd := GetIndice(FileInfo, f) ;
IF fd=NIL
THEN
FormatError('this file has probably been closed and not reopened successfully or alternatively never opened\n') ;
HALT
ELSE
WITH fd^.name DO
IF address=NIL
THEN
StrCopy('', a)
ELSE
p := address ;
i := 0 ;
WHILE (p^#nul) AND (i<=HIGH(a)) DO
a[i] := p^ ;
INC(p) ;
INC(i)
END
END
END
END
END
END GetFileName ;
(*
getFileName - returns the address of the filename associated with, f.
*)
PROCEDURE getFileName (f: File) : ADDRESS ;
VAR
fd: FileDescriptor ;
BEGIN
IF f#Error
THEN
fd := GetIndice(FileInfo, f) ;
IF fd=NIL
THEN
FormatError('this file has probably been closed and not reopened successfully or alternatively never opened\n') ;
HALT
ELSE
RETURN fd^.name.address
END
END ;
RETURN NIL
END getFileName ;
(*
getFileNameLength - returns the number of characters associated with filename, f.
*)
PROCEDURE getFileNameLength (f: File) : CARDINAL ;
VAR
fd: FileDescriptor ;
BEGIN
IF f#Error
THEN
fd := GetIndice(FileInfo, f) ;
IF fd=NIL
THEN
FormatError('this file has probably been closed and not reopened successfully or alternatively never opened\n') ;
HALT
ELSE
RETURN fd^.name.size
END
END ;
RETURN 0
END getFileNameLength ;
(*
PreInitialize - preinitialize the file descriptor.
*)
PROCEDURE PreInitialize (f: File; fname: ARRAY OF CHAR;
state: FileStatus; use: FileUsage;
towrite: BOOLEAN; osfd: INTEGER; bufsize: CARDINAL) ;
VAR
fd, fe: FileDescriptor ;
BEGIN
IF InitializeFile(f, ADR(fname), StrLen(fname), state, use, towrite, bufsize)=f
THEN
fd := GetIndice(FileInfo, f) ;
IF f=Error
THEN
fe := GetIndice(FileInfo, StdErr) ;
IF fe=NIL
THEN
HALT
ELSE
fd^.unixfd := fe^.unixfd (* the error channel *)
END
ELSE
fd^.unixfd := osfd
END
ELSE
HALT
END
END PreInitialize ;
(*
FlushOutErr - flushes, StdOut, and, StdErr.
It is also called when the application calls M2RTS.Terminate.
(which is automatically placed in program modules by the GM2
scaffold).
*)
PROCEDURE FlushOutErr ;
BEGIN
IF IsNoError(StdOut)
THEN
FlushBuffer(StdOut)
END ;
IF IsNoError(StdErr)
THEN
FlushBuffer(StdErr)
END
END FlushOutErr ;
(*
Init - initialize the modules, global variables.
*)
PROCEDURE Init ;
BEGIN
FileInfo := InitIndex(0) ;
Error := 0 ;
PreInitialize(Error , 'error' , toomanyfilesopen, unused , FALSE, -1, 0) ;
StdIn := 1 ;
PreInitialize(StdIn , '<stdin>' , successful , openedforread , FALSE, 0, MaxBufferLength) ;
StdOut := 2 ;
PreInitialize(StdOut , '<stdout>', successful , openedforwrite, TRUE, 1, MaxBufferLength) ;
StdErr := 3 ;
PreInitialize(StdErr , '<stderr>', successful , openedforwrite, TRUE, 2, MaxBufferLength) ;
IF NOT InstallTerminationProcedure(FlushOutErr)
THEN
HALT
END
END Init ;
BEGIN
Init
FINALLY
FlushOutErr
END FIO.