home *** CD-ROM | disk | FTP | other *** search
- (***************************************************************************
-
- $RCSfile: Files.mod $
- Description: Port of the Project Oberon Files module
-
- Created by: J. Gutknecht
- Ported by: fjc (Frank Copeland)
- $Revision: 1.6 $
- $Author: fjc $
- $Date: 1994/08/08 16:41:14 $
-
- Copyright © 1990-1993, ETH Zuerich
- Copyright © 1994, Frank Copeland.
- This file is part of the Oberon-A Library.
- See Oberon-A.doc for conditions of use and distribution.
-
- Log entries are at the end of the file.
-
- ***************************************************************************)
-
- MODULE Files;
-
- (*
- Interface notes
- ===============
-
- This module attempts to reproduce the behaviour of the Project Oberon
- module as closely as possible, with two major exceptions. AmigaDOS does
- not allow multiple simultaneous access to a file. Until I can work out
- some way of recycling AmigaDOS FileHandles, only one user will be allowed
- per file. This implementation of Oberon does not include a resource
- tracker. This means that files must be explicitly closed, using either
- Register (), Close () or Purge (). File variables remain allocated after
- calls to these procedures, but cannot be used again; they should be
- explicitly de-allocated with SYSTEM.DISPOSE ().
-
- Implementation Notes
- ====================
-
- This module is built as a layer on top of AmigaDOS. Old() attempts to
- open the named file with a read/write (but not exclusive) lock. New()
- creates a temporary file. Both will fail if they attempt to open an
- interactive file. Register() deletes any existing file and renames the
- temporary file. Purge() deletes the file. Register(), Close() and
- Purge() explicitly close the AmigaDOS file but do not de-allocate the File
- variable; this allows the programmer to check for any errors that occur
- during the close operation.
-
- [TBD]
-
- *)
-
- (*
- ** $C= CaseChk $I= IndexChk $L= LongAdr $N= NilChk
- ** $P= PortableCode $R= RangeChk $S= StackChk $T= TypeChk
- ** $V= OvflChk $Z= ZeroVars
- *)
-
- IMPORT SYS := SYSTEM, Dos, DosUtil, Str := Strings, Oberon;
-
-
- (* --- Public declarations ---------------------------------------------- *)
-
-
- TYPE
- File * = POINTER TO Handle; (* Note that file.dosError is public. *)
- Buffer = POINTER TO BufferRecord;
-
- Rider * = RECORD
- eof * : BOOLEAN;
- res * : LONGINT; (* Set to the error code returned by AmigaDOS *)
- file : File;
- apos : LONGINT;
- buf : Buffer;
- bpos : INTEGER;
- END; (* Rider *)
-
-
- (* --- Private declarations and procedures ------------------------------ *)
-
- (*
- These definitions are taken from the Project Oberon module FileDir, which
- does not exist in this implementation.
- *)
-
-
- CONST
- FnLength = 256; (* for AmigaDOS, = 32 for Project Oberon *)
- SectorSize = 1024;
-
- TYPE
- FileName = ARRAY FnLength OF CHAR;
- DataSector = ARRAY SectorSize OF SYS.BYTE;
-
-
- (*------------------------------------*)
-
-
- CONST
- MaxBufs = 4;
-
- TYPE
- DiskAdr = LONGINT;
-
- Handle = RECORD
- len : LONGINT;
- nofbufs : INTEGER;
- firstbuf : Buffer;
- name : FileName;
- time, date : LONGINT;
- fileHandle : Dos.FileHandlePtr;
- dosError * : LONGINT; (* The AmigaDOS error code for the most
- * recent operation
- *)
- tempKey : LONGINT;
- next : File;
- END; (* Handle *)
-
- BufferRecord = RECORD
- apos : LONGINT;
- lim : INTEGER;
- mod : BOOLEAN;
- next : Buffer;
- data : DataSector;
- END; (* BufferRecord *)
-
- VAR
- tempKey : LONGINT; (* Used to generate temporary file names. *)
- files : File;
-
- (*------------------------------------*)
- PROCEDURE Unlink (f : File);
-
- VAR f0 : File;
-
- BEGIN (* Unlink *)
- IF f # NIL THEN
- IF files # NIL THEN
- IF f = files THEN
- files := files.next
- ELSE
- f0 := files;
- WHILE (f0.next # NIL) & (f0.next # f) DO
- f0 := f0.next
- END;
- IF f0.next = f THEN f0.next := f.next END;
- END;
- END;
- f.next := NIL
- END;
- END Unlink;
-
- (*------------------------------------*)
- PROCEDURE ReadBuf (f : File; buf : Buffer; pos : LONGINT);
-
- VAR res : LONGINT;
-
- BEGIN (* ReadBuf *)
- res := Dos.base.Seek (f.fileHandle, pos, Dos.offsetBeginning);
- IF res # -1 THEN
- (* ASSERT (buf # NIL, 137); *)
- buf.lim := SHORT (Dos.base.Read (f^.fileHandle, buf.data, SectorSize));
- buf.apos := pos;
- buf.mod := FALSE;
- ELSE
- f.dosError := Dos.base.IoErr ()
- END
- END ReadBuf;
-
-
- (*------------------------------------*)
- PROCEDURE WriteBuf (f : File; buf : Buffer);
-
- VAR res : LONGINT;
-
- BEGIN (* WriteBuf *)
- (* ASSERT (buf # NIL, 137); *)
- res := Dos.base.Seek (f.fileHandle, buf.apos, Dos.offsetBeginning);
- IF res # -1 THEN
- res := Dos.base.Write (f.fileHandle, buf.data, buf.lim);
- IF res = buf.lim THEN
- buf.mod := FALSE;
- ELSE
- f.dosError := Dos.base.IoErr ();
- END
- ELSE
- f.dosError := Dos.base.IoErr ();
- END
- END WriteBuf;
-
-
- (*------------------------------------*)
- PROCEDURE GetBuf (f : File; pos : LONGINT) : Buffer;
-
- VAR buf, last, next : Buffer;
-
- BEGIN (* GetBuf *)
- buf := f.firstbuf;
- LOOP
- (* ASSERT (buf # NIL, 137); *)
- IF buf.apos = pos THEN EXIT END;
- IF buf.next = f.firstbuf THEN
- last := buf;
- IF f.nofbufs < MaxBufs THEN (* allocate new buffer *)
- NEW (buf);
- (* ASSERT (buf # NIL, 137); *)
- INC (f.nofbufs);
- ELSE (* take one of the buffers (assuming more than one) *)
- buf := f.firstbuf; f.firstbuf := buf.next; last.next := buf.next;
- IF buf.mod THEN WriteBuf (f, buf) END
- END;
- IF pos < f.firstbuf.apos THEN
- f.firstbuf := buf
- ELSIF pos < last.apos THEN
- WHILE last.next.apos < pos DO last := last.next END;
- END;
- buf.next := last.next; last.next := buf;
- buf.apos := pos; buf.lim := 0; buf.mod := FALSE;
- IF pos < f.len THEN ReadBuf (f, buf, pos) END;
- EXIT
- END;
- buf := buf.next
- END; (* LOOP *)
- RETURN buf;
- END GetBuf;
-
-
- (*------------------------------------*)
- PROCEDURE Unbuffer (f : File);
-
- VAR buf : Buffer;
-
- BEGIN (* Unbuffer *)
- buf := f.firstbuf;
- REPEAT
- (* ASSERT (buf # NIL, 137); *)
- IF buf.mod THEN WriteBuf (f, buf) END;
- buf := buf.next
- UNTIL buf = f.firstbuf
- END Unbuffer;
-
-
- (*------------------------------------*)
- PROCEDURE MakeTempName (VAR name : ARRAY OF CHAR; key : LONGINT);
-
- VAR i : INTEGER; digit : LONGINT;
-
- BEGIN (* MakeTempName *)
- COPY ("T:", name);
- i := 10;
- WHILE i > 0 DO
- digit := key MOD 10H; IF digit >= 10 THEN INC (digit, 7) END;
- DEC (i); name [i] := CHR (digit + ORD ("0")); key := key DIV 10H
- END; (* WHILE *)
- name [10] := 0X; Str.Append (name, ".tmp")
- END MakeTempName;
-
-
- (* --- Public procedures ------------------------------------------------ *)
-
-
- (*------------------------------------*)
- (* $D- disable copying of open arrays *)
- PROCEDURE Delete * (name : ARRAY OF CHAR; VAR res : LONGINT);
-
- BEGIN (* Delete *)
- IF Dos.base.DeleteFile (name) THEN
- res := 0
- ELSE
- res := Dos.base.IoErr ();
- IF res = Dos.errorObjectNotFound THEN res := 0 END
- END; (* ELSE *)
- END Delete;
-
-
- (*------------------------------------*)
- (* $D- disable copying of open arrays *)
- PROCEDURE Rename * (old, new : ARRAY OF CHAR; VAR res : LONGINT);
-
- BEGIN (* Rename *)
- IF Dos.base.Rename (old, new) THEN
- res := 0
- ELSE
- res := Dos.base.IoErr ()
- END
- END Rename;
-
-
- (*------------------------------------*)
- PROCEDURE Old * (name : ARRAY OF CHAR) : File;
- (*
- [TBD]
-
- * This really needs better error handling. I expect testing will
- force me to provide it :-)
-
- * Implement check for interactive files.
- *)
-
- VAR
- f : File; fl : Dos.FileLockPtr; fh : Dos.FileHandlePtr;
- fib : Dos.FileInfoBlockPtr; buf : Buffer;
-
- (* $D- disable copying of open arrays *)
- BEGIN (* Old *)
- f := NIL;
- fl := Dos.base.Lock (name, Dos.sharedLock);
- IF fl # NIL THEN
- fh := Dos.base.Open (name, Dos.modeOldFile);
- IF fh # NIL THEN
- NEW (fib);
- IF fib # NIL THEN
- IF Dos.base.Examine (fl, fib^) THEN
- Dos.base.UnLock (fl);
- NEW (buf);
- (* ASSERT (buf # NIL, 137); *)
- buf.apos := 0; buf.next := buf; buf.mod := FALSE;
- NEW (f);
- (* ASSERT (f # NIL, 137); *)
- f.len := fib.size;
- Oberon.ADOS2OberonTime (fib.date, f.time, f.date);
- IF f.len > SectorSize THEN buf.lim := SectorSize
- ELSE buf.lim := SHORT (f.len)
- END;
- f.firstbuf := buf; f.nofbufs := 1; f.name [0] := 0X;
- f.fileHandle := fh; f.dosError := 0; f.tempKey := 0;
- f.next := files; files := f;
- ReadBuf (f, buf, 0);
- ELSE
- SYS.PUTREG (0, Dos.base.Close (fh));
- END; (* IF *)
- SYS.DISPOSE (fib);
- ELSE
- SYS.PUTREG (0, Dos.base.Close (fh));
- END; (* IF *)
- END; (* IF *)
- END; (* IF *)
- RETURN f;
- END Old;
-
- (*------------------------------------*)
- PROCEDURE New * (name : ARRAY OF CHAR) : File;
- (*
- [TBD]
-
- * This really needs better error handling. I expect testing will
- force me to provide it :-)
-
- * Implement check for interactive files.
- *)
-
- VAR
- tempName : FileName; f : File; fh : Dos.FileHandlePtr; buf : Buffer;
- ch : CHAR; i : INTEGER;
-
- (* $D- disable copying of open arrays *)
- BEGIN (* New *)
- f := NIL;
- IF name [0] = 0X THEN
- REPEAT MakeTempName (tempName, tempKey); INC (tempKey)
- UNTIL ~DosUtil.FileExists (tempName);
- fh := Dos.base.Open (tempName, Dos.modeNewFile);
- ELSE
- COPY (name, tempName); Str.Append (tempName, "$tmp*");
- i := SHORT (Str.Length (tempName)) - 1; ch := "A";
- REPEAT tempName [i] := ch; ch := CHR (ORD (ch) + 1)
- UNTIL ~DosUtil.FileExists (tempName);
- fh := Dos.base.Open (tempName, Dos.modeNewFile);
- END; (* ELSE *)
- IF fh # NIL THEN
- NEW (buf);
- (* ASSERT (buf # NIL, 137); *)
- buf.apos := 0; buf.next := buf; buf.mod := TRUE; buf.lim := 0;
- NEW (f);
- (* ASSERT (f # NIL, 137); *)
- Oberon.GetClock (f.time, f.date);
- f.len := 0; f.firstbuf := buf; f.nofbufs := 1; COPY (name, f.name);
- f.fileHandle := fh; f.dosError := 0;
- IF name [0] = 0X THEN f.tempKey := tempKey-1
- ELSE f.tempKey := ORD (ch) - 1
- END;
- f.next := files; files := f;
- ReadBuf (f, buf, 0);
- END; (* IF *)
- RETURN f;
- END New;
-
-
- (*------------------------------------*)
- PROCEDURE Register * (f : File);
-
- VAR tempName, bkpName : FileName; i : INTEGER;
-
- BEGIN (* Register *)
- IF (f # NIL) & (f.fileHandle # NIL) THEN
- Unlink (f); Unbuffer (f);
- IF Dos.base.Close (f.fileHandle) THEN
- f.dosError := 0;
- IF f.name [0] = 0X THEN
- MakeTempName (tempName, f.tempKey);
- Delete (tempName, f.dosError)
- ELSE
- COPY (f.name, tempName); Str.Append (tempName, "$tmp*");
- i := SHORT (Str.Length (tempName)) - 1;
- tempName [i] := CHR (f.tempKey);
- COPY (f.name, bkpName); Str.Append (bkpName, "$bak");
- Rename (f.name, bkpName, f.dosError);
- IF (f.dosError = 0) THEN
- Rename (tempName, f.name, f.dosError);
- IF f.dosError = 0 THEN
- Delete (bkpName, f.dosError)
- END; (* IF *)
- ELSIF (f.dosError = Dos.errorObjectNotFound) THEN
- Rename (tempName, f.name, f.dosError);
- END; (* IF *)
- END; (* IF *)
- ELSE f.dosError := Dos.base.IoErr ()
- END;
- END; (* IF *)
- END Register;
-
-
- (*------------------------------------*)
- PROCEDURE Close * (f : File);
-
- BEGIN (* Close *)
- IF f # NIL THEN
- Unlink (f); Unbuffer (f);
- IF Dos.base.Close (f.fileHandle) THEN f.dosError := 0
- ELSE f.dosError := Dos.base.IoErr ()
- END; (* ELSE *)
- END; (* IF *)
- END Close;
-
-
- (*------------------------------------*)
- PROCEDURE Purge * (f : File);
-
- VAR tempName : FileName; i : INTEGER;
-
- BEGIN (* Purge *)
- IF f # NIL THEN
- Unlink (f); Unbuffer (f);
- IF Dos.base.Close (f.fileHandle) THEN
- f.dosError := 0;
- IF f.name [0] = 0X THEN
- MakeTempName (tempName, f.tempKey);
- Delete (tempName, f.dosError)
- ELSE
- COPY (f.name, tempName); Str.Append (tempName, "$tmp*");
- i := SHORT (Str.Length (tempName)) - 1;
- tempName [i] := CHR (f.tempKey);
- Delete (tempName, f.dosError)
- END; (* ELSE *)
- ELSE f.dosError := Dos.base.IoErr ()
- END;
- END; (* IF *)
- END Purge;
-
-
- (*------------------------------------*)
- PROCEDURE Length * (f : File) : LONGINT;
-
- BEGIN (* Length *)
- (* ASSERT (f # NIL, 137); *)
- RETURN f.len
- END Length;
-
-
- (*------------------------------------*)
- PROCEDURE GetDate * (f : File; VAR t, d : LONGINT);
-
- BEGIN (* GetDate *)
- (* ASSERT (f # NIL, 137); *)
- t := f.time; d := f.date
- END GetDate;
-
-
- (*------------------------------------*)
- PROCEDURE Set * (VAR r : Rider; f : File; pos : LONGINT);
-
- BEGIN (* Set *)
- r.eof := FALSE; r.res := 0; r.file := f;
- IF f # NIL THEN
- IF pos < 0 THEN
- r.apos := 0; r.bpos := 0
- ELSE
- r.bpos := SHORT (pos MOD SectorSize); r.apos := pos - r.bpos
- END;
- r.buf := f.firstbuf
- END
- END Set;
-
-
- (*------------------------------------*)
- PROCEDURE Read * (VAR r : Rider; VAR x : SYS.BYTE);
-
- VAR buf : Buffer;
-
- BEGIN (* Read *)
- (* ASSERT (r.file # NIL, 137); *)
- (* ASSERT (r.buf # NIL, 137); *)
- IF r.apos # r.buf.apos THEN r.buf := GetBuf (r.file, r.apos) END;
- IF r.bpos < r.buf.lim THEN
- x := r.buf.data [r.bpos]; INC (r.bpos)
- ELSIF (r.apos + SectorSize) < r.file.len THEN
- INC (r.apos, SectorSize);
- r.buf := GetBuf (r.file, r.apos);
- x := r.buf.data [0]; r.bpos := 1
- ELSE
- x := 0X; r.eof := TRUE
- END
- END Read;
-
-
- (*------------------------------------*)
- PROCEDURE ReadBytes *
- ( VAR r : Rider; VAR x : ARRAY OF SYS.BYTE; n : LONGINT);
-
- VAR src, dst, m : LONGINT;
- buf : Buffer;
-
- BEGIN (* ReadBytes *)
- (* ASSERT (r.file # NIL, 137); *)
- (* ASSERT (r.buf # NIL, 137); *)
- dst := SYS.VAL (LONGINT, SYS.ADR (x));
- IF LEN (x) < n THEN HALT (25) END;
- IF r.apos # r.buf.apos THEN r.buf := GetBuf (r.file, r.apos) END;
- LOOP
- IF n <= 0 THEN EXIT END;
- src := SYS.VAL (LONGINT, SYS.ADR(r.buf.data));
- INC (src, r.bpos); m := r.bpos + n;
- IF m <= r.buf.lim THEN
- SYS.MOVE (src, dst, n); r.bpos := SHORT (m); r.res := 0;
- EXIT
- ELSIF r.buf.lim = SectorSize THEN
- m := r.buf.lim - r.bpos;
- IF m > 0 THEN
- SYS.MOVE (src, dst, m); INC (dst, m); DEC (n, m)
- END;
- IF r.apos < r.file.len THEN
- INC (r.apos, SectorSize);
- r.bpos := 0; r.buf := GetBuf (r.file, r.apos);
- ELSE
- r.res := n; r.eof := TRUE; EXIT
- END; (* ELSE *)
- ELSE
- m := r.buf.lim - r.bpos;
- IF m > 0 THEN
- SYS.MOVE (src, dst, m); r.bpos := r.buf.lim
- END;
- r.res := n - m; r.eof := TRUE; EXIT
- END; (* ELSE *)
- END; (* LOOP *)
- END ReadBytes;
-
-
- (*------------------------------------*)
- PROCEDURE Write * (VAR r : Rider; x : SYS.BYTE);
-
- VAR f : File; buf : Buffer;
-
- BEGIN (* Write *)
- (* ASSERT (r.file # NIL, 137); *)
- (* ASSERT (r.buf # NIL, 137); *)
- IF r.apos # r.buf.apos THEN r.buf := GetBuf (r.file, r.apos) END;
- IF r.bpos >= r.buf.lim THEN
- IF r.bpos < SectorSize THEN
- INC (r.buf.lim); INC (r.file.len)
- ELSE
- f := r.file; INC (r.apos, SectorSize);
- r.buf := GetBuf (f, r.apos);
- IF r.apos >= f.len THEN r.buf.lim := 1; f.len := r.apos END;
- r.bpos := 0
- END
- END;
- r.buf.data [r.bpos] := x; INC (r.bpos); r.buf.mod := TRUE
- END Write;
-
-
- (*------------------------------------*)
- PROCEDURE WriteBytes *
- (VAR r : Rider; VAR x : ARRAY OF SYS.BYTE; n : LONGINT);
-
- VAR src, dst, m : LONGINT; f : File; buf : Buffer;
-
- BEGIN (* WriteBytes *)
- (* ASSERT (r.file # NIL, 137); *)
- (* ASSERT (r.buf # NIL, 137); *)
- src := SYS.VAL (LONGINT, SYS.ADR (x));
- IF LEN (x) < n THEN HALT (25) END;
- IF r.apos # r.buf.apos THEN r.buf := GetBuf (r.file, r.apos) END;
- LOOP
- IF n <= 0 THEN EXIT END;
- r.buf.mod := TRUE;
- dst := SYS.VAL (LONGINT, SYS.ADR(r.buf.data)); INC (dst, r.bpos);
- m := r.bpos + n;
- IF m <= r.buf.lim THEN
- SYS.MOVE (src, dst, n); r.bpos := SHORT (m); EXIT
- ELSIF m <= SectorSize THEN
- SYS.MOVE (src, dst, n); r.bpos := SHORT (m);
- INC (r.file.len, n); r.buf.lim := SHORT (m); EXIT
- ELSE
- m := SectorSize - r.bpos;
- IF m > 0 THEN
- SYS.MOVE (src, dst, m); INC (src, m); DEC (n, m);
- INC (r.buf.lim, SHORT (m))
- END;
- f := r.file; INC (r.apos, SectorSize);
- r.bpos := 0; r.buf := GetBuf (f, r.apos);
- IF r.apos >= f.len THEN r.buf.lim := 0; f.len := r.apos END;
- END;
- END; (* LOOP *)
- END WriteBytes;
-
- (*------------------------------------*)
- PROCEDURE Pos * (VAR r : Rider) : LONGINT;
-
- BEGIN (* Pos *)
- RETURN r.apos + r.bpos
- END Pos;
-
-
- (*------------------------------------*)
- PROCEDURE Base * (VAR r : Rider) : File;
-
- BEGIN (* Base *)
- RETURN r.file;
- END Base;
-
-
- (*------------------------------------*)
- PROCEDURE InitTempKey ();
-
- VAR time, date : LONGINT;
-
- BEGIN (* InitTempKey *)
- Oberon.GetClock (time, date); tempKey := date * 10000H + time;
- IF tempKey = 0 THEN INC (tempKey) END
- END InitTempKey;
-
- (*------------------------------------*)
- PROCEDURE* Cleanup ();
-
- BEGIN
- WHILE files # NIL DO
- IF files.fileHandle # NIL THEN
- Unbuffer (files); Dos.base.OldClose (files.fileHandle)
- END;
- files := files.next
- END;
- END Cleanup;
-
- BEGIN
- InitTempKey();
- files := NIL; SYS.SETCLEANUP (Cleanup)
- END Files.
-
- (***************************************************************************
-
- $Log: Files.mod $
- Revision 1.6 1994/08/08 16:41:14 fjc
- Release 1.4
-
- Revision 1.5 1994/06/14 02:14:31 fjc
- - Updated for release
-
- Revision 1.4 1994/06/09 14:12:41 fjc
- - Incorporated changes to Amiga interface
-
- Revision 1.3 1994/06/04 16:03:39 fjc
- - Changed to use new Amiga interface
-
- Revision 1.2 1994/05/12 20:45:18 fjc
- - Prepared for release
-
- Revision 1.1 1994/01/15 21:39:12 fjc
- Start of revision control
-
- 13 Jan 94 [FJC] : Chinged GetBuf to keep buffers in position
- order, hopefully eliminating seek errors.
- Now no procedures call ReadBuf or WriteBuf directly.
- 28 Dec 93 [FJC] : Actually, I had totally stuffed the handling
- of temporary files. *This* time, hopefully,
- it is fixed.
- 15 Dec 93 [FJC] : *Really* fixed handling of temporary files.
- 2 Dec 93 [FJC] : Fixed handling of temporary files.
-
- ***************************************************************************)
-