<> <> <> <> DIRECTORY BasicTime USING [FromPupTime, nullGMT], BcdDefs USING [Base, BcdBase, CTHandle, CTIndex, CTNull, FTHandle, FTIndex, FTNull, FTSelf, MTHandle, MTIndex, NameRecord, NameString, NullName, NullVersion, VersionID, VersionStamp], BcdOps USING [ProcessConfigs, ProcessFiles, ProcessModules], DFInternal USING [AbortDF, CheckAbort, Client, ClientDescriptor, DefaultInteractionProc, GetFileInfo, LocalFile, LocalFileInfo, RemoteFileInfo, ReportFSError, RetryFSOperation, SimpleInteraction], DFOperations USING [AbortInteraction, DFInfoInteraction, InfoInteraction, InteractionProc], DFUtilities USING [Date, DateToRope, DirectoryItem, FileItem, Filter, ImportsItem, IncludeItem, ParseFromStream, ProcessItemProc, RemoveVersionNumber, SyntaxError], FS USING [Close, Error, GetInfo, Open, OpenFile, PagesForBytes, Read, StreamOpen], List USING [CompareProc, UniqueSort], IO USING [Close, GetIndex, PutFR, STREAM], Rope USING [Compare, Concat, Equal, Fetch, Find, FromProc, Index, Length, ROPE, Substr], RopeHash USING [FromRope], VM USING [AddressForPageNumber, Allocate, Free, Interval, nullInterval, PageCount]; VerifyImpl: CEDAR PROGRAM IMPORTS BasicTime, BcdOps, DFInternal, DFUtilities, FS, List, IO, Rope, RopeHash, VM EXPORTS DFOperations = BEGIN ROPE: TYPE = Rope.ROPE; STREAM: TYPE = IO.STREAM; FileDesc: TYPE = RECORD [ shortName: ROPE _ NIL, path: ROPE _ NIL, importedFrom: REF FileDesc _ NIL, <> parent: ROPE _ NIL, <> date: DFUtilities.Date _ [], version: BcdDefs.VersionStamp _ BcdDefs.NullVersion, bcd: BOOL _ FALSE, needed: Needed _ $no ]; Needed: TYPE = {no, yes, wrongVersion}; htSize: NAT = 97; HashIndex: TYPE = [0..htSize); HashTable: TYPE = REF HashTableArray; HashTableArray: TYPE = ARRAY HashIndex OF LIST OF REF FileDesc; cacheSize: NAT = 64; Omission: TYPE = RECORD [ missing: ROPE, neededBy: ROPE ]; Verify: PUBLIC PROC [dfFile: ROPE, interact: DFOperations.InteractionProc _ NIL, clientData: REF ANY _ NIL, log: STREAM _ NIL] RETURNS [errors, warnings, filesActedUpon: INT _ 0] = { client: DFInternal.Client = NEW[DFInternal.ClientDescriptor _ [ (interact _ IF interact = NIL THEN DFInternal.DefaultInteractionProc ELSE interact), clientData, log ]]; rootList: LIST OF REF FileDesc _ NIL; hashTable: HashTable = NEW[HashTableArray _ ALL[NIL]]; bcdCache: BcdCache = CreateBcdCache[cacheSize]; omissions: LIST OF --REF Omission--REF ANY _ NIL; Hash: PROC [name: ROPE] RETURNS [HashIndex] = { RETURN[RopeHash.FromRope[rope: name, case: FALSE] MOD htSize]; }; EnterInHashTable: PROC [desc: REF FileDesc] RETURNS [BOOL _ TRUE] = { hi: HashIndex = Hash[desc.shortName]; FOR l: LIST OF REF FileDesc _ hashTable[hi], l.rest UNTIL l = NIL DO IF desc.shortName.Equal[l.first.shortName, FALSE] THEN { IF desc.date ~= l.first.date THEN { <> warnings _ warnings.SUCC; DFInternal.SimpleInteraction[ client, NEW[DFOperations.InfoInteraction _ [ class: $warning, message: IO.PutFR["'%g' appears more than once (via '%g' and '%g').", [rope[desc.shortName]], [rope[ IF l.first.importedFrom = NIL THEN l.first.parent ELSE FullName[l.first.importedFrom] ]], [rope[ IF desc.importedFrom = NIL THEN desc.parent ELSE FullName[desc.importedFrom] ]] ] ]] ]; }; RETURN[FALSE] }; ENDLOOP; hashTable[hi] _ CONS[desc, hashTable[hi]]; filesActedUpon _ filesActedUpon.SUCC; }; LookupInHashTable: PROC [shortName: ROPE] RETURNS [desc: REF FileDesc _ NIL] = { FOR l: LIST OF REF FileDesc _ hashTable[Hash[shortName]], l.rest UNTIL l = NIL DO IF shortName.Equal[l.first.shortName, FALSE] THEN RETURN[l.first]; ENDLOOP; }; EnumerateHashTable: PROC [proc: PROC [REF FileDesc]] = { FOR hi: HashIndex IN HashIndex DO FOR l: LIST OF REF FileDesc _ hashTable[hi], l.rest UNTIL l = NIL DO proc[l.first]; ENDLOOP; ENDLOOP; }; WorthRemembering: PROC [desc: REF FileDesc] RETURNS [BOOL _ FALSE] = { ext: ROPE = desc.shortName.Substr[start: desc.shortName.Index[s2: "."]]; exts: ARRAY [0..3) OF ROPE = [".bcd", ".mesa", ".config"]; FOR i: NAT IN [0..exts.LENGTH) DO IF ext.Equal[exts[i], FALSE] THEN {desc.bcd _ i = 0; RETURN[TRUE]}; ENDLOOP; }; Extant: PROC [desc: REF FileDesc] RETURNS [BOOL] = INLINE { RETURN[desc.date.gmt ~= BasicTime.nullGMT] }; importedFrom: REF FileDesc _ NIL; VerifyInner: PROC [dfFile: ROPE, date: DFUtilities.Date, filter: DFUtilities.Filter] RETURNS [finished: BOOL _ FALSE] = { directoryPath: ROPE _ NIL; DoOneItem: DFUtilities.ProcessItemProc = { DFInternal.CheckAbort[client]; WITH item SELECT FROM directory: REF DFUtilities.DirectoryItem => directoryPath _ directory.path1; file: REF DFUtilities.FileItem => { desc: REF FileDesc = NEW[FileDesc _ [ shortName: DFUtilities.RemoveVersionNumber[file.name], path: directoryPath, parent: dfFile, importedFrom: importedFrom ]]; remoteInfo: REF DFInternal.RemoteFileInfo = NEW[DFInternal.RemoteFileInfo _ [ name: FullName[desc], date: file.date ]]; DFInternal.GetFileInfo[info: remoteInfo, client: client, errorLevel: $warning ! FS.Error => CONTINUE ]; desc.date _ remoteInfo.date; IF (filter.list ~= NIL OR WorthRemembering[desc]) AND EnterInHashTable[desc] AND file.verifyRoot AND importedFrom = NIL THEN rootList _ CONS[desc, rootList]; }; imports: REF DFUtilities.ImportsItem => IF imports.form ~= $list AND filter.list = NIL THEN { warnings _ warnings.SUCC; DFInternal.SimpleInteraction[ client, NEW[DFOperations.InfoInteraction _ [ class: $warning, message: IO.PutFR["'%g' appears in an Imports statement (in '%g') without a Using list; it will be ignored.", [rope[imports.path1]], [rope[dfFile]] ] ]] ]; } ELSE { outerMostImports: BOOL = (filter.list = NIL); newFilter: DFUtilities.Filter = [ comments: filter.comments, -- comments processing is unaffected by imports filterA: filter.filterA, -- source/derived distinction is unaffected by imports filterB: IF imports.form = $exports THEN $public ELSE filter.filterB, filterC: $all, -- if the top level passes imports, they can come from anywhere list: IF imports.form = $list THEN imports.list ELSE filter.list ]; IF outerMostImports THEN importedFrom _ NEW[FileDesc _ [ path: imports.path1, -- hack: shortName is NIL, but only CheckIfNeeded cares. date: imports.date, parent: dfFile ]]; IF VerifyInner[imports.path1, imports.date, newFilter] AND outerMostImports THEN { FOR i: NAT IN [0..imports.list.nEntries) DO desc: REF FileDesc = LookupInHashTable[imports.list.u[i].name]; SELECT TRUE FROM desc = NIL => { warnings _ warnings.SUCC; DFInternal.SimpleInteraction[ client, NEW[DFOperations.InfoInteraction _ [ class: $warning, message: IO.PutFR[ "'%g' could not be found inside '%g' (or any nested DF file).", [rope[imports.list.u[i].name]], [rope[imports.path1]] ] ]] ]; }; imports.list.u[i].verifyRoot => rootList _ CONS[desc, rootList]; ENDCASE; ENDLOOP; importedFrom _ NIL; }; }; include: REF DFUtilities.IncludeItem => [] _ VerifyInner[include.path1, include.date, filter]; ENDCASE; }; dfInfo: REF DFInternal.RemoteFileInfo = NEW[DFInternal.RemoteFileInfo _ [name: dfFile, date: date]]; dfStream: STREAM; DFInternal.GetFileInfo[info: dfInfo, client: client ! FS.Error => {errors _ errors.SUCC; GO TO skip}]; dfStream _ FS.StreamOpen[fileName: dfInfo.name ! FS.Error => { errors _ errors.SUCC; DFInternal.ReportFSError[error, dfInfo, client]; GO TO skip} ]; dfInfo.name _ DFUtilities.RemoveVersionNumber[dfInfo.name]; DFInternal.SimpleInteraction[ client, NEW[DFOperations.DFInfoInteraction _ [action: $start, dfFile: dfInfo.name]] ]; DFUtilities.ParseFromStream[dfStream, DoOneItem, filter ! DFUtilities.SyntaxError -- [reason: ROPE]-- => { errors _ errors.SUCC; DFInternal.SimpleInteraction[ client, NEW[DFOperations.InfoInteraction _ [ class: $error, message: IO.PutFR[ "Syntax error in '%g'[%d]: %g\NProcessing of this DF file aborted.", [rope[dfInfo.name]], [cardinal[dfStream.GetIndex[]]], [rope[reason]] ] ]] ]; CONTINUE }; DFInternal.AbortDF => dfStream.Close[]; ]; dfStream.Close[]; DFInternal.SimpleInteraction[ client, NEW[DFOperations.DFInfoInteraction _ [action: $end, dfFile: dfInfo.name]] ]; RETURN[TRUE]; EXITS skip => NULL; }; VerifyDependencies: PROC = { VerifyDependenciesInner: PROC [parent: REF FileDesc] = { <> <<(1) If the parameter file is not a BCD or if it is a BCD that was imported, the file is a leaf of the dependency tree. It sufficies, therefore, to check that the file exists, which was already done in the course of building the hash table. Therefore, VerifyDependenciesInner does nothing for non-BCDs.>> <<(2) If the parameter is a BCD, the file table is enumerated and, for each file, the DF input is checked to see that (a) there exists a file with the same short name, (b) that the file is extant, and (c) that the BCD version stamp matches the stamp in the file table entry. If (a) fails, a necessary file has been omitted from the input and its (short) name is placed on a list for future printing. If (b) fails, the file appeared in the DF input but doesn't exist on the server. This error was reported during hash table construction, so no further action is required here. If (c) fails, the file specified in the DF input exists, but is the wrong version. An error is reported (unless it was already reported.) After the file table enumeration is complete, the source file for 'parent' is checked to be sure it exists.>> parentBcd: BcdDefs.BcdBase _ NIL; parentFileTable: BcdDefs.Base; parentSource: REF FileDesc _ NIL; parentFileName: ROPE _ NIL; RopeForNameRecord: PROC [bcd: BcdDefs.BcdBase, name: BcdDefs.NameRecord] RETURNS [r: ROPE] = TRUSTED { ssb: BcdDefs.NameString = LOOPHOLE[bcd + bcd.ssOffset]; len: NAT; i: INT _ name; GetFromNameString: SAFE PROC RETURNS [char: CHAR] = TRUSTED { char _ ssb.string[i]; i _ i + 1}; r _ Rope.FromProc[ssb.size[name], GetFromNameString]; len _ r.Length[]; IF len > 0 AND r.Fetch[len-1] = '. THEN r _ r.Substr[len: len-1]; }; CheckDependentFile: PROC [fth: BcdDefs.FTHandle, recur: BOOL _ TRUE] = TRUSTED { file: ROPE _ RopeForNameRecord[parentBcd, fth.name]; child: REF FileDesc; DFInternal.CheckAbort[client]; IF file.Find["."] < 0 THEN file _ file.Concat[".bcd"]; IF (child _ LookupInHashTable[file]) = NIL THEN omissions _ CONS[ NEW[Omission _ [missing: file, neededBy: parentFileName]], omissions] ELSE IF Extant[child] THEN { IF child.needed = $wrongVersion THEN RETURN; IF child.version = BcdDefs.NullVersion THEN { childBcd: BcdDefs.BcdBase; childBcd _ GetBcd[bcdCache, child ! FS.Error => IF DFInternal.RetryFSOperation[error, client] THEN RETRY ELSE { errors _ errors.SUCC; DFInternal.ReportFSError[ error, NEW[DFInternal.RemoteFileInfo _ [name: parentFileName, date: parent.date]], client ]; GO TO proceedWithoutBCD } ]; IF childBcd.versionIdent = BcdDefs.VersionID THEN child.version _ childBcd.version; ReleaseBcd[bcdCache, childBcd]; }; SELECT child.needed FROM $no => { IF fth.version = child.version THEN { child.needed _ $yes; IF child.importedFrom ~= NIL AND child.importedFrom.needed = $no THEN child.importedFrom.needed _ $yes; IF recur THEN VerifyDependenciesInner[child]; RETURN }; }; $yes => IF fth.version = child.version THEN RETURN; ENDCASE; child.needed _ $wrongVersion; errors _ errors.SUCC; DFInternal.SimpleInteraction[ client, NEW[DFOperations.InfoInteraction _ [ class: $error, message: IO.PutFR[ "'%g' {%g} depends on '%g'; '%g' {%g} is the wrong version.", [rope[parentFileName]], [rope[DFUtilities.DateToRope[parent.date]]], [rope[file]], [rope[FullName[child]]], [rope[DFUtilities.DateToRope[child.date]]] ] ]] ]; EXITS proceedWithoutBCD => NULL; }; }; parentSourceName: BcdDefs.NameRecord _ BcdDefs.NullName; parentSourceVersion: BcdDefs.VersionStamp; IF ~parent.bcd OR parent.importedFrom ~= NIL THEN RETURN; parentFileName _ FullName[parent]; parentBcd _ GetBcd[bcdCache, parent ! FS.Error => IF DFInternal.RetryFSOperation[error, client] THEN RETRY ELSE { errors _ errors.SUCC; DFInternal.ReportFSError[ error, NEW[DFInternal.RemoteFileInfo _ [name: parentFileName, date: parent.date]], client ]; GO TO skipThisBCD } ]; TRUSTED{ parentSourceName _ parentBcd.source; parentSourceVersion _ parentBcd.sourceVersion; parentFileTable _ LOOPHOLE[parentBcd + parentBcd.ftOffset]; }; IF parentSourceName = BcdDefs.NullName THEN { warnings _ warnings.SUCC; DFInternal.SimpleInteraction[ client, NEW[DFOperations.InfoInteraction _ [ class: $warning, message: IO.PutFR[ "'%g' {%g} does not specify a source file.", [rope[parentFileName]], [rope[DFUtilities.DateToRope[parent.date]]] ] ]] ]; } ELSE { sourceFileName: ROPE = RopeForNameRecord[parentBcd, parentSourceName]; IF (parentSource _ LookupInHashTable[sourceFileName]) = NIL THEN omissions _ CONS[ NEW[Omission _ [ missing: sourceFileName, neededBy: parentFileName ]], omissions] ELSE IF Extant[parentSource] AND parentSource.needed ~= $wrongVersion THEN { sourceDate: DFUtilities.Date = [$explicit, BasicTime.FromPupTime[LOOPHOLE[parentSourceVersion.time]]]; IF sourceDate = parentSource.date THEN parentSource.needed _ $yes ELSE { parentSource.needed _ $wrongVersion; errors _ errors.SUCC; DFInternal.SimpleInteraction[ client, NEW[DFOperations.InfoInteraction _ [ class: $error, message: IO.PutFR[ "'%g' {%g} expects source of {%g}, but DF file specifies '%g' {%g}.", [rope[parentFileName]], [rope[DFUtilities.DateToRope[parent.date]]], [rope[DFUtilities.DateToRope[sourceDate]]], [rope[FullName[parentSource]]], [rope[DFUtilities.DateToRope[parentSource.date]]] ] ]] ]; }; }; }; TRUSTED { IF parentBcd.nConfigs > 0 THEN { remoteSubConfig: BOOL _ FALSE; ctb: BcdDefs.Base = LOOPHOLE[parentBcd + parentBcd.ctOffset]; sgb: BcdDefs.Base = LOOPHOLE[parentBcd + parentBcd.sgOffset]; DoOneModule: PROC [mth: BcdDefs.MTHandle, mti: BcdDefs.MTIndex] RETURNS [BOOL _ FALSE] = TRUSTED { cti: BcdDefs.CTIndex _ mth.config; UNTIL cti = BcdDefs.CTNull DO cth: BcdDefs.CTHandle = @ctb[cti]; IF cth.file ~= BcdDefs.FTSelf THEN {remoteSubConfig _ TRUE; RETURN}; cti _ cth.config; ENDLOOP; <> IF mth.file ~= BcdDefs.FTSelf AND mth.file ~= BcdDefs.FTNull THEN CheckDependentFile[@parentFileTable[mth.file]]; }; DoOneConfig: PROC [cth: BcdDefs.CTHandle, cti: BcdDefs.CTIndex] RETURNS [BOOL _ FALSE] = TRUSTED { IF cth.file ~= BcdDefs.FTSelf THEN { <> outerCti: BcdDefs.CTIndex _ cth.config; UNTIL outerCti = BcdDefs.CTNull DO outerCth: BcdDefs.CTHandle = @ctb[outerCti]; IF outerCth.file ~= BcdDefs.FTSelf THEN RETURN; outerCti _ outerCth.config; ENDLOOP; CheckDependentFile[@parentFileTable[cth.file]]; }; }; [] _ BcdOps.ProcessModules[parentBcd, DoOneModule]; IF remoteSubConfig THEN [] _ BcdOps.ProcessConfigs[parentBcd, DoOneConfig]; } ELSE { DoOneFile: PROC [fth: BcdDefs.FTHandle, fti: BcdDefs.FTIndex] RETURNS [BOOL _ FALSE] = TRUSTED { CheckDependentFile[fth]; }; [] _ BcdOps.ProcessFiles[parentBcd, DoOneFile]; }; }; ReleaseBcd[bcdCache, parentBcd]; EXITS skipThisBCD => NULL; }; IF rootList = NIL THEN { warnings _ warnings.SUCC; DFInternal.SimpleInteraction[ client, NEW[DFOperations.InfoInteraction _ [ class: $warning, message: IO.PutFR["No files in '%g' are marked with '+'.", [rope[dfFile]]] ]] ]; } ELSE FOR l: LIST OF REF FileDesc _ rootList, l.rest UNTIL l = NIL DO rootDesc: REF FileDesc = l.first; IF rootDesc.needed = $no THEN { rootDesc.needed _ $yes; IF rootDesc.importedFrom ~= NIL AND rootDesc.importedFrom.needed = $no THEN rootDesc.importedFrom.needed _ $yes; IF Extant[rootDesc] THEN VerifyDependenciesInner[rootDesc ! DFInternal.AbortDF => FlushBcdCache[bcdCache]]; }; ENDLOOP; FlushBcdCache[bcdCache]; }; ReportOmissions: PROC = { Compare: List.CompareProc = { RETURN[Rope.Compare[ NARROW[ref1, REF Omission].missing, NARROW[ref2, REF Omission].missing] ] }; nFiles: NAT _ 0; msg: ROPE _ "The following should appear in the DF input:\N"; IF omissions = NIL THEN RETURN; FOR omissions _ List.UniqueSort[omissions, Compare], omissions.rest UNTIL omissions = NIL DO omission: REF Omission = NARROW[omissions.first]; msg _ msg.Concat[ IO.PutFR[" '%g', needed by '%g'\N", [rope[omission.missing]], [rope[omission.neededBy]] ] ]; nFiles _ nFiles.SUCC; ENDLOOP; errors _ errors + nFiles; DFInternal.SimpleInteraction[ client, NEW[DFOperations.InfoInteraction _ [ class: $error, message: msg ]] ]; }; CheckIfNeeded: PROC [desc: REF FileDesc] = { IF desc.needed ~= $no OR ~WorthRemembering[desc] THEN RETURN; IF desc.importedFrom ~= NIL THEN IF desc.importedFrom.needed = $no THEN <> (desc _ desc.importedFrom).needed _ $yes ELSE RETURN; warnings _ warnings.SUCC; DFInternal.SimpleInteraction[ client, NEW[DFOperations.InfoInteraction _ [ class: $warning, message: IO.PutFR[ "'%g' (%g) is superfluous.", [rope[IF desc.shortName = NIL THEN desc.path ELSE desc.shortName]], [rope[ IF desc.importedFrom = NIL THEN IO.PutFR["in '%g'", [rope[desc.parent]]] ELSE IO.PutFR["via '%g'", [rope[FullName[desc.importedFrom]]]] ]] ] ]] ]; }; { ENABLE { ABORTED => { DFInternal.SimpleInteraction[client, NEW[DFOperations.AbortInteraction _ [TRUE]]]; <> }; DFInternal.AbortDF => { errors _ errors.SUCC; DFInternal.SimpleInteraction[client, NEW[DFOperations.DFInfoInteraction _ [action: $abort, dfFile: dfFile]]]; CONTINUE }; }; IF DFInternal.LocalFile[dfFile] THEN { dfInfo: REF DFInternal.LocalFileInfo = NEW[DFInternal.LocalFileInfo _ [name: dfFile]]; DFInternal.GetFileInfo[info: dfInfo, client: client, errorLevel: $abort]; IF (dfFile _ dfInfo.attachedTo) = NIL THEN { errors _ errors.SUCC; DFInternal.SimpleInteraction[ client, NEW[DFOperations.InfoInteraction _ [ class: $abort, message: IO.PutFR[ "'%g' isn't a remote file and therefore can't be verified.", [rope[dfInfo.name]] ] ]] ]; RETURN }; }; <> IF VerifyInner[dfFile, [format: $explicit], []] THEN { <> VerifyDependencies[]; <> ReportOmissions[]; <> EnumerateHashTable[CheckIfNeeded]; }; }; }; <> BcdCache: TYPE = REF BcdCacheObject; BcdCacheObject: TYPE = RECORD [ locked: CachedBcdList _ NIL, -- linear list available: CachedBcdList _ NIL, -- circularly chained size: NAT _ 0, replacementSize: NAT ]; CachedBcdList: TYPE = LIST OF CachedBcd; CachedBcd: TYPE = REF CachedBcdEntry; CachedBcdEntry: TYPE = RECORD [ buffer: VM.Interval _ VM.nullInterval, desc: REF FileDesc _ NIL ]; initialVM: VM.PageCount = 10; <> <> CreateBcdCache: PROC [replacementSize: NAT] RETURNS [bcdCache: BcdCache] = { RETURN[NEW[BcdCacheObject _ [replacementSize: replacementSize]]] }; GetBcd: PROC [bcdCache: BcdCache, desc: REF FileDesc] RETURNS [bcd: BcdDefs.BcdBase _ NIL] = { prev: CachedBcdList _ bcdCache.available; new: CachedBcd _ NIL; list: CachedBcdList _ NIL; NewEntry: PROC RETURNS [CachedBcdList] = { bcdCache.size _ bcdCache.size.SUCC; RETURN[CONS[NEW[CachedBcdEntry _ []], NIL]] }; SELECT TRUE FROM prev = NIL => <<'available' list is empty. Create a new cache entry regardless of present cache size.>> list _ NewEntry[]; prev = prev.rest => { <<'available' list has precisely one entry, which may or may not be the file of interest.>> list _ bcdCache.available; bcdCache.available _ NIL; IF list.first.desc ~= desc THEN list.first.desc _ NIL; }; ENDCASE => { <<'available' list has at least two entries.>> list _ prev.rest; DO <> IF list.first.desc = desc THEN GO TO dequeue; -- 'list.first' is a cache hit prev _ list; IF (list _ list.rest) = bcdCache.available.rest THEN { <> IF bcdCache.size < bcdCache.replacementSize THEN {list _ NewEntry[]; EXIT} ELSE {list.first.desc _ NIL; GO TO dequeue}; }; REPEAT dequeue => { prev.rest _ list.rest; IF bcdCache.available = list THEN bcdCache.available _ list.rest; }; ENDLOOP; }; <<'list' is a single element list (although list.rest may be garbage) containing the CachedBcd to be (re)used. We link it on the 'locked' list.>> list.rest _ bcdCache.locked; bcdCache.locked _ list; <> IF (new _ list.first).desc = NIL THEN { ENABLE UNWIND => { bcdCache.locked _ bcdCache.locked.rest; bcdCache.size _ bcdCache.size.PRED; }; name: ROPE = FullName[desc]; file: FS.OpenFile; nPages: INT; file _ FS.Open[name: name, wantedCreatedTime: desc.date.gmt]; IF new.buffer.count = 0 THEN new.buffer _ VM.Allocate[initialVM]; nPages _ MIN[FS.PagesForBytes[FS.GetInfo[file].bytes], new.buffer.count]; TRUSTED { lp: LONG POINTER _ VM.AddressForPageNumber[new.buffer.page]; bcd _ LOOPHOLE[lp]; FS.Read[file: file, from: 0, nPages: nPages, to: lp]; IF bcd.nPages > nPages THEN { <> nPages _ bcd.nPages; VM.Free[new.buffer]; new.buffer _ VM.Allocate[nPages]; lp _ VM.AddressForPageNumber[new.buffer.page]; bcd _ LOOPHOLE[lp]; FS.Read[file: file, from: 0, nPages: nPages, to: lp]; }; }; FS.Close[file]; new.desc _ desc; } ELSE TRUSTED {bcd _ LOOPHOLE[VM.AddressForPageNumber[new.buffer.page]]}; }; ReleaseBcd: PROC [bcdCache: BcdCache, bcd: BcdDefs.BcdBase] = { list: CachedBcdList _ bcdCache.locked; prev: CachedBcdList _ NIL; UNTIL list = NIL DO TRUSTED {IF VM.AddressForPageNumber[list.first.buffer.page] = bcd THEN EXIT}; prev _ list; list _ list.rest; REPEAT FINISHED => ERROR; ENDLOOP; <> IF prev = NIL THEN bcdCache.locked _ list.rest ELSE prev.rest _ list.rest; <> IF bcdCache.available = NIL THEN list.rest _ list ELSE {list.rest _ bcdCache.available.rest; bcdCache.available.rest _ list}; bcdCache.available _ list; }; FlushBcdCache: PROC [bcdCache: BcdCache] = { list: CachedBcdList; <> FOR list _ bcdCache.locked, list.rest UNTIL list = NIL DO TRUSTED {VM.Free[list.first.buffer]}; ENDLOOP; bcdCache.locked _ NIL; IF bcdCache.available = NIL THEN RETURN; list _ bcdCache.available.rest; -- head of 'available' list bcdCache.available.rest _ NIL; -- break circular chain bcdCache.available _ NIL; UNTIL list = NIL DO TRUSTED {VM.Free[list.first.buffer]}; list _ list.rest; ENDLOOP; }; FullName: PROC [desc: REF FileDesc] RETURNS [ROPE] = INLINE { RETURN[desc.path.Concat[desc.shortName]] }; END.