blob: 4b06b5b4d8848265ae35f7e56f01789d418c2b72 [file] [log] [blame]
(* FileSystem.mod provides a PIM [234] FileSystem module.
Copyright (C) 2004-2025 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 FileSystem ;
FROM M2RTS IMPORT InstallTerminationProcedure ;
FROM Storage IMPORT ALLOCATE ;
FROM SYSTEM IMPORT ADR, COFF_T ;
IMPORT SFIO, libc, wrapc, StrLib ;
FROM DynamicStrings IMPORT InitString, ConCat, ConCatChar,
KillString, string, Dup ;
FROM FormatStrings IMPORT Sprintf2 ;
CONST
TMPDIR = '/tmp' ;
DIRSEP = '/' ;
SEEK_SET = 0 ; (* seek relative to from beginning of the file *)
TYPE
FileList = POINTER TO RECORD
next : FileList ;
n : String ;
stillTemp: BOOLEAN ;
END ;
VAR
HeadOfTemp: FileList ;
tempNo : CARDINAL ;
(*
Create - creates a temporary file. To make the file perminant
the file must be renamed.
*)
PROCEDURE Create (VAR f: File) ;
BEGIN
WITH f DO
flags := FlagSet{write, temporary} ;
eof := FALSE ;
lastWord := WORD(0) ;
lastByte := CHAR(0) ;
name := MakeTemporary() ;
fio := SFIO.OpenToWrite(name) ;
IF FIO.IsNoError(fio)
THEN
res := done
ELSE
res := notdone
END ;
highpos := 0 ;
lowpos := 0
END
END Create ;
(*
Close - closes an open file.
*)
PROCEDURE Close (f: File) ;
BEGIN
WITH f DO
eof := TRUE ;
FIO.Close(fio) ;
IF FIO.IsNoError(fio)
THEN
res := done
ELSE
res := notdone
END ;
IF temporary IN flags
THEN
deleteFile(name, f)
END ;
name := KillString(name)
END
END Close ;
(*
Lookup - looks for a file, filename. If the file is found
then, f, is opened. If it is not found and, newFile,
is TRUE then a new file is created and attached to, f.
If, newFile, is FALSE and no file was found then f.res
is set to notdone.
*)
PROCEDURE Lookup (VAR f: File; filename: ARRAY OF CHAR; newFile: BOOLEAN) ;
BEGIN
WITH f DO
flags := FlagSet{} ;
IF FIO.Exists(filename)
THEN
fio := FIO.OpenToRead(filename) ;
INCL(flags, read) ;
res := done
ELSIF newFile
THEN
fio := FIO.OpenToWrite(filename) ;
INCL(flags, write) ;
res := done
ELSE
res := notdone
END ;
name := InitString(filename) ;
eof := FALSE ;
highpos := 0 ;
lowpos := 0
END
END Lookup ;
(*
Rename - rename a file and change a temporary file to a permanent
file. f.res is set appropriately.
*)
PROCEDURE Rename (VAR f: File; newname: ARRAY OF CHAR) ;
VAR
s: String ;
r: INTEGER ;
BEGIN
s := InitString(newname) ;
WITH f DO
r := libc.rename(string(name), string(s)) ;
IF r=0
THEN
res := done
ELSE
res := notdone
END ;
EXCL(flags, temporary) ;
name := KillString(name) ;
name := s
END
END Rename ;
(*
deleteFile - deletes file, name. It also kills the string, name.
*)
PROCEDURE deleteFile (VAR name: String; VAR f: File) ;
VAR
r: INTEGER ;
BEGIN
r := libc.unlink(string(name)) ;
IF r=0
THEN
f.res := done
ELSE
f.res := notdone
END ;
name := KillString(name) ;
name := NIL
END deleteFile ;
(*
Delete - deletes a file, name, and sets the f.res field.
f.res is set appropriately.
*)
PROCEDURE Delete (name: ARRAY OF CHAR; VAR f: File) ;
VAR
s: String ;
BEGIN
s := InitString(name) ;
deleteFile(s, f) ;
s := KillString(s)
END Delete ;
(*
ReadWord - reads a WORD, w, from file, f.
f.res is set appropriately.
*)
PROCEDURE ReadWord (VAR f: File; VAR w: WORD) ;
VAR
n: CARDINAL ;
BEGIN
WITH f DO
IF again IN flags
THEN
w := lastWord ;
EXCL(flags, again)
ELSE
ReadNBytes(f, ADR(w), SIZE(w), n) ;
IF n=SIZE(w)
THEN
res := done
ELSE
res := notdone ;
eof := TRUE
END
END
END
END ReadWord ;
(*
WriteWord - writes one word to a file, f.
f.res is set appropriately.
*)
PROCEDURE WriteWord (VAR f: File; w: WORD) ;
VAR
n: CARDINAL ;
BEGIN
WriteNBytes(f, ADR(w), SIZE(w), n) ;
WITH f DO
IF n=SIZE(w)
THEN
res := done
ELSE
res := notdone
END
END
END WriteWord ;
(*
ReadChar - reads one character from a file, f.
*)
PROCEDURE ReadChar (VAR f: File; VAR ch: CHAR) ;
VAR
n: CARDINAL ;
BEGIN
WITH f DO
IF again IN flags
THEN
ch := CHAR(lastByte) ;
EXCL(flags, again)
ELSE
ReadNBytes(f, ADR(ch), SIZE(ch), n) ;
IF n=SIZE(ch)
THEN
res := done ;
lastByte := BYTE(ch)
ELSE
res := notdone ;
eof := TRUE
END
END
END
END ReadChar ;
(*
WriteChar - writes a character, ch, to a file, f.
f.res is set appropriately.
*)
PROCEDURE WriteChar (VAR f: File; ch: CHAR) ;
VAR
n: CARDINAL ;
BEGIN
WriteNBytes(f, ADR(ch), SIZE(ch), n) ;
WITH f DO
IF n=SIZE(ch)
THEN
res := done
ELSE
res := notdone
END
END
END WriteChar ;
(*
ReadByte - reads a BYTE, b, from file, f.
f.res is set appropriately.
*)
PROCEDURE ReadByte (VAR f: File; VAR b: BYTE) ;
VAR
n: CARDINAL ;
BEGIN
WITH f DO
IF again IN flags
THEN
b := lastByte ;
EXCL(flags, again)
ELSE
ReadNBytes(f, ADR(b), SIZE(b), n) ;
IF n=SIZE(b)
THEN
res := done ;
lastByte := b
ELSE
res := notdone ;
eof := TRUE
END
END
END
END ReadByte ;
(*
WriteByte - writes one BYTE, b, to a file, f.
f.res is set appropriately.
*)
PROCEDURE WriteByte (VAR f: File; b: BYTE) ;
VAR
n: CARDINAL ;
BEGIN
WriteNBytes(f, ADR(b), SIZE(b), n) ;
WITH f DO
IF n=SIZE(b)
THEN
res := done
ELSE
res := notdone
END
END
END WriteByte ;
(*
ReadNBytes - reads a sequence of bytes from a file, f.
*)
PROCEDURE ReadNBytes (VAR f: File; a: ADDRESS; amount: CARDINAL;
VAR actuallyRead: CARDINAL) ;
BEGIN
WITH f DO
IF amount>0
THEN
actuallyRead := FIO.ReadNBytes(fio, amount, a) ;
IF FIO.IsNoError(fio)
THEN
res := done ;
IF MAX(CARDINAL)-lowpos<actuallyRead
THEN
INC(highpos)
END ;
INC(lowpos, actuallyRead)
ELSE
res := notdone ;
eof := TRUE
END
END
END
END ReadNBytes ;
(*
WriteNBytes - writes a sequence of bytes to file, f.
*)
PROCEDURE WriteNBytes (VAR f: File; a: ADDRESS; amount: CARDINAL;
VAR actuallyWritten: CARDINAL) ;
BEGIN
actuallyWritten := 0 ;
WITH f DO
IF amount>0
THEN
actuallyWritten := FIO.WriteNBytes(fio, amount, a) ;
IF FIO.IsNoError(fio)
THEN
res := done ;
IF MAX(CARDINAL)-lowpos<actuallyWritten
THEN
INC(highpos)
END ;
INC(lowpos, actuallyWritten)
ELSE
res := notdone
END
END
END
END WriteNBytes ;
(*
Again - returns the last character read to the internal buffer
so that it can be read again.
*)
PROCEDURE Again (VAR f: File) ;
BEGIN
INCL(f.flags, again)
END Again ;
(*
doModeChange -
*)
PROCEDURE doModeChange (VAR f: File; mode: Flag) ;
VAR
r: COFF_T ;
BEGIN
WITH f DO
IF NOT (mode IN flags)
THEN
INCL(flags, mode) ;
IF mode=read
THEN
EXCL(flags, write)
ELSIF mode=write
THEN
EXCL(flags, read)
END ;
IF opened IN flags
THEN
FIO.Close(fio)
END ;
IF read IN flags
THEN
fio := SFIO.OpenToRead(name)
ELSIF write IN flags
THEN
fio := SFIO.OpenToWrite(name)
END ;
INCL (flags, opened) ;
r := libc.lseek (fio,
VAL (COFF_T, lowpos) + VAL (COFF_T, highpos) * VAL (COFF_T, MAX (CARDINAL)),
SEEK_SET)
END
END
END doModeChange ;
(*
SetRead - puts the file, f, into the read state.
The file position is unchanged.
*)
PROCEDURE SetRead (VAR f: File) ;
BEGIN
doModeChange(f, read)
END SetRead ;
(*
SetWrite - puts the file, f, into the write state.
The file position is unchanged.
*)
PROCEDURE SetWrite (VAR f: File) ;
BEGIN
doModeChange(f, write)
END SetWrite ;
(*
SetModify - puts the file, f, into the modify state.
The file position is unchanged but the file can be
read and written.
*)
PROCEDURE SetModify (VAR f: File) ;
BEGIN
doModeChange(f, modify)
END SetModify ;
(*
SetOpen - places a file, f, into the open state. The file may
have been in the read/write/modify state before and
in which case the previous buffer contents are flushed
and the file state is reset to open. The position is
unaltered.
*)
PROCEDURE SetOpen (VAR f: File) ;
BEGIN
doModeChange(f, opened)
END SetOpen ;
(*
Reset - places a file, f, into the open state and reset the
position to the start of the file.
*)
PROCEDURE Reset (VAR f: File) ;
BEGIN
SetOpen(f) ;
SetPos(f, 0, 0)
END Reset ;
(*
SetPos - lseek to a position within a file.
*)
PROCEDURE SetPos (VAR f: File; high, low: CARDINAL) ;
VAR
r: COFF_T ;
BEGIN
WITH f DO
r := libc.lseek(fio, VAL(COFF_T, low) +
(VAL(COFF_T, MAX(CARDINAL)) * VAL(COFF_T, high)),
SEEK_SET) ;
highpos := high ;
lowpos := low ;
END
END SetPos ;
(*
GetPos - return the position within a file.
*)
PROCEDURE GetPos (VAR f: File; VAR high, low: CARDINAL) ;
BEGIN
WITH f DO
high := highpos ;
low := lowpos
END
END GetPos ;
(*
Length - returns the length of file, in, high, and, low.
*)
PROCEDURE Length (VAR f: File; VAR high, low: CARDINAL) ;
VAR
i: INTEGER ;
BEGIN
WITH f DO
i := wrapc.filesize(FIO.GetUnixFileDescriptor(fio), high, low)
END
END Length ;
(*
Doio - effectively flushes a file in write mode, rereads the
current buffer from disk if in read mode and writes
and rereads the buffer if in modify mode.
*)
PROCEDURE Doio (VAR f: File) ;
BEGIN
WITH f DO
IF opened IN flags
THEN
FIO.Close(fio) ;
EXCL(flags, opened)
END ;
IF read IN flags
THEN
fio := SFIO.OpenToRead(name) ;
INCL(flags, opened) ;
SetPos(f, lowpos, highpos)
ELSIF write IN flags
THEN
fio := SFIO.OpenToWrite(name) ;
INCL(flags, opened) ;
SetPos(f, lowpos, highpos)
END
END
END Doio ;
(*
FileNameChar - checks to see whether the character, ch, is
legal in a filename. nul is returned if the
character was illegal.
*)
PROCEDURE FileNameChar (ch: CHAR) : CHAR ;
BEGIN
RETURN ch
END FileNameChar ;
(*
GetFileName - return a new string containing the name of the file.
The string should be killed by the caller.
*)
PROCEDURE GetFileName (file: File) : String ;
BEGIN
RETURN Dup (file.name)
END GetFileName ;
(*
WriteString - writes contents to file. The nul char
will terminate the contents string otherwise
all characters 0..HIGH (contents) are written.
*)
PROCEDURE WriteString (file: File; contents: ARRAY OF CHAR) ;
VAR
ch : CHAR ;
i, high: CARDINAL ;
BEGIN
i := 0 ;
high := StrLib.StrLen (contents) ;
WHILE i <= high DO
WriteChar (file, contents[i]) ;
INC (i)
END
END WriteString ;
(*
MakeTemporary - creates a temporary file and returns its name.
*)
PROCEDURE MakeTemporary () : String ;
VAR
p: FileList ;
i: INTEGER ;
BEGIN
NEW(p) ;
INC(tempNo) ;
i := libc.getpid() ;
WITH p^ DO
next := HeadOfTemp ;
n := Sprintf2(InitString('fs-%d-%d'), i, tempNo) ;
n := ConCat(ConCatChar(InitString(TMPDIR), DIRSEP), n) ;
RETURN n
END
END MakeTemporary ;
(*
CleanUp - deletes all temporary files.
*)
PROCEDURE CleanUp ;
VAR
p: FileList ;
r: INTEGER ;
BEGIN
p := HeadOfTemp ;
WHILE p#NIL DO
WITH p^ DO
IF stillTemp
THEN
stillTemp := FALSE ;
r := libc.unlink(string(n))
END
END ;
p := p^.next
END
END CleanUp ;
(*
Init - installs the termination procedure to tidy up any temporary files.
*)
PROCEDURE Init ;
BEGIN
tempNo := 0 ;
HeadOfTemp := NIL ;
IF NOT InstallTerminationProcedure(CleanUp)
THEN
HALT
END
END Init ;
BEGIN
Init
END FileSystem.