(* Aos, Copyright 2001, Pieter Muller, ETH Zurich *)
MODULE ISO9660Files; (** AUTHOR "?/be"; PURPOSE "ISO 9660 File System (ported from Native Oberon)"; *)
IMPORT SYSTEM, Modules, Files, KernelLog, Strings;
CONST
debug = FALSE; nameDebug = FALSE;
SS = 2048;
MaxBufs = 4;
Directory = 1;
eFileDoesNotExist = 8903;
eCannotOpenSubDir = 8916;
eInvalidFirstCluster = 8917;
eNameIsWild = 8927;
eInvalidFileName = 8941;
eInvalidVolume = 9000;
TYPE
Filename = ARRAY 256 OF CHAR;
VolDesc = POINTER TO RECORD
root, rootDirSize: LONGINT (* sector number of root directory and root directory size *)
END;
Buffer = POINTER TO RECORD (Files.Hint)
pos, lim: LONGINT;
next: Buffer;
data: POINTER TO ARRAY OF CHAR
END;
FileSystem = OBJECT(Files.FileSystem)
VAR
pri, sup, cur: VolDesc;
jolietLevel: LONGINT;
(** Open an existing file. The same file descriptor is returned if a file is opened multiple times. End users use Files.Old instead. *)
PROCEDURE Old0(name: ARRAY OF CHAR): Files.File;
VAR f: File; namebuf: Filename; dircl, dirpos, time, date, filecl, len: LONGINT; attr: SET; res: INTEGER;
BEGIN {EXCLUSIVE}
res := 0; f := NIL;
Check(name, namebuf, res);
IF res = 0 THEN
LocateFile(namebuf, SELF, dircl, dirpos, time, date, filecl, len, attr, res);
IF debug THEN LogString("Old0; filecl: "); LogInt(filecl); LogLn END;
IF res = 0 THEN
IF Directory IN attr THEN res := eCannotOpenSubDir
ELSIF filecl < 16 THEN res := eInvalidFirstCluster
ELSE f := OpenFile(namebuf, SELF, dircl, dirpos, time, date, filecl, len, attr)
END
END
END;
RETURN f
END Old0;
(** Enumerate canonical file names. mask may contain * wildcards. For internal use only. End users use Enumerator instead. *)
PROCEDURE Enumerate0(mask: ARRAY OF CHAR; flags: SET; enum: Files.Enumerator);
VAR
fname, name, mmask, pname, fullname: Filename;
f: Files.File; R: Files.Rider;
attr: SET; bAddToEnum: BOOLEAN;
pos, time, date, len, cl: LONGINT; res: INTEGER;
BEGIN {EXCLUSIVE}
Check(mask, name, res);
IF (res = 0) OR (res = eNameIsWild) THEN
SeparateName(name, name, mmask); IF (mmask = "") THEN COPY("*", mmask) END;
Files.JoinName(prefix, name, pname);
IF nameDebug THEN LogString("Enumerate; dir name: "); LogString(pname); LogLn END;
f := OldDir(SELF, name);
IF f # NIL THEN
f.Set(R, 0); pos := 0;
LOOP
MatchFile(R, mmask, fname, pos, cl, time, date, len, attr, res);
IF res # 0 THEN EXIT END;
COPY(pname, fullname);
IF name[0] # 0X THEN Files.AppendStr("/", fullname) END;
Files.AppendStr(fname, fullname);
bAddToEnum := TRUE;
IF Directory IN attr THEN
IF (fname = ".") OR (fname = "..") THEN
bAddToEnum := FALSE;
END;
flags := { Files.Directory };
len := 0;
ELSE
flags := {};
END;
IF bAddToEnum THEN
enum.PutEntry(fullname, flags, time, date, len)
END;
END;
ELSE res := eFileDoesNotExist
END
END
END Enumerate0;
(** Return the unique non-zero key of the named file, if it exists. *)
PROCEDURE FileKey*(name: ARRAY OF CHAR): LONGINT;
VAR res: INTEGER; namebuf: Filename; t, key, filecl: LONGINT; attr: SET;
BEGIN {EXCLUSIVE}
IF nameDebug THEN LogString("OFSFATFiles.FileKey: "); LogString(name); LogLn END;
key := 0;
Check(name, namebuf, res);
IF res = 0 THEN
LocateFile(namebuf, SELF, t, t, t, t, filecl, t, attr, res);
IF res = 0 THEN key := filecl END
END;
RETURN key
END FileKey;
(** Finalize the file system. *)
PROCEDURE Finalize*;
BEGIN {EXCLUSIVE}
vol.Finalize;
Finalize^
END Finalize;
END FileSystem;
File = OBJECT (Files.File)
VAR
len,
time, date,
filecl: LONGINT; (* first cluster *)
attr: SET ; (* ISO file attributes *)
(* directory info *)
name: Filename;
dircl, (* start cluster of dir. that contains entry for file *)
dirpos: LONGINT; (* position in cluster of dir. in which entry lies *)
nofbufs: INTEGER;
firstbuf: Buffer;
PROCEDURE Set*(VAR r: Files.Rider; pos: LONGINT);
BEGIN {EXCLUSIVE}
r.eof := FALSE; r.res := 0; r.file := SELF; r.fs := fs;
IF (pos < 0) THEN r.apos := 0
ELSIF (pos < len) THEN r.apos := pos
ELSE r.apos := len
END;
r.hint := firstbuf
END Set;
PROCEDURE Pos*(VAR r: Files.Rider): LONGINT;
BEGIN {EXCLUSIVE}
RETURN r.apos
END Pos;
PROCEDURE FindBuf(pos: LONGINT; hint: Buffer): Buffer;
VAR buf: Buffer;
BEGIN
buf := hint;
LOOP
IF (pos >= buf.pos) & (pos < buf.pos+buf.lim) THEN EXIT END;
buf := buf.next;
IF buf = hint THEN buf := NIL; EXIT END
END;
RETURN buf
END FindBuf;
PROCEDURE ReadBuf(buf: Buffer; pos: LONGINT);
BEGIN
ASSERT(pos <= len, 100);
buf.pos := pos - pos MOD fs.vol.blockSize;
pos := pos DIV fs.vol.blockSize;
IF pos = len DIV fs.vol.blockSize THEN buf.lim := len MOD fs.vol.blockSize
ELSE buf.lim := fs.vol.blockSize
END;
IF debug THEN LogString("ReadBuf; block: "); LogInt(filecl+pos); LogLn END;
fs.vol.GetBlock(filecl+pos, buf.data^)
END ReadBuf;
PROCEDURE GetBuf(pos: LONGINT; hint: Buffer): Buffer;
VAR buf: Buffer;
BEGIN
buf := FindBuf(pos, hint);
IF buf = NIL THEN
IF nofbufs < MaxBufs THEN (*allocate new buffer*)
NEW(buf); NEW(buf.data, fs.vol.blockSize);
buf.next := firstbuf.next; firstbuf.next := buf;
INC(nofbufs)
ELSE (*reuse one of the buffers; round robin *)
buf := firstbuf; firstbuf := buf.next;
END;
ReadBuf(buf, pos);
END;
RETURN buf
END GetBuf;
PROCEDURE Read*(VAR r: Files.Rider; VAR x: CHAR);
VAR buf: Buffer;
BEGIN {EXCLUSIVE}
r.res := 0;
IF (r.apos < len) THEN
buf := GetBuf(r.apos, r.hint(Buffer));
x := buf.data[r.apos-buf.pos];
INC(r.apos)
ELSE
x := 0X; r.eof := TRUE
END
END Read;
PROCEDURE ReadBytes*(VAR r: Files.Rider; VAR x: ARRAY OF CHAR; ofs, len: LONGINT);
VAR m, pos: LONGINT; src: ADDRESS; buf: Buffer;
BEGIN {EXCLUSIVE}
IF LEN(x) < len THEN SYSTEM.HALT(19) END;
IF len <= 0 THEN RETURN END;
buf := r.hint(Buffer);
m := SELF.len - r.apos;
IF len <= m THEN r.res := 0 ELSE r.eof := TRUE; r.res := len-m; len := m END;
WHILE len > 0 DO
buf := GetBuf(r.apos, buf);
pos := r.apos - buf.pos;
src := ADDRESSOF(buf.data[pos]); m := buf.lim-pos;
IF m > len THEN m := len END;
SYSTEM.MOVE(src, ADDRESSOF(x[ofs]), m);
DEC(len, m); INC(ofs, m); INC(r.apos, m);
END;
r.hint := buf
END ReadBytes;
PROCEDURE Length*(): LONGINT;
BEGIN RETURN len
END Length;
PROCEDURE GetDate*(VAR t, d: LONGINT);
BEGIN t := time; d := date
END GetDate;
PROCEDURE GetName*(VAR name: ARRAY OF CHAR);
BEGIN COPY(SELF.name, name)
END GetName;
PROCEDURE Update*;
END Update; (* nothing *)
END File;
VAR (* svr *)
ExtractNameProc: PROCEDURE(VAR dir, name: ARRAY OF CHAR);
(* debug procedures *)
PROCEDURE LogString(s: ARRAY OF CHAR);
BEGIN
KernelLog.String(s)
END LogString;
PROCEDURE LogInt(i: LONGINT);
BEGIN
KernelLog.Int(i, 0)
END LogInt;
PROCEDURE LogLn;
BEGIN
KernelLog.Ln
END LogLn;
(* help procedures *)
(* Get733 - 32 bit, both byte orders *)
PROCEDURE Get733(VAR s: ARRAY OF CHAR; first: LONGINT; VAR d: LONGINT);
BEGIN
d := LONG(ORD(s[first]));
d := d + LONG(ORD(s[first+1]))*100H;
d := d + LONG(ORD(s[first+2]))*10000H;
d := d + LONG(ORD(s[first+3]))*1000000H
END Get733;
(* Check - check filename. Return correct name, or empty name if incorrect. *)
PROCEDURE Check(s: ARRAY OF CHAR; VAR name: Filename; VAR res: INTEGER);
VAR i, j: LONGINT; ch: CHAR;
BEGIN
IF nameDebug THEN LogString("Check: "); LogString(s) END;
res := 0; i := 0;
IF (s[0] = "/") OR (s[0] = "\") THEN j := 1 ELSE j := 0 END; (* remove leading / or \ *)
LOOP
ch := s[j];
IF ch = 0X THEN EXIT END;
IF ch = "\" THEN ch := "/" END;
IF (ch < " ") OR (ch >= 07FX) THEN res := eInvalidFileName; i := 0; EXIT END;
IF (ch = "?") OR (ch = "*") THEN res := eNameIsWild END;
name[i] := ch;
INC(i); INC(j)
END;
name[i] := 0X;
IF nameDebug THEN LogString(" => "); LogString(name); LogLn END;
END Check;
PROCEDURE GetVolumeDescriptors(fs: FileSystem; res: INTEGER);
VAR b: ARRAY SS OF CHAR; i: LONGINT; vol: Files.Volume;
BEGIN
vol := fs.vol;
i := 16; fs.pri := NIL; fs.sup := NIL; fs.cur := NIL; fs.jolietLevel := 0;
REPEAT
vol.GetBlock(i, b); (* read boot sector *)
CASE b[0] OF
1X: (* Primary volume descriptor *)
ASSERT(fs.pri = NIL, 102); (* exactly one primary volume desc *)
NEW(fs.pri);
Get733(b, 158, fs.pri.root); (* location of root directory *)
Get733(b, 166, fs.pri.rootDirSize) (* size of root directory in bytes *)
| 2X: (* Supplementary volume descriptor *)
ASSERT(fs.sup = NIL, 103); (* 0 or 1 supplementary volume descriptor *)
ASSERT((b[88] = 25X) & (b[89] = 2FX) & ((b[90] = 40X) OR (b[90] = 43X) OR (b[90] = 45X)), 104);
IF b[90] = 40X THEN fs.jolietLevel := 1
ELSIF b[90] = 43X THEN fs.jolietLevel := 2
ELSIF b[90] = 45X THEN fs.jolietLevel := 3
END;
NEW(fs.sup);
Get733(b, 158, fs.sup.root); (* location of root directory *)
Get733(b, 166, fs.sup.rootDirSize) (* size of root directory in bytes *)
ELSE ASSERT((b[0] = 0X) OR (b[0] = 2X) OR (b[0] = 0FFX), 100) (* boot or end *)
END;
INC(i)
UNTIL (res # 0) OR (b[0] = 0FFX);
IF res = 0 THEN
IF fs.pri = NIL THEN res := eInvalidVolume
ELSIF fs.sup # NIL THEN fs.cur := fs.sup; ExtractNameProc := ExtractLongName
ELSE fs.cur := fs.pri; ExtractNameProc := ExtractShortName
END
END;
END GetVolumeDescriptors;
(* GetDir - Get information from a directory entry *)
PROCEDURE GetDir(VAR dir, fname: ARRAY OF CHAR; VAR time, date, cl, len: LONGINT; VAR attr: SET);
VAR t: LONGINT;
BEGIN
ExtractName(dir, fname);
t (*attr*) := ORD(dir[24]); attr := SYSTEM.VAL(SET, t);
time := LONG(ORD(dir[20]))*64*64 + LONG(ORD(dir[21]))*64 + LONG(ORD(dir[22]));
date := LONG(ORD(dir[17]))*32*16 + LONG(ORD(dir[18]))*16 + LONG(ORD(dir[19]));
Get733(dir, 1, cl);
Get733(dir, 9, len)
END GetDir;
PROCEDURE SplitName(str: ARRAY OF CHAR; VAR prefix, name: ARRAY OF CHAR);
VAR i, j: LONGINT;
BEGIN
IF nameDebug THEN LogString("SplitName: "); LogString(str) END;
i := -1; j := -1;
REPEAT INC(i); INC(j); prefix[j] := str[i] UNTIL (str[i] = 0X) OR (str[i] = "/");
IF str[i] = "/" THEN
prefix[j] := 0X; j := -1;
REPEAT INC(i); INC(j); name[j] := str[i] UNTIL name[j] = 0X
ELSE name[0] := 0X
END;
IF nameDebug THEN LogString(" => "); LogString(prefix); LogString(", "); LogString(name); LogLn END
END SplitName;
(* SeparateName - separate str into a prefix and a name. *)
PROCEDURE SeparateName(str: ARRAY OF CHAR; VAR prefix: ARRAY OF CHAR; VAR name: Filename);
VAR i, j : LONGINT;
BEGIN
(* Pre: str is result of a Check operation; all "\"s have been changed to "/" *)
i := 0; j := -1;
WHILE str[i] # 0X DO
IF str[i] = "/" THEN j := i END;
INC(i)
END;
IF j >= 0 THEN
COPY(str, prefix); prefix[j] := 0X;
i := -1;
REPEAT INC(i); INC(j); name[i] := str[j] UNTIL name[i] = 0X
ELSE COPY(str, name); prefix[0] := 0X
END
END SeparateName;
PROCEDURE ExtractShortName(VAR dir, name: ARRAY OF CHAR);
VAR i, j, len: LONGINT;
BEGIN
len := ORD(dir[31]);
i := 0; j := 32;
WHILE (i < len) & (dir[j] # ";") DO name[i] := dir[j]; INC(i); INC(j) END;
name[i] := 0X;
END ExtractShortName;
PROCEDURE ExtractLongName(VAR dir, name: ARRAY OF CHAR);
VAR i, j, end: LONGINT;
BEGIN
end := 33+ORD(dir[31]);
i := 0; j := 33;
WHILE (j < end) & (dir[j] # ";") DO name[i] := dir[j]; INC(i); INC(j, 2) END;
name[i] := 0X;
END ExtractLongName;
PROCEDURE ExtractName(VAR dir, name: ARRAY OF CHAR);
VAR len: LONGINT;
BEGIN
len := ORD(dir[31]);
IF len = 1 THEN
IF dir[33] = 0X THEN COPY(".", name)
ELSIF dir[33] = 1X THEN COPY("..", name)
END
ELSE ExtractNameProc(dir, name)
END
END ExtractName;
PROCEDURE MatchFile(VAR R: Files.Rider; name: ARRAY OF CHAR; VAR fname: ARRAY OF CHAR;
VAR pos, cl, time, date, len: LONGINT; VAR attr: SET; VAR res: INTEGER);
VAR
found: BOOLEAN; f: File; fs: FileSystem; buf: ARRAY 256 OF CHAR; entryLen, padding: LONGINT;
BEGIN
f := R.file(File); fs := R.fs(FileSystem);
found := FALSE;
LOOP
pos := R.file.Pos(R);
IF debug THEN LogString("MatchFile; pos: "); LogInt(pos); LogLn END;
R.file.Read(R, buf[0]); entryLen := ORD(buf[0]);
IF debug THEN LogString("MatchFile; entryLen: "); LogInt(entryLen); LogLn END;
IF R.eof THEN
IF debug THEN LogString("MatchFile; eof"); LogLn END;
EXIT
END;
IF entryLen > 0 THEN
R.file.ReadBytes(R, buf, 0, entryLen-1);
GetDir(buf, fname, time, date, cl, len, attr);
found := Strings.Match(name, fname);
ELSE (* Directory entries may not cross sectors. Skip padding bytes *)
padding := SS - (R.file.Pos(R) MOD SS);
IF (padding > 0) & (padding <= 256) THEN
IF debug THEN LogString("MatchFile; skip padding: "); LogInt(padding); LogLn; END;
R.file.ReadBytes(R, buf, 0, padding); (* Skip padding *)
ELSE
IF debug THEN LogString("MatchFile; Confusing directory structure... givin' up"); END;
EXIT;
END;
END;
IF found THEN EXIT END;
END;
IF found THEN res := 0 ELSE res := eInvalidFileName END;
END MatchFile;
PROCEDURE FindFile(fs: FileSystem; name: ARRAY OF CHAR; dircl, dirlen: LONGINT;
VAR dirpos, time, date, filecl, len: LONGINT; VAR attr: SET; VAR res: INTEGER);
VAR f: File; R: Files.Rider; fname: Filename;
BEGIN
ASSERT(name # "", 100);
f := OpenFile("", fs, -1, -1, -1, -1, dircl, dirlen, {});
f.Set(R, 0);
MatchFile(R, name, fname, dirpos, filecl, time, date, len, attr, res);
END FindFile;
PROCEDURE LocateFile(name: ARRAY OF CHAR; fs: FileSystem;
VAR dircl, dirpos, time, date, filecl, len: LONGINT; VAR attr: SET; VAR res: INTEGER);
VAR cur: Filename; dirlen: LONGINT;
BEGIN
res := 0;
dircl := fs.cur.root; dirlen := fs.cur.rootDirSize; (* start in root directory *)
IF name[0] = 0X THEN (* root dir *)
filecl := dircl; attr := {Directory};
len := dirlen; dirpos := -1;
ELSE
LOOP
SplitName(name, cur, name);
FindFile(fs, cur, dircl, dirlen, dirpos, time, date, filecl, len, attr, res);
IF (res = 0) & (name # "") & ~(Directory IN attr) THEN res := eInvalidFileName END;
IF (res # 0) OR (name = "") THEN EXIT END;
dircl := filecl; dirlen := len
END
END
END LocateFile;
PROCEDURE OpenFile(name: ARRAY OF CHAR; fs: FileSystem; dircl, dirpos, time, date, filecl, len: LONGINT; attr: SET): File;
VAR f: File; buf: Buffer;
BEGIN
NEW(f); COPY(name, f.name); f.fs := fs; f.key := filecl;
f.dircl := dircl; f.dirpos := dirpos; f.time := time; f.date := date;
f.filecl := filecl; f.len := len; f.attr := attr;
NEW(buf); buf.next := buf;
NEW(buf.data, fs.vol.blockSize);
IF f.len = 0 THEN buf.pos := 0; buf.lim := 0
ELSE f.ReadBuf(buf, 0) (* file is not empty *)
END;
f.firstbuf := buf; f.nofbufs := 1;
RETURN f
END OpenFile;
PROCEDURE OldDir(fs: Files.FileSystem; name: ARRAY OF CHAR): Files.File;
VAR f: File; dircl, dirpos, time, date, filecl, len: LONGINT; attr: SET; res: INTEGER;
BEGIN
res := 0; f := NIL;
LocateFile(name, fs(FileSystem), dircl, dirpos, time, date, filecl, len, attr, res);
IF res = 0 THEN
IF ~(Directory IN attr) THEN res := eCannotOpenSubDir
ELSIF filecl < 16 THEN res := eInvalidFirstCluster
ELSE f := OpenFile(name, fs(FileSystem), dircl, dirpos, time, date, filecl, len, attr)
END
END;
RETURN f
END OldDir;
(** Generate a new file system object. OFS.NewVol has volume parameter, OFS.Par has mount prefix. *)
PROCEDURE NewFS*(context : Files.Parameters);
VAR fs: FileSystem; res: INTEGER;
BEGIN
IF Files.This(context.prefix) = NIL THEN
NEW(fs); fs.vol := context.vol;
GetVolumeDescriptors(fs, res);
IF res = 0 THEN
IF debug THEN
LogString(" primary root: "); LogInt(fs.pri.root); LogLn;
LogString(" primary root dir size: "); LogInt(fs.pri.rootDirSize); LogLn
END;
fs.desc[0] := 0X; Files.AppendStr(context.vol.name, fs.desc); Files.AppendStr(" / ISO9660FS", fs.desc);
Files.Add(fs, context.prefix)
ELSE context.error.String("No ISO9660 file system found on "); context.error.String(context.vol.name); context.error.Ln;
END
ELSE context.error.String(context.prefix); context.error.String(" already in use"); context.error.Ln;
END;
END NewFS;
(* Clean up when module freed. *)
PROCEDURE Cleanup;
VAR ft: Files.FileSystemTable; i: LONGINT;
BEGIN
IF Modules.shutdown = Modules.None THEN
Files.GetList(ft);
IF ft # NIL THEN
FOR i := 0 TO LEN(ft^)-1 DO
IF ft[i] IS FileSystem THEN Files.Remove(ft[i]) END
END
END
END
END Cleanup;
BEGIN
Modules.InstallTermHandler(Cleanup)
END ISO9660Files.
ISO9660Volumes.Mod
Partitions.Show
OFSTools.Mount CD IsoFS IDE2 ~
OFSTools.Unmount CD0 ~
SystemTools.Free ISO9660Files ISO9660Volumes ~
System.Directory CD:*.* ~
History:
30.05.2006 Fixed MatchFile: Directory entries may not cross sector boundaries. Skip padding bytes if necessary. (staubesv)