-- 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.