-- Directory.Mesa Edited by Sandman on July 9, 1980 4:13 PM -- Copyright Xerox Corporation 1979, 1980 DIRECTORY AltoDefs USING [BytesPerWord], AltoFileDefs USING [DEfile, DEfree, DirFP, DV, FA, FilenameChars, FP], BFSDefs USING [CreateFile, MakeCFP, MakeFP], DirectoryDefs USING [DEptr], InlineDefs USING [BITAND, COPY], NucleusOps USING [], SegmentDefs USING [ AccessOptions, FileHandle, InsertFile, InsufficientVM, LockFile, NewFile, OldFileOnly, ReleaseFile, UnlockFile], StreamDefs USING [ AccessOptions, Append, CreateWordStream, JumpToFA, Read, ReadBlock, SetPosition, DiskHandle, Write, WriteBlock], StreamScan USING [Finish, GetBuffer, Init, Handle], StringDefs USING [ AppendChar, BcplSTRING, BcplToMesaString, MesaToBcplString, WordsForBcplString], Storage USING [FreePages, Pages]; Directory: PROGRAM IMPORTS BFSDefs, InlineDefs, SegmentDefs, StreamDefs, StreamScan, StringDefs, Storage EXPORTS DirectoryDefs, NucleusOps =PUBLIC BEGIN OPEN AltoFileDefs, StreamDefs, StringDefs; DEptr: PRIVATE TYPE = DirectoryDefs.DEptr; FPptr: TYPE = POINTER TO AltoFileDefs.FP; HDptr: TYPE = POINTER TO HD; BadFilename: SIGNAL [name: STRING] = CODE; BadDirectory: SIGNAL = CODE; Enumerate: PROCEDURE [ dir: DiskHandle, proc: PROCEDURE [FPptr, STRING] RETURNS [BOOLEAN]] = BEGIN false: BOOLEAN ← FALSE; PassItOn: PROCEDURE [i: CARDINAL, ssd: StreamScan.Handle, de: DEptr] RETURNS [BOOLEAN] = BEGIN s: STRING ← [AltoFileDefs.FilenameChars]; fp: AltoFileDefs.FP; BFSDefs.MakeFP[@fp, @de.dv.fp]; BcplToMesaString[@de.name, s]; RETURN[proc[@fp, s]] END; [] ← EnumerateEntries[dir, PassItOn, @false]; RETURN END; EnumerateDirectory: PROCEDURE [ proc: PROCEDURE [FPptr, STRING] RETURNS [BOOLEAN]] = BEGIN dir: DiskHandle ← CreateWordStream[workingDir, Read]; Enumerate[dir, proc]; dir.destroy[dir]; RETURN END; Insert: PROCEDURE [dir: DiskHandle, fp: FPptr, name: STRING] RETURNS [old: BOOLEAN] = BEGIN hd: HD; fn: STRING = [AltoFileDefs.FilenameChars]; tFP: AltoFileDefs.FP; MakeLegalFileName[name, fn, 0, name.length]; old ← FindName[dir, @tFP, fn, @hd].found; IF old THEN RETURN; MakeEntry[dir, fp, name, @hd]; RETURN END; Lookup: PROCEDURE [dir: DiskHandle, fp: FPptr, name: STRING, create: BOOLEAN] RETURNS [old: BOOLEAN] = BEGIN hd: HD; fn: STRING = [AltoFileDefs.FilenameChars]; MakeLegalFileName[name, fn, 0, name.length]; old ← FindName[dir, fp, fn, @hd].found; IF ~old AND create THEN BEGIN BFSDefs.CreateFile[fn, fp, @dir.file.fp]; MakeEntry[dir, fp, fn, @hd]; END; RETURN END; DirectoryLookup: PROCEDURE [fp: FPptr, name: STRING, create: BOOLEAN] RETURNS [old: BOOLEAN] = BEGIN fn: STRING = [AltoFileDefs.FilenameChars]; hd: HD; access: AccessOptions = IF ~create THEN Read ELSE Read + Write + Append; dir: DiskHandle = ParseFileName[name, fn, access]; old ← FindName[dir, fp, fn, @hd].found; IF ~old AND create THEN BEGIN BFSDefs.CreateFile[fn, fp, @dir.file.fp]; MakeEntry[dir, fp, fn, @hd]; END; dir.destroy[dir]; RETURN END; LookupFP: PROCEDURE [dir: DiskHandle, fp: FPptr, name: STRING] RETURNS [old: BOOLEAN] = BEGIN RETURN[FindFP[dir, fp, name].found]; END; DirectoryLookupFP: PROCEDURE [fp: FPptr, name: STRING] RETURNS [old: BOOLEAN] = BEGIN dir: DiskHandle = CreateWordStream[workingDir, Read]; old ← FindFP[dir, fp, name].found; dir.destroy[dir]; RETURN END; Purge: PROCEDURE [dir: DiskHandle, fp: FPptr, name: STRING] RETURNS [found: BOOLEAN] = BEGIN index: CARDINAL; fn: STRING = [AltoFileDefs.FilenameChars]; MakeLegalFileName[name, fn, 0, name.length]; [found, index] ← FindName[dir, fp, fn, NIL]; IF found THEN DeleteEntry[dir, index]; RETURN END; DirectoryPurge: PROCEDURE [fp: FPptr, name: STRING] RETURNS [found: BOOLEAN] = BEGIN index: CARDINAL; fn: STRING = [AltoFileDefs.FilenameChars]; dir: DiskHandle ← ParseFileName[name, fn, Read + Write]; [found, index] ← FindName[dir, fp, fn, NIL]; IF found THEN DeleteEntry[dir, index]; dir.destroy[dir]; RETURN END; PurgeFP: PROCEDURE [dir: DiskHandle, fp: FPptr] RETURNS [found: BOOLEAN] = BEGIN index: CARDINAL; [found, index] ← FindFP[dir, fp, NIL]; IF found THEN DeleteEntry[dir, index]; RETURN END; DirectoryPurgeFP: PROCEDURE [fp: FPptr] RETURNS [found: BOOLEAN] = BEGIN dir: DiskHandle ← CreateWordStream[workingDir, Read + Write]; found ← PurgeFP[dir, fp]; dir.destroy[dir]; RETURN END; -- Support Routines DEugly: INTEGER = AltoFileDefs.DEfile + 1; -- for malformed DEfile entries. BcplWords: INTEGER = 20; -- WordsForBcplString[FilenameChars]; HD: TYPE = RECORD [size, needed, index: CARDINAL]; maxBuffers: CARDINAL = 2; upMask: CARDINAL = 137B; -- masks lower case to upper case (sort of) EnumerateEntries: PROCEDURE [ dir: DiskHandle, proc: PROCEDURE [CARDINAL, StreamScan.Handle, DEptr] RETURNS [BOOLEAN], inspectFree: POINTER TO READONLY BOOLEAN, lengthFilter: CARDINAL ← 0] RETURNS [index: CARDINAL] = BEGIN OPEN AltoFileDefs; minLength: CARDINAL = IF lengthFilter = 0 THEN 0 ELSE SIZE[AltoFileDefs.DV] + lengthFilter/2 + 1; buffer: POINTER; pos, endPos: CARDINAL ← 0; deSpace: ARRAY [0..SIZE[DV] + BcplWords) OF UNSPECIFIED; tempDE: DEptr = LOOPHOLE[@deSpace]; ssd: StreamScan.Handle; bufArray: ARRAY [0..maxBuffers) OF POINTER; nBufs: CARDINAL; AdvanceBuffer: PROCEDURE = BEGIN buffer ← StreamScan.GetBuffer[ssd]; index ← index + endPos; pos ← pos - endPos; endPos ← ssd.numChars/AltoDefs.BytesPerWord END; -- allocate buffers for scanning FOR nBufs ← 0, nBufs + 1 UNTIL nBufs = maxBuffers DO bufArray[nBufs] ← Storage.Pages[ 1 ! SegmentDefs.InsufficientVM => IF nBufs # 0 THEN EXIT]; ENDLOOP; dir.reset[dir]; index ← 0; ssd ← StreamScan.Init[dir, @bufArray, nBufs]; AdvanceBuffer[]; -- now search until eof UNTIL buffer = NIL DO BEGIN thisDE: DEptr ← buffer + pos; IF thisDE.dv.length = 0 THEN ERROR BadDirectory; SELECT thisDE.dv.type FROM DEfile => IF thisDE.dv.length >= minLength THEN BEGIN -- this might be an interesting entry thisIndex: CARDINAL = index + pos; length: CARDINAL; pos ← pos + (length ← thisDE.dv.length); IF pos > endPos THEN -- overflow BEGIN OPEN InlineDefs; COPY[from: thisDE, to: tempDE, nwords: length - (pos - endPos)]; AdvanceBuffer[]; COPY[from: buffer, to: tempDE + length - pos, nwords: pos]; thisDE ← tempDE; END; IF (thisDE.name.length = lengthFilter OR lengthFilter = 0) AND proc[ thisIndex, ssd, thisDE] THEN BEGIN index ← thisIndex; EXIT END; END ELSE pos ← pos + thisDE.dv.length; DEfree => BEGIN thisIndex: CARDINAL ← index + pos; IF inspectFree↑ AND proc[thisIndex, ssd, thisDE] THEN BEGIN index ← thisIndex; EXIT END; pos ← pos + thisDE.dv.length; END; ENDCASE => pos ← pos + thisDE.dv.length; WHILE pos >= endPos AND buffer # NIL DO AdvanceBuffer[]; ENDLOOP; END; ENDLOOP; -- finish the scan and free the buffers StreamScan.Finish[ssd]; BEGIN i: CARDINAL; FOR i IN [0..nBufs) DO Storage.FreePages[bufArray[i]]; ENDLOOP; END; RETURN END; FindName: PRIVATE PROCEDURE [ dir: DiskHandle, fp: FPptr, name: STRING, hd: HDptr] RETURNS [found: BOOLEAN, index: CARDINAL] = BEGIN OPEN AltoFileDefs; inspectFree: BOOLEAN ← TRUE; holeFA: FA; MatchName: PROCEDURE [i: CARDINAL, ssd: StreamScan.Handle, de: DEptr] RETURNS [BOOLEAN] = BEGIN SELECT de.dv.type FROM DEfile => BEGIN n: STRING = capitalizedName; i: CARDINAL; FOR i IN [0..n.length) DO IF n[i] # InlineDefs.BITAND[de.name.char[i], upMask] THEN RETURN[FALSE]; ENDLOOP; BFSDefs.MakeFP[fp, @de.dv.fp]; found ← TRUE; END; DEfree => BEGIN -- executed only if inspectFree (and therefore hd#NIL) IF hd.index + hd.size # i OR i = 0 THEN -- not adjacent to previous hole BEGIN hd.index ← i; hd.size ← 0; holeFA ← [da: ssd.da, page: ssd.pageNumber, byte: 0]; END; hd.size ← hd.size + de.dv.length; IF hd.size >= hd.needed THEN inspectFree ← FALSE; RETURN[FALSE] END; ENDCASE; -- SIGNAL BadDirectory; RETURN[found] END; -- Note: the following is not a true capitalization algorithm, but -- it maps non-alphabetics into unique characters that are not -- legal in Alto file names. i: CARDINAL; capitalizedName: STRING = [FilenameChars]; capitalizedName.length ← name.length; FOR i IN [0..name.length) DO capitalizedName[i] ← InlineDefs.BITAND[name[i], upMask]; ENDLOOP; IF hd = NIL THEN inspectFree ← FALSE ELSE hd↑ ← HD[0, SIZE[DV] + WordsForBcplString[name.length], 0]; found ← FALSE; index ← EnumerateEntries[dir, MatchName, @inspectFree, name.length]; IF inspectFree THEN hd.index ← index ELSE IF ~found THEN JumpToFA[dir, @holeFA]; RETURN END; FindFP: PRIVATE PROCEDURE [dir: DiskHandle, fp: FPptr, name: STRING] RETURNS [found: BOOLEAN, index: CARDINAL] = BEGIN MatchFP: PROCEDURE [i: CARDINAL, ssd: StreamScan.Handle, de: DEptr] RETURNS [BOOLEAN] = BEGIN -- de is guaranteed to be type DEFile f: AltoFileDefs.FP; BFSDefs.MakeFP[@f, @de.dv.fp]; IF (found ← f = fp↑) AND name # NIL THEN BEGIN name.length ← 0; BcplToMesaString[@de.name, name]; END; RETURN[found] END; found ← FALSE; index ← EnumerateEntries[dir, MatchFP, @found]; RETURN END; MakeEntry: PRIVATE PROCEDURE [ dir: DiskHandle, fp: FPptr, name: STRING, hd: HDptr] = BEGIN OPEN AltoFileDefs; leftover: CARDINAL; dv: DV ← DV[DEfile, hd.needed, ]; dn: ARRAY [0..BcplWords) OF UNSPECIFIED; bcpl: POINTER TO BcplSTRING ← @dn[0]; IF name.length > AltoFileDefs.FilenameChars THEN ERROR BadFilename[name]; BFSDefs.MakeCFP[@dv.fp, fp]; MesaToBcplString[name, bcpl]; IF bcpl.length MOD 2 = 0 THEN bcpl.char[bcpl.length] ← 0C; SetPosition[dir, 2*hd.index]; [] ← WriteBlock[dir, @dv, SIZE[DV]]; [] ← WriteBlock[dir, bcpl, hd.needed - SIZE[DV]]; IF (leftover ← hd.size - hd.needed) > 0 THEN BEGIN dv ← DV[DEfree, leftover, ]; [] ← WriteBlock[dir, @dv, 1]; END; RETURN END; DeleteEntry: PRIVATE PROCEDURE [dir: DiskHandle, index: CARDINAL] = BEGIN dv: AltoFileDefs.DV; SetPosition[dir, 2*index]; [] ← ReadBlock[dir, @dv, 1]; dv.type ← AltoFileDefs.DEfree; SetPosition[dir, 2*index]; [] ← WriteBlock[dir, @dv, 1]; RETURN END; -- Filename Parsing ParseFileName: PROCEDURE [ name, filename: STRING, dirAccess: SegmentDefs.AccessOptions] RETURNS [StreamDefs.DiskHandle] = BEGIN OPEN SegmentDefs; dirFile: FileHandle; dirName: STRING = [100]; i: CARDINAL; sep: CARDINAL ← 0; filename.length ← 0; FOR i DECREASING IN [0..name.length) DO IF name[i] = '< OR name[i] = '> THEN BEGIN sep ← i; MakeLegalFileName[name, dirName, 0, sep]; MakeLegalFileName[name, filename, sep + 1, name.length]; EXIT END; REPEAT FINISHED => MakeLegalFileName[name, filename, 0, name.length]; ENDLOOP; IF dirName.length = 0 THEN BEGIN IF name[sep] = '< THEN dirFile ← sysDir ELSE dirFile ← workingDir; END ELSE dirFile ← NewFile[dirName, dirAccess, OldFileOnly]; RETURN[StreamDefs.CreateWordStream[dirFile, dirAccess]] END; MakeLegalFileName: PRIVATE PROCEDURE [ src, dest: STRING, first, last: CARDINAL] = BEGIN i: CARDINAL; char: CHARACTER; FOR i IN [first..last) DO char ← src[i]; SELECT char FROM IN ['a..'z], IN ['A..'Z], IN ['0..'9], '., '$, '!, '?, '+, '-, '<, '> => StringDefs.AppendChar[dest, char]; ENDCASE => StringDefs.AppendChar[dest, '-]; ENDLOOP; IF dest.length = 0 OR dest[dest.length - 1] # '. THEN StringDefs.AppendChar[dest, '.]; RETURN END; SetWorkingDir: PROCEDURE [dir: SegmentDefs.FileHandle] = BEGIN OPEN SegmentDefs; IF dir # NIL AND dir.fp.serial.bits.directory = 0 THEN ERROR BadDirectory; UnlockFile[workingDir]; IF workingDir.segcount = 0 THEN ReleaseFile[workingDir]; workingDir ← IF dir = NIL THEN sysDir ELSE dir; LockFile[workingDir]; END; GetWorkingDir: PROCEDURE RETURNS [SegmentDefs.FileHandle] = BEGIN RETURN[workingDir] END; init: PRIVATE PROCEDURE = BEGIN OPEN SegmentDefs; fp: AltoFileDefs.FP ← AltoFileDefs.DirFP; sysDir ← workingDir ← InsertFile[@fp, Read + Write + Append]; LockFile[sysDir]; LockFile[workingDir]; END; -- Main Body sysDir, workingDir: SegmentDefs.FileHandle; init[]; END.