MODULE Files;
IMPORT SYSTEM, Streams, KernelLog, Modules, Kernel, Commands;
CONST
ReadOnly* = 0;
Removable* = 1;
Boot* = 2;
Directory* = 1;
Hidden* = 2;
System* = 3;
Archive* = 4;
Temporary* = 5;
Ok* = 0;
ReadOnlyError = 2901;
VolumeFull = 2902;
InvalidAdr= 2903;
VolumeReadOnly* = 2905;
FsNotFound* = 2906;
FileAlreadyExists* = 2908;
BadFileName* = 2909;
FileNotFound* = 2910;
EnumSize* = 0; EnumTime* = 1;
PrefixLength* = 16;
NameLength* = 256;
Trace = FALSE;
WriteError = 2907;
DefaultWriterSize = 4096;
DefaultReaderSize = 4096;
PathDelimiter* = "/";
BufferSize = 32*1024;
SetSize = MAX (SET) + 1;
NeedsPrefix* = 0;
TYPE
FileName* = ARRAY PrefixLength+NameLength OF CHAR;
Rider* = RECORD
eof*: BOOLEAN;
res*: LONGINT;
apos*, bpos*: LONGINT;
hint*: Hint;
file*: File;
fs*: FileSystem;
END;
TYPE
Reader* = OBJECT (Streams.Reader)
VAR
file : File;
r: Rider;
PROCEDURE Receive(VAR buf: ARRAY OF CHAR; ofs, size, min: LONGINT; VAR len, res: LONGINT);
BEGIN
file.ReadBytes(r, buf, ofs, size);
len := size - r.res;
IF len >= min THEN res := Streams.Ok ELSE res := Streams.EOF END
END Receive;
PROCEDURE CanSetPos*() : BOOLEAN;
BEGIN
RETURN TRUE;
END CanSetPos;
PROCEDURE SetPos*(pos : LONGINT);
BEGIN
file.Set(r, pos);
Reset;
received := pos;
END SetPos;
PROCEDURE &InitFileReader*(file : File; pos: LONGINT);
BEGIN
ASSERT(file # NIL);
SELF.file := file;
file.Set(r, pos);
received := pos;
InitReader(SELF.Receive, DefaultReaderSize);
END InitFileReader;
END Reader;
TYPE
Writer* = OBJECT (Streams.Writer)
VAR
file : File;
r: Rider;
PROCEDURE Send(CONST buf: ARRAY OF CHAR; ofs, len: LONGINT; propagate: BOOLEAN; VAR res: LONGINT);
BEGIN
r.file.WriteBytes(r, buf, ofs, len);
IF propagate THEN r.file.Update END;
IF r.res = 0 THEN res := Streams.Ok ELSE res := WriteError END
END Send;
PROCEDURE CanSetPos*() : BOOLEAN;
BEGIN
RETURN TRUE;
END CanSetPos;
PROCEDURE SetPos*(pos : LONGINT);
BEGIN
Update;
file.Set(r, pos);
Reset;
END SetPos;
PROCEDURE Pos*(): LONGINT;
BEGIN
Update;
RETURN file.Pos(r)
END Pos;
PROCEDURE &InitFileWriter*(file: File; pos: LONGINT);
BEGIN
ASSERT(file # NIL);
SELF.file := file;
file.Set(r, pos);
InitWriter(SELF.Send, DefaultWriterSize);
END InitFileWriter;
END Writer;
Prefix* = ARRAY PrefixLength OF CHAR;
Address* = LONGINT;
Hint* = POINTER TO RECORD END;
Bytes2 = ARRAY 2 OF CHAR;
Bytes4 = ARRAY 4 OF CHAR;
Bytes8 = ARRAY 8 OF CHAR;
TYPE
Volume* = OBJECT
VAR
size*: LONGINT;
blockSize*: LONGINT;
flags*: SET;
name*: ARRAY 32 OF CHAR;
map: POINTER TO ARRAY OF SET;
used: LONGINT;
reserved: LONGINT;
PROCEDURE AllocBlock*(hint: Address; VAR adr: Address);
BEGIN {EXCLUSIVE}
IF ReadOnly IN flags THEN HALT(ReadOnlyError) END;
IF size - used <= reserved THEN HALT(VolumeFull) END;
ASSERT(hint >= 0);
IF hint > size THEN hint := 0 END;
adr := hint+1;
LOOP
IF adr > size THEN adr := 0 END;
IF (adr MOD SetSize) IN map[adr DIV SetSize] THEN
INC(adr)
ELSE
INCL(map[adr DIV SetSize], adr MOD SetSize);
EXIT
END;
IF adr = hint THEN HALT(VolumeFull) END
END;
INC(used)
END AllocBlock;
PROCEDURE FreeBlock*(adr: Address);
BEGIN {EXCLUSIVE}
IF (adr < 1) OR (adr > size) THEN HALT(InvalidAdr) END;
IF ReadOnly IN flags THEN HALT(ReadOnlyError) END;
EXCL(map[adr DIV SetSize], adr MOD SetSize);
DEC(used)
END FreeBlock;
PROCEDURE MarkBlock*(adr: Address);
BEGIN {EXCLUSIVE}
IF (adr < 1) OR (adr > size) THEN HALT(InvalidAdr) END;
IF ReadOnly IN flags THEN HALT(ReadOnlyError) END;
INCL(map[adr DIV SetSize], adr MOD SetSize);
INC(used)
END MarkBlock;
PROCEDURE Marked*(adr: Address): BOOLEAN;
BEGIN {EXCLUSIVE}
IF (adr < 1) OR (adr > size) THEN HALT(InvalidAdr) END;
IF ReadOnly IN flags THEN HALT(ReadOnlyError) END;
RETURN (adr MOD SetSize) IN map[adr DIV SetSize]
END Marked;
PROCEDURE Available*(): LONGINT;
BEGIN {EXCLUSIVE}
RETURN size-used
END Available;
PROCEDURE GetBlock*(adr: LONGINT; VAR blk: ARRAY OF CHAR);
BEGIN HALT(301) END GetBlock;
PROCEDURE PutBlock*(adr: LONGINT; VAR blk: ARRAY OF CHAR);
BEGIN HALT(301) END PutBlock;
PROCEDURE Finalize*;
BEGIN
map := NIL; size := 0; blockSize := 0
END Finalize;
PROCEDURE Init*(flags: SET; size, reserved: LONGINT);
VAR maplen: LONGINT;
BEGIN
SELF.flags := flags; SELF.size := size; SELF.reserved := reserved;
IF ~(ReadOnly IN flags) THEN
maplen := (size+1+SetSize - 1) DIV SetSize;
NEW(map, maplen);
WHILE maplen > 0 DO DEC(maplen); map[maplen] := {} END;
INCL(map[0], 0);
used := 0
ELSE
used := size
END
END Init;
END Volume;
TYPE
FileSystem* = OBJECT
VAR
next: FileSystem;
prefix*: Prefix;
desc*: ARRAY 32 OF CHAR;
vol*: Volume;
flags*: SET;
PROCEDURE New0*(name: ARRAY OF CHAR): File;
BEGIN HALT(301) END New0;
PROCEDURE Old0*(name: ARRAY OF CHAR): File;
BEGIN HALT(301) END Old0;
PROCEDURE Delete0*(name: ARRAY OF CHAR; VAR key, res: LONGINT);
BEGIN HALT(301) END Delete0;
PROCEDURE Rename0*(old, new: ARRAY OF CHAR; f: File; VAR res: LONGINT);
BEGIN HALT(301) END Rename0;
PROCEDURE Enumerate0*(mask: ARRAY OF CHAR; flags: SET; enum: Enumerator);
BEGIN HALT(301) END Enumerate0;
PROCEDURE FileKey*(name: ARRAY OF CHAR): LONGINT;
BEGIN HALT(301) END FileKey;
PROCEDURE CreateDirectory0*(name: ARRAY OF CHAR; VAR res: LONGINT);
BEGIN res := -1
END CreateDirectory0;
PROCEDURE RemoveDirectory0*(name: ARRAY OF CHAR; force: BOOLEAN; VAR key, res: LONGINT);
BEGIN res := -1
END RemoveDirectory0;
PROCEDURE Finalize*;
BEGIN
vol := NIL
END Finalize;
END FileSystem;
FileSystemTable* = POINTER TO ARRAY OF FileSystem;
TYPE
File* = OBJECT
VAR
flags*: SET;
key*: LONGINT;
fs*: FileSystem;
PROCEDURE Set*(VAR r: Rider; pos: LONGINT);
BEGIN HALT(301) END Set;
PROCEDURE Pos*(VAR r: Rider): LONGINT;
BEGIN HALT(301) END Pos;
PROCEDURE Read*(VAR r: Rider; VAR x: CHAR);
BEGIN HALT(301) END Read;
PROCEDURE ReadBytes*(VAR r: Rider; VAR x: ARRAY OF CHAR; ofs, len: LONGINT);
BEGIN HALT(301) END ReadBytes;
PROCEDURE Write*(VAR r: Rider; x: CHAR);
BEGIN HALT(301) END Write;
PROCEDURE WriteBytes*(VAR r: Rider; CONST x: ARRAY OF CHAR; ofs, len: LONGINT);
BEGIN HALT(301) END WriteBytes;
PROCEDURE Length*(): LONGINT;
BEGIN HALT(301) END Length;
PROCEDURE GetDate*(VAR t, d: LONGINT);
BEGIN HALT(301) END GetDate;
PROCEDURE SetDate*(t, d: LONGINT);
BEGIN HALT(301) END SetDate;
PROCEDURE GetAttributes*(): SET;
BEGIN HALT(301) END GetAttributes;
PROCEDURE SetAttributes*(flags: SET);
BEGIN HALT(301) END SetAttributes;
PROCEDURE GetName*(VAR name: ARRAY OF CHAR);
BEGIN HALT(301) END GetName;
PROCEDURE Register0*(VAR res: LONGINT);
BEGIN HALT(301) END Register0;
PROCEDURE Update*;
BEGIN HALT(301) END Update;
PROCEDURE Close*;
BEGIN
END Close;
END File;
TYPE
Enumerator* = OBJECT
VAR
r: Rider;
adding: BOOLEAN;
size-: LONGINT;
PROCEDURE Open*(mask: ARRAY OF CHAR; flags: SET);
BEGIN
r.file := New("");
r.file.Set(r, 0);
size := 0;
adding := TRUE;
Enumerate(mask, flags, SELF);
adding := FALSE;
r.file.Set(r, 0)
END Open;
PROCEDURE Reset*;
BEGIN
r.file.Set(r, 0)
END Reset;
PROCEDURE HasMoreEntries*(): BOOLEAN;
BEGIN
RETURN r.file.Pos(r) < r.file.Length()
END HasMoreEntries;
PROCEDURE GetEntry*(VAR name: ARRAY OF CHAR; VAR flags: SET; VAR time, date, size: LONGINT): BOOLEAN;
VAR len: LONGINT;
BEGIN
ReadNum(r, len);
IF ~r.eof THEN
name[len] := 0X;
r.file.ReadBytes(r, name, 0, len);
ReadSet(r, flags); ReadNum(r, time); ReadNum(r, date); ReadNum(r, size);
ASSERT(~r.eof)
END;
RETURN ~r.eof
END GetEntry;
PROCEDURE Close*;
BEGIN
r.hint := NIL; r.file := NIL; r.fs := NIL
END Close;
PROCEDURE PutEntry*(VAR name: ARRAY OF CHAR; flags: SET; time, date, size: LONGINT);
VAR len: LONGINT;
BEGIN
ASSERT(adding);
INC(SELF.size);
len := 0; WHILE name[len] # 0X DO INC(len) END;
WriteNum(r, len); r.file.WriteBytes(r, name, 0, len);
WriteSet(r, flags); WriteNum(r, time); WriteNum(r, date); WriteNum(r, size)
END PutEntry;
END Enumerator;
TYPE
FileSearcher = OBJECT
VAR fs: FileSystem; key: LONGINT; found: File;
PROCEDURE EnumFile(f: ANY; VAR cont: BOOLEAN);
BEGIN
WITH f: File DO
IF (f.fs = fs) & (f.key = key) THEN
found := f; cont := FALSE
END
END
END EnumFile;
END FileSearcher;
TYPE
Parameters* = OBJECT(Commands.Context)
VAR
vol*: Volume;
prefix*: Prefix;
END Parameters;
FileSystemFactory* = PROCEDURE(context : Parameters);
VAR
fsroot: FileSystem;
files: Kernel.FinalizedCollection;
seacher: FileSearcher;
fileClipboard : File;
PROCEDURE OpenReader*(VAR b: Reader; f: File; pos: LONGINT);
BEGIN
NEW(b, f, pos)
END OpenReader;
PROCEDURE OpenWriter*(VAR b: Writer; f: File; pos: LONGINT);
BEGIN
NEW(b, f, pos)
END OpenWriter;
PROCEDURE SplitName*(fullname: ARRAY OF CHAR; VAR prefix, name: ARRAY OF CHAR);
VAR i, j, len: LONGINT;
BEGIN
i := 0; WHILE (fullname[i] # ":") & (fullname[i] # 0X) DO INC(i) END;
IF (fullname[i] # ":") OR (i >= LEN(prefix)) THEN
COPY("", prefix); COPY(fullname, name);
ELSE
j := 0; WHILE j # i DO prefix[j] := fullname[j]; INC(j) END;
prefix[j] := 0X;
j := 0; INC(i); len := LEN(name)-1;
WHILE (j < len) & (fullname[i] # 0X) DO name[j] := fullname[i]; INC(j); INC(i) END;
name[j] := 0X
END
END SplitName;
PROCEDURE JoinName*(prefix, name: ARRAY OF CHAR; VAR fullname: ARRAY OF CHAR);
VAR i, j, len: LONGINT;
BEGIN
len := LEN(fullname)-1;
i := 0; WHILE (i < len) & (prefix[i] # 0X) DO fullname[i] := prefix[i]; INC(i) END;
IF (i < len) THEN fullname[i] := ":"; INC(i) END;
j := 0; WHILE (i < len) & (name[j] # 0X) DO fullname[i] := name[j]; INC(i); INC(j) END;
fullname[i] := 0X
END JoinName;
PROCEDURE SplitPath*(pathname: ARRAY OF CHAR; VAR path, name: ARRAY OF CHAR);
VAR i,j,len: LONGINT;
BEGIN
i := 0; j := -1;
WHILE pathname[i] # 0X DO
IF (pathname[i] = PathDelimiter) OR (pathname[i] = ":") THEN j := i END;
INC(i)
END;
i := 0; len := LEN(path)-1;
WHILE (i < len) & (i < j) DO path[i] := pathname[i]; INC(i) END; path[i] := 0X;
IF pathname[i] = ":" THEN path[i] := ":"; path[i+1] := 0X; END;
INC(j); i := 0; len := LEN(name)-1;
WHILE (i < len) & (pathname[j] # 0X) DO name[i] := pathname[j]; INC(i); INC(j) END;
name[i] := 0X
END SplitPath;
PROCEDURE JoinPath*(path, name: ARRAY OF CHAR; VAR pathname: ARRAY OF CHAR);
VAR i,j,len: LONGINT;
BEGIN
len := LEN(pathname)-1;
i := 0; WHILE (i < len) & (path[i] # 0X) DO pathname[i] := path[i]; INC(i) END;
IF ((i = 0) OR (pathname[i-1] # PathDelimiter)) & (i < len) THEN pathname[i] := PathDelimiter; INC(i) END;
j := 0; WHILE (i < len) & (name[j] # 0X) DO pathname[i] := name[j]; INC(i); INC(j) END;
pathname[i] := 0X
END JoinPath;
PROCEDURE SplitExtension*(filename: ARRAY OF CHAR; VAR name, extension: ARRAY OF CHAR);
VAR i,j,len: LONGINT;
BEGIN
i := 0; j := -1;
WHILE filename[i] # 0X DO
IF filename[i] = "." THEN j := i END;
INC(i)
END;
IF (j = -1) THEN
COPY(filename, name); COPY("", extension)
ELSE
i := 0; len := LEN(name)-1;
WHILE (i < len) & (i < j) DO name[i] := filename[i]; INC(i) END; name[i] := 0X;
INC(j); i := 0; len := LEN(extension)-1;
WHILE (i < len) & (filename[j] # 0X) DO extension[i] := filename[j]; INC(i); INC(j) END;
extension[i] := 0X
END
END SplitExtension;
PROCEDURE JoinExtension*(name, extension: ARRAY OF CHAR; VAR filename: ARRAY OF CHAR);
VAR i,j,len: LONGINT;
BEGIN
len := LEN(filename)-1;
i := 0; WHILE (i < len) & (name[i] # 0X) DO filename[i] := name[i]; INC(i) END;
IF ((i = 0) OR (filename[i-1] # ".")) & (i < len) THEN filename[i] := "."; INC(i) END;
j := 0; IF extension[0] = "." THEN INC(j) END;
WHILE (i < len) & (extension[j] # 0X) DO filename[i] := extension[j]; INC(i); INC(j) END;
filename[i] := 0X
END JoinExtension;
PROCEDURE ForceTrailingDelimiter*(VAR path: ARRAY OF CHAR);
VAR i: LONGINT;
BEGIN
i := 0; WHILE path[i] # 0X DO INC(i) END;
IF (i = 0) OR (path[i-1] # PathDelimiter) THEN
path[i] := PathDelimiter;
path[i+1] := 0X
END
END ForceTrailingDelimiter;
PROCEDURE WriteFS(fs: FileSystem);
BEGIN
IF Trace THEN
IF fs.vol # NIL THEN KernelLog.String(fs.vol.name); KernelLog.Char(" ") END;
KernelLog.String(fs.desc)
END
END WriteFS;
PROCEDURE Add*(fs: FileSystem; prefix: ARRAY OF CHAR);
VAR p, c: FileSystem;
BEGIN {EXCLUSIVE}
IF Trace THEN
KernelLog.Enter; KernelLog.String("Files: Adding "); WriteFS(fs); KernelLog.Exit
END;
COPY(prefix, fs.prefix);
p := NIL; c := fsroot;
WHILE c # NIL DO
ASSERT((c # fs) & (c.prefix # fs.prefix));
p := c; c := c.next
END;
IF p = NIL THEN fsroot := fs ELSE p.next := fs END;
fs.next := NIL
END Add;
PROCEDURE DeleteFS(fs: FileSystem);
VAR p, c: FileSystem;
BEGIN
p := NIL; c := fsroot;
WHILE c # fs DO p := c; c := c.next END;
IF p = NIL THEN fsroot := c.next ELSE p.next := c.next END;
c.next := NIL
END DeleteFS;
PROCEDURE Promote*(fs: FileSystem);
BEGIN {EXCLUSIVE}
DeleteFS(fs);
fs.next := fsroot; fsroot := fs
END Promote;
PROCEDURE Remove*(fs: FileSystem);
VAR
enum: OBJECT
VAR count: LONGINT; fs: FileSystem;
PROCEDURE EnumFile(f: ANY; VAR cont: BOOLEAN);
BEGIN
WITH f: File DO
IF f.fs = fs THEN INC(count); f.Update(); f.fs := NIL END
END
END EnumFile;
END;
BEGIN {EXCLUSIVE}
IF Trace THEN
KernelLog.Enter; KernelLog.String("Files: Removing "); WriteFS(fs); KernelLog.Exit
END;
NEW(enum); enum.count := 0; enum.fs := fs;
files.Enumerate(enum.EnumFile);
IF enum.count # 0 THEN
KernelLog.Enter; KernelLog.String("Files: "); KernelLog.Int(enum.count, 1);
KernelLog.String(" open files");
IF fs.vol # NIL THEN
KernelLog.String(" on "); KernelLog.String(fs.vol.name)
END;
KernelLog.Exit
END;
fs.Finalize();
DeleteFS(fs)
END Remove;
PROCEDURE FindFS(prefix: ARRAY OF CHAR): FileSystem;
VAR fs: FileSystem;
BEGIN
fs := fsroot; WHILE (fs # NIL) & (fs.prefix # prefix) DO fs := fs.next END;
RETURN fs
END FindFS;
PROCEDURE This*(prefix: ARRAY OF CHAR): FileSystem;
BEGIN {EXCLUSIVE}
RETURN FindFS(prefix)
END This;
PROCEDURE GetList*(VAR list: FileSystemTable);
VAR fs: FileSystem; n, i: LONGINT;
BEGIN {EXCLUSIVE}
fs := fsroot; n := 0;
WHILE (fs # NIL) DO fs := fs.next; INC(n) END;
IF n # 0 THEN
NEW(list, n);
fs := fsroot;
FOR i := 0 TO n-1 DO
list[i] := fs; fs := fs.next
END
ELSE
list := NIL
END
END GetList;
PROCEDURE OpenOld(enum: FileSearcher; fs: FileSystem; VAR fname: ARRAY OF CHAR): File;
VAR f: File; key: LONGINT;
BEGIN
f := NIL;
IF (fs # NIL) & (fname # "") THEN
key := fs.FileKey(fname);
IF key # 0 THEN f := FindOpenFile(enum, fs, key) END;
IF f = NIL THEN
f := fs.Old0(fname);
IF f # NIL THEN
ASSERT(f.key # 0);
files.Add(f, NIL);
END
END
END;
RETURN f
END OpenOld;
PROCEDURE Old*(name: ARRAY OF CHAR): File;
VAR fs: FileSystem; f: File; prefix: Prefix; fname: FileName;
BEGIN {EXCLUSIVE}
f := NIL;
SplitName(name, prefix, fname);
IF prefix = "" THEN
fs := fsroot;
WHILE (fs # NIL) & (f = NIL) DO
IF ~(NeedsPrefix IN fs.flags) THEN
f := OpenOld(seacher, fs, fname);
END;
fs := fs.next
END
ELSE
f := OpenOld(seacher, FindFS(prefix), fname)
END;
RETURN f
END Old;
PROCEDURE New*(name: ARRAY OF CHAR): File;
VAR fs: FileSystem; f: File; prefix: Prefix; fname: FileName;
BEGIN {EXCLUSIVE}
f := NIL; SplitName(name, prefix, fname);
IF prefix = "" THEN
fs := fsroot;
IF fname = "" THEN
WHILE (fs # NIL) & ((fs.vol = NIL) OR (fs.vol.flags * {Boot,ReadOnly} # {Boot})) DO
fs := fs.next
END;
IF fs = NIL THEN fs := fsroot END
END
ELSE
fs := FindFS(prefix);
END;
IF fs # NIL THEN
IF (fs.vol = NIL) OR ~(ReadOnly IN fs.vol.flags) THEN
f := fs.New0(fname);
END
END;
RETURN f
END New;
PROCEDURE Delete*(VAR name: ARRAY OF CHAR; VAR res: LONGINT);
VAR fs: FileSystem; f: File; key: LONGINT; prefix: Prefix; fname: FileName;
BEGIN {EXCLUSIVE}
SplitName(name, prefix, fname);
IF prefix = "" THEN fs := fsroot ELSE fs := FindFS(prefix) END;
IF fs # NIL THEN
IF (fs.vol = NIL) OR ~(ReadOnly IN fs.vol.flags) THEN
fs.Delete0(fname, key, res);
IF key # 0 THEN
LOOP
f := FindOpenFile(seacher, fs, key);
IF f = NIL THEN EXIT END;
files.Remove(f)
END
END
ELSE
res := VolumeReadOnly
END
ELSE
res := FsNotFound
END
END Delete;
PROCEDURE Copy*(name: ARRAY OF CHAR; VAR res: LONGINT);
VAR file: File;
BEGIN
res := -1;
file := Old(name);
IF file = NIL THEN RETURN END;
fileClipboard := file;
res := 0;
END Copy;
PROCEDURE Paste*(name: ARRAY OF CHAR; VAR res: LONGINT);
VAR writer : Writer;
reader : Reader;
file : File;
chunk : ARRAY 4096 OF CHAR;
len : LONGINT;
BEGIN
IF fileClipboard = NIL THEN RETURN END;
IF Old(name) # NIL THEN res := FileAlreadyExists;
ELSE
file := New(name);
IF file = NIL THEN res := BadFileName; RETURN END;
NEW(writer, file, 0);
NEW(reader, fileClipboard, 0);
WHILE (reader.res = Streams.Ok) DO
reader.Bytes(chunk, 0, LEN(chunk), len);
writer.Bytes(chunk, 0, len);
END;
writer.Update;
Register(file);
res := 0;
END;
END Paste;
PROCEDURE CopyFile*(source, destination : ARRAY OF CHAR; VAR overwrite : BOOLEAN; VAR res : LONGINT);
VAR
sprefix, dprefix : Prefix;
sname, dname, dpath, dfilename: FileName;
sPos, dPos : Rider;
sfs, dfs : FileSystem;
sfile, dfile : File;
buffer : ARRAY BufferSize OF CHAR;
i : LONGINT;
BEGIN
SplitName(source, sprefix, sname);
SplitName(destination, dprefix, dname);
BEGIN {EXCLUSIVE}
IF sprefix = "" THEN sfs := fsroot; ELSE sfs := FindFS(sprefix); END;
IF dprefix = "" THEN dfs := fsroot; ELSE dfs := FindFS(dprefix); END;
IF (sfs # NIL) & (dfs # NIL) THEN
IF (dfs.vol = NIL) OR ~(ReadOnly IN dfs.vol.flags) THEN
sfile := OpenOld(seacher, sfs, sname);
IF sfile # NIL THEN
SplitName(dname, dpath, dfilename);
IF (dfilename # "") THEN
dfile := OpenOld(seacher, dfs, dname);
IF (dfile = NIL) OR overwrite THEN
IF dfile # NIL THEN overwrite := TRUE; ELSE overwrite := FALSE; END;
dfile := dfs.New0(dname);
IF dfile # NIL THEN
res := Ok;
ELSE res := BadFileName;
END;
ELSE res := FileAlreadyExists;
END;
ELSE res := BadFileName;
END;
ELSE res := FileNotFound;
END;
ELSE res :=VolumeReadOnly;
END;
ELSE res := FsNotFound;
END;
END;
IF res # Ok THEN RETURN END;
sfile.Set(sPos, 0); dfile.Set(dPos, 0);
i := 0;
WHILE i < (sfile.Length() DIV BufferSize) DO
sfile.ReadBytes(sPos, buffer, 0, BufferSize);
dfile.WriteBytes(dPos, buffer, 0, BufferSize);
INC(i);
END;
sfile.ReadBytes(sPos, buffer, 0, sfile.Length() MOD BufferSize);
dfile.WriteBytes(dPos, buffer, 0, sfile.Length() MOD BufferSize);
Register(dfile);
END CopyFile;
PROCEDURE Rename*(CONST old, new: ARRAY OF CHAR; VAR res: LONGINT);
VAR
key: LONGINT; ofs, nfs: FileSystem; f: File; pold, pnew: Prefix;
fold, fnew: FileName;
BEGIN {EXCLUSIVE}
SplitName(old, pold, fold);
SplitName(new, pnew, fnew);
IF pold = "" THEN ofs := fsroot ELSE ofs := FindFS(pold) END;
IF pnew = "" THEN nfs := fsroot ELSE nfs := FindFS(pnew) END;
IF (nfs # NIL) & (ofs = nfs) THEN
IF (nfs.vol = NIL) OR ~(ReadOnly IN nfs.vol.flags) THEN
key := nfs.FileKey(fold);
IF key # 0 THEN f := FindOpenFile(seacher, nfs, key) ELSE f := NIL END;
nfs.Rename0(fold, fnew, f, res)
ELSE
res := VolumeReadOnly
END
ELSE
res := FsNotFound
END
END Rename;
PROCEDURE Register*(f: File);
VAR res: LONGINT;
BEGIN {EXCLUSIVE}
IF f # NIL THEN
f.Register0(res);
IF res = 0 THEN
ASSERT(f.key # 0);
files.Add(f, NIL)
END
END
END Register;
PROCEDURE CreateDirectory*(path: ARRAY OF CHAR; VAR res: LONGINT);
VAR prefix: Prefix; fs: FileSystem; fpath: FileName;
BEGIN {EXCLUSIVE}
SplitName(path, prefix, fpath);
IF prefix = "" THEN fs := fsroot
ELSE fs := FindFS(prefix)
END;
IF fs # NIL THEN fs.CreateDirectory0(fpath, res)
ELSE res := -1
END
END CreateDirectory;
PROCEDURE RemoveDirectory*(path: ARRAY OF CHAR; force: BOOLEAN; VAR res: LONGINT);
VAR prefix: Prefix; fs: FileSystem; f: File; key: LONGINT; fpath: FileName;
BEGIN {EXCLUSIVE}
SplitName(path, prefix, fpath);
IF prefix = "" THEN fs := fsroot ELSE fs := FindFS(prefix) END;
IF fs # NIL THEN
IF (fs.vol = NIL) OR ~(ReadOnly IN fs.vol.flags) THEN
fs.RemoveDirectory0(fpath, force, key, res);
IF key # 0 THEN
LOOP
f := FindOpenFile(seacher, fs, key);
IF f = NIL THEN EXIT END;
files.Remove(f)
END
END
ELSE
res := VolumeReadOnly
END
ELSE
res := FsNotFound
END
END RemoveDirectory;
PROCEDURE Enumerate(VAR mask: ARRAY OF CHAR; flags: SET; enum: Enumerator);
VAR
fs: FileSystem; ft: FileSystemTable; i: LONGINT;
prefix: Prefix; fmask: FileName
;BEGIN
SplitName(mask, prefix, fmask);
IF prefix = "" THEN
GetList(ft);
IF ft # NIL THEN
FOR i := 0 TO LEN(ft^)-1 DO
IF ~(NeedsPrefix IN ft[i].flags) THEN
ft[i].Enumerate0(fmask, flags, enum)
END;
END
END
ELSE
fs := This(prefix);
IF fs # NIL THEN fs.Enumerate0(fmask, flags, enum) END
END
END Enumerate;
PROCEDURE FindOpenFile(enum: FileSearcher; fs: FileSystem; key: LONGINT): File;
BEGIN
enum.fs := fs; enum.key := key; enum.found := NIL;
files.Enumerate(enum.EnumFile);
RETURN enum.found
END FindOpenFile;
PROCEDURE ReadSInt*(VAR r: Rider; VAR x: SHORTINT);
BEGIN
r.file.Read(r, SYSTEM.VAL(CHAR, x))
END ReadSInt;
PROCEDURE ReadInt*(VAR r: Rider; VAR x: INTEGER);
BEGIN
r.file.ReadBytes(r, SYSTEM.VAL(Bytes2, x), 0, 2)
END ReadInt;
PROCEDURE ReadLInt*(VAR r: Rider; VAR x: LONGINT);
BEGIN
r.file.ReadBytes(r, SYSTEM.VAL(Bytes4, x), 0, 4)
END ReadLInt;
PROCEDURE ReadSet*(VAR r: Rider; VAR x: SET);
CONST Size = SYSTEM.SIZEOF (SET);
TYPE Bytes = ARRAY Size OF CHAR;
BEGIN
r.file.ReadBytes(r, SYSTEM.VAL(Bytes, x), 0, Size)
END ReadSet;
PROCEDURE ReadBool*(VAR r: Rider; VAR x: BOOLEAN);
VAR ch: CHAR;
BEGIN
r.file.Read(r, ch); x := ch # 0X
END ReadBool;
PROCEDURE ReadReal*(VAR r: Rider; VAR x: REAL);
BEGIN
r.file.ReadBytes(r, SYSTEM.VAL(Bytes4, x), 0, 4)
END ReadReal;
PROCEDURE ReadLReal*(VAR r: Rider; VAR x: LONGREAL);
BEGIN
r.file.ReadBytes(r, SYSTEM.VAL(Bytes8, x), 0, 8)
END ReadLReal;
PROCEDURE ReadString*(VAR r: Rider; VAR x: ARRAY OF CHAR);
VAR i: LONGINT; ch: CHAR; f: File;
BEGIN
i := 0; f := r.file;
LOOP
f.Read(r, ch); x[i] := ch; INC(i);
IF ch = 0X THEN EXIT END;
IF i = LEN(x) THEN
x[i-1] := 0X;
REPEAT f.Read(r, ch) UNTIL ch = 0X;
EXIT
END
END
END ReadString;
PROCEDURE ReadNum*(VAR r: Rider; VAR x: LONGINT);
VAR ch: CHAR; n, y: LONGINT; f: File;
BEGIN
n := 0; y := 0; f := r.file;
f.Read(r, ch);
WHILE ch >= 80X DO
INC(y, SYSTEM.LSH(LONG(ORD(ch)) - 128, n)); INC(n, 7);
f.Read(r, ch)
END;
x := ASH(SYSTEM.LSH(LONG(ORD(ch)), 25), n-25) + y
END ReadNum;
PROCEDURE WriteSInt*(VAR r: Rider; x: SHORTINT);
BEGIN
r.file.Write(r, SYSTEM.VAL(CHAR, x))
END WriteSInt;
PROCEDURE WriteInt*(VAR r: Rider; x: INTEGER);
BEGIN
r.file.WriteBytes(r, SYSTEM.VAL(Bytes2, x), 0, 2)
END WriteInt;
PROCEDURE WriteLInt*(VAR r: Rider; x: LONGINT);
BEGIN
r.file.WriteBytes(r, SYSTEM.VAL(Bytes4, x), 0, 4)
END WriteLInt;
PROCEDURE WriteSet*(VAR r: Rider; x: SET);
CONST Size = SYSTEM.SIZEOF (SET);
TYPE Bytes = ARRAY Size OF CHAR;
BEGIN
r.file.WriteBytes(r, SYSTEM.VAL(Bytes, x), 0, Size)
END WriteSet;
PROCEDURE WriteBool*(VAR r: Rider; x: BOOLEAN);
BEGIN
IF x THEN r.file.Write(r, 1X) ELSE r.file.Write(r, 0X) END
END WriteBool;
PROCEDURE WriteReal*(VAR r: Rider; x: REAL);
BEGIN
r.file.WriteBytes(r, SYSTEM.VAL(Bytes4, x), 0, 4)
END WriteReal;
PROCEDURE WriteLReal*(VAR r: Rider; x: LONGREAL);
BEGIN
r.file.WriteBytes(r, SYSTEM.VAL(Bytes8, x), 0, 8)
END WriteLReal;
PROCEDURE WriteString*(VAR r: Rider; x: ARRAY OF CHAR);
VAR i: LONGINT;
BEGIN
i := 0; WHILE x[i] # 0X DO INC(i) END;
r.file.WriteBytes(r, x, 0, i+1)
END WriteString;
PROCEDURE WriteNum*(VAR r: Rider; x: LONGINT);
VAR f: File;
BEGIN
f := r.file;
WHILE (x < - 64) OR (x > 63) DO
f.Write(r, CHR(x MOD 128 + 128)); x := x DIV 128
END;
f.Write(r, CHR(x MOD 128))
END WriteNum;
PROCEDURE AppendStr*(from: ARRAY OF CHAR; VAR to: ARRAY OF CHAR);
VAR i, j, m: LONGINT;
BEGIN
j := 0; WHILE to[j] # 0X DO INC(j) END;
m := LEN(to)-1;
i := 0; WHILE (from[i] # 0X) & (j # m) DO to[j] := from[i]; INC(i); INC(j) END;
to[j] := 0X
END AppendStr;
PROCEDURE AppendInt*(x: LONGINT; VAR to: ARRAY OF CHAR);
VAR i, m: LONGINT;
BEGIN
ASSERT(x >= 0);
i := 0; WHILE to[i] # 0X DO INC(i) END;
IF x # 0 THEN
m := 1000000000;
WHILE x < m DO m := m DIV 10 END;
REPEAT
to[i] := CHR(48 + (x DIV m) MOD 10); INC(i);
m := m DIV 10
UNTIL m = 0
ELSE
to[i] := "0"; INC(i)
END;
to[i] := 0X
END AppendInt;
PROCEDURE GetDevPart*(arg : Streams.Reader; VAR deviceName : ARRAY OF CHAR; VAR partition : LONGINT);
VAR i : LONGINT; ch : CHAR;
BEGIN
arg.SkipWhitespace;
deviceName := ""; partition := 0;
i := 0;
ch := arg.Peek();
WHILE (i < LEN(deviceName)-1) & (ch > " ") & (ch # "#") & (ch # ",") & (arg.res = Streams.Ok) DO
arg.Char(ch);
deviceName[i] := ch; INC(i);
ch := arg.Peek();
END;
deviceName[i] := 0X;
IF (ch = "#") THEN
arg.Char(ch);
arg.Int(partition, FALSE);
ELSE
partition := 0;
END;
END GetDevPart;
PROCEDURE FSCleanup;
VAR ft: FileSystemTable; i: LONGINT;
BEGIN
GetList(ft);
IF ft # NIL THEN
FOR i := 0 TO LEN(ft^)-1 DO Remove(ft[i]) END
END
END FSCleanup;
BEGIN
fsroot := NIL; NEW(seacher); NEW(files);
Modules.InstallTermHandler(FSCleanup)
END Files.
(**
Notes:
o A typical code pattern for reading a file is:
VAR f: Files.File; r: Files.Reader; ch: CHAR;
f := Files.Old(filename); (* open an existing file *)
IF f # NIL THEN
Files.OpenReader(r, f, 0); (* open a buffer on the file *)
LOOP
ch := r.Get(); (* read a character from the buffer *)
IF r.res # Streams.Ok THEN EXIT END; (* end-of-file, or other error *)
"do something with ch"
END
END
o A typical code pattern for writing a file is:
VAR f: Files.File; w: Files.Writer; ch: CHAR;
f := Files.New(filename); (* create a new file (not visible yet) *)
IF f # NIL THEN
Files.OpenWriter(w, f, 0); (* open a buffer on the file *)
WHILE "not done" DO
"assign ch"
w.Char(ch) (* write a character to the buffer (if the buffer is full, it is written to the file) *)
END;
w.Update; (* write the last buffer to the file *)
Files.Register(f) (* enter the file in the directory *)
o See the Streams module for more procedures operating on Reader and Writer buffers, e.g. ReadRawInt, WriteRawInt, etc.
o Never use an exported identifier with a name ending in "0", unless you are implementing a file system.
o Never use an exported identifier that is documented as "private".
o File system implementations must implement the FileKey procedure to assign a unique key value to every file in the file system. The key is used by the Files module to ensure that the Old procedure returns an existing file if it is already open. The key need not be persistent, but must stay unique during a whole session (between mount and unmount). The 0 key is reserved to indicate non-existent files.
*)
(*
On-the-fly GC by bsm
In order to be non-leaking, a file system must provide the following:
- FileSystem.Purge -- to reclaim blocks of an open (being closed) file
- FileSystem.Registered -- reports if a particular open file is registered in the file directory
The following procedures need to be modified to purge file blocks when appropriate.
- FileSystem.Register0 -- if an entry to a file, F, which is not open is replaced, purge F.
- FileSystem.Rename0 -- same as register.
- FileSystem.Delete0 -- if the entry being deleted refers to a file, F, which is not open, purge F.
*)
(*
Lock order: Files, File, FileSystem
*)
Files.File
Files.File
Files.Rider
Files.Reader
Files.Writer
Files.Old
Files.Old
Files.Set
Files.OpenReader
Files.OpenWriter
Files.ReadNum
Streams.ReadRawNum
Files.ReadInt
Streams.ReadRawInt
Files.ReadLInt
Streams.ReadRawLInt
Files.ReadString
Streams.ReadRawString
Files.ReadBytes
Streams.ReadBytes [add 0 ofs parameter, and len parameter]
Files.Read(
Streams.Read(
Files.ReadBool
Streams.ReadRawBool
Files.WriteInt
Streams.WriteRawInt
Files.Write(
Streams.Write(
Files.WriteBytes
Streams.WriteBytes [add 0 ofs parameter]