-- ImageInfo.mesa; edited by Sandman, July 8, 1980 8:44 AM -- Copyright Xerox Corporation 1979, 1980 DIRECTORY AltoDefs USING [PageSize], AltoFileDefs USING [FP], BcdDefs USING [ FTIndex, FTNull, FTSelf, MTIndex, SGIndex, VersionStamp, NullVersion, Base], BcdOps USING [ BcdBase, FTHandle, MTHandle, NameString, ProcessModules, ProcessSegs, SGHandle], ControlDefs USING [FrameCodeBase, GFT, GFTItem, GlobalFrame, NullGlobalFrame], DirectoryDefs USING [EnumerateDirectory], ImageFileInfoDefs: FROM "imagefileinfodefs", ImageFormat USING [ImageHeader], LoadStateFormat USING [ ModuleTable, ConfigIndex, NullConfig, LoadState, AltoVersionID, BcdObject], LoadStateOps USING [EnumerationDirection, Map], MiscDefs USING [Zero], SDDefs USING [SD, sGFTLength], SegmentDefs USING [ AddressFromPage, DeleteFileSegment, FileHandle, FileSegmentAddress, FileSegmentHandle, FileSegmentObject, InsertFile, NewFile, NewFileSegment, OldFileOnly, Read, SwapIn, SwapOut, Unlock, VMtoFileSegment], String USING [ AppendSubString, EquivalentSubStrings, SubString, SubStringDescriptor], Storage USING [Node, Pages, Free, FreePages]; ImageInfo: PROGRAM IMPORTS DirectoryDefs, ImageFileInfoDefs, BcdOps, MiscDefs, SegmentDefs, String, Storage EXPORTS ImageFileInfoDefs, LoadStateOps SHARES ImageFormat = BEGIN OPEN ImageFileInfoDefs, LoadStateFormat; SetImage: PUBLIC PROCEDURE [name: STRING] = BEGIN OPEN SegmentDefs; ClearFileObjectTable[]; ClearFrameObjectTable[]; IF headerSeg # NIL THEN BEGIN Unlock[headerSeg]; DeleteFileSegment[headerSeg] END; headerSeg _ NewFileSegment[NewFile[name, Read, OldFileOnly], 1, 1, Read]; SwapIn[headerSeg]; InitImageLoadState[headerSeg]; InitializeImageCache[headerSeg]; Unlock[headerSeg]; RETURN END; headerSeg: FileSegmentHandle _ NIL; Version: PUBLIC PROCEDURE RETURNS [v: BcdDefs.VersionStamp] = BEGIN OPEN SegmentDefs; image: POINTER TO ImageFormat.ImageHeader; IF headerSeg = NIL THEN RETURN[BcdDefs.NullVersion]; SwapIn[headerSeg]; image _ FileSegmentAddress[headerSeg]; v _ image.prefix.version; Unlock[headerSeg]; RETURN END; HeaderSegment: PUBLIC PROCEDURE RETURNS [FileSegmentHandle] = BEGIN RETURN[headerSeg]; END; -- Global Frame Table management EnumerateGlobalFrames: PUBLIC PROCEDURE [ proc: PROCEDURE [GlobalFrameHandle] RETURNS [BOOLEAN]] RETURNS [GlobalFrameHandle] = BEGIN i: CARDINAL; frame: GlobalFrameHandle; gft: POINTER TO ARRAY [0..0) OF ControlDefs.GFTItem _ ControlDefs.GFT; FOR i IN [0..SDDefs.SD[SDDefs.sGFTLength]) DO frame _ READ[@gft[i].frame]; IF frame # ControlDefs.NullGlobalFrame AND READ[@gft[i].epbase] = 0 AND proc[frame] THEN RETURN[frame]; ENDLOOP; RETURN[ControlDefs.NullGlobalFrame] END; GlobalFrame: TYPE = ControlDefs.GlobalFrame; globalFrame: GlobalFrame; VirtualGlobalFrame: PUBLIC PROCEDURE [frame: GlobalFrameHandle] RETURNS [GlobalFrameHandle] = BEGIN CopyRead[to: @globalFrame, from: frame, nwords: SIZE[GlobalFrame]]; RETURN[@globalFrame] END; BcdBase: TYPE = BcdOps.BcdBase; EnumerationDirection: TYPE = LoadStateOps.EnumerationDirection; ConfigIndex: TYPE = LoadStateFormat.ConfigIndex; loadstate: PUBLIC LoadStateFormat.LoadState; gft: PUBLIC LoadStateFormat.ModuleTable; nbcds: CARDINAL; InputLoadState: PUBLIC PROCEDURE RETURNS [ConfigIndex] = BEGIN OPEN LoadStateFormat, SegmentDefs, SDDefs; IF state = NIL THEN ERROR; SwapIn[state]; loadstate _ FileSegmentAddress[state]; BEGIN ENABLE UNWIND => Unlock[state]; IF loadstate.versionident # AltoVersionID THEN ERROR; END; gft _ DESCRIPTOR[@loadstate.gft, READ[SD + sGFTLength]]; nbcds _ loadstate.nBcds; RETURN[nbcds] END; ReleaseLoadState: PUBLIC PROCEDURE = BEGIN OPEN SegmentDefs; IF ~state.swappedin THEN RETURN; Unlock[state]; IF state.lock = 0 THEN BEGIN SwapOut[state]; loadstate _ NIL; nbcds _ 0; END; END; MapConfigToReal: PUBLIC PROCEDURE [cgfi: GFTIndex, config: ConfigIndex] RETURNS [rgfi: GFTIndex] = BEGIN IF cgfi = 0 THEN RETURN[0]; FOR rgfi IN [0..LENGTH[gft]) DO IF gft[rgfi].config = config AND gft[rgfi].gfi = cgfi THEN RETURN[rgfi]; ENDLOOP; RETURN[0]; END; MapRealToConfig: PUBLIC PROCEDURE [rgfi: GFTIndex] RETURNS [cgfi: GFTIndex, config: ConfigIndex] = BEGIN RETURN[gft[rgfi].gfi, gft[rgfi].config]; END; GetMap: PUBLIC PROCEDURE [config: ConfigIndex] RETURNS [map: LoadStateOps.Map] = BEGIN max: CARDINAL _ 0; i: GFTIndex; FOR i IN [0..LENGTH[gft]) DO IF gft[i].config = config THEN max _ MAX[max, gft[i].gfi]; ENDLOOP; max _ max + 1; map _ DESCRIPTOR[Storage.Node[max], max]; MiscDefs.Zero[BASE[map], max]; FOR i IN [0..LENGTH[gft]) DO IF gft[i].config = config THEN map[gft[i].gfi] _ i; ENDLOOP; END; ReleaseMap: PUBLIC PROCEDURE [map: LoadStateOps.Map] = BEGIN Storage.Free[BASE[map]]; END; BcdSegFromLoadState: PUBLIC PROCEDURE [bcd: ConfigIndex] RETURNS [seg: FileSegmentHandle] = BEGIN OPEN SegmentDefs, LoadStateFormat; b: alto BcdObject _ LOOPHOLE[loadstate.bcds[bcd]]; seg _ NewFileSegment[state.file, b.base, b.pages, Read]; RETURN END; EnumerateLoadStateGFT: PUBLIC PROCEDURE [ proc: PROCEDURE [GFTIndex, GFTIndex, ConfigIndex] RETURNS [BOOLEAN]] RETURNS [GFTIndex] = BEGIN i: GFTIndex; FOR i IN [0..LENGTH[gft]) DO IF proc[i, gft[i].gfi, gft[i].config] THEN RETURN[i]; ENDLOOP; RETURN[0] END; EnumerateBcds: PUBLIC PROCEDURE [ dir: EnumerationDirection, proc: PROCEDURE [ConfigIndex] RETURNS [BOOLEAN]] RETURNS [config: ConfigIndex] = BEGIN SELECT dir FROM recentfirst => FOR config DECREASING IN [0..loadstate.nBcds) DO IF proc[config] THEN RETURN[config]; ENDLOOP; recentlast => FOR config IN [0..loadstate.nBcds) DO IF proc[config] THEN RETURN[config]; ENDLOOP; ENDCASE; RETURN[NullConfig] END; InitImageLoadState: PUBLIC PROCEDURE [seg: FileSegmentHandle] = BEGIN OPEN SegmentDefs; image: POINTER TO ImageFormat.ImageHeader; SwapIn[seg]; image _ FileSegmentAddress[seg]; state _ NewFileSegment[ seg.file, image.prefix.initialLoadStateBase, image.prefix.loadStatePages, Read]; END; state: PUBLIC FileSegmentHandle; -- SymbolTable Lookup FTIndex: TYPE = BcdDefs.FTIndex; FTHandle: TYPE = BcdOps.FTHandle; MTIndex: TYPE = BcdDefs.MTIndex; MTHandle: TYPE = BcdOps.MTHandle; SGIndex: TYPE = BcdDefs.SGIndex; SGHandle: TYPE = BcdOps.SGHandle; AddSymbolFileName: PUBLIC PROCEDURE [ bcd: BcdBase, sgi: SGIndex, sgh: SGHandle] = BEGIN ExistingFile: PROCEDURE [f: FileItem] RETURNS [BOOLEAN] = BEGIN RETURN[f.fti = sgh.file]; END; f: FileItem; IF sgh.class = code THEN RETURN; IF EnumFileObjects[ExistingFile] = NIL THEN BEGIN f _ GetFileObject[]; f^ _ [fileHandle: NIL, fti: sgh.file]; END; IF sgh.file = BcdDefs.FTSelf THEN f.fileHandle _ SegmentDefs.VMtoFileSegment[bcd].file; RETURN; END; AddModuleSymbols: PUBLIC PROCEDURE [ bcd: BcdBase, config: ConfigIndex, mti: MTIndex, mth: MTHandle] = BEGIN OPEN SegmentDefs; sgh: SGHandle _ @LOOPHOLE[bcd + bcd.sgOffset, BcdDefs.Base][mth.sseg]; SymbolFile: PROCEDURE [f: FileItem] RETURNS [BOOLEAN] = BEGIN RETURN[f.fti = sgh.file]; END; fi: FileItem; fr: FrameItem; symfile: FileHandle; frame: GlobalFrameHandle; symfile _ IF (fi _ EnumFileObjects[SymbolFile]) = NIL THEN NIL ELSE fi.fileHandle; frame _ READ[@ControlDefs.GFT[MapConfigToReal[mth.gfi, config]].frame]; fr _ GetFrameItem[]; fr.symbols _ IF symfile = NIL OR sgh.pages = 0 THEN NIL ELSE NewFileSegment[symfile, sgh.base, sgh.pages + sgh.extraPages, Read]; fr.frame _ frame; sgh _ @LOOPHOLE[bcd + bcd.sgOffset, BcdDefs.Base][mth.code.sgi]; fr.code _ NewFileSegment[headerSeg.file, sgh.base, sgh.pages, Read]; fr.offset _ mth.code.offset; RETURN; END; bcd: BcdBase; FindAllSymbols: PUBLIC PROCEDURE = BEGIN LookupEachBcd: PROCEDURE [config: ConfigIndex] RETURNS [BOOLEAN] = BEGIN EnterFile: PROCEDURE [sgh: SGHandle, sgi: SGIndex] RETURNS [BOOLEAN] = BEGIN AddSymbolFileName[bcd, sgi, sgh]; RETURN[FALSE]; END; DoModule: PROCEDURE [mth: MTHandle, mti: MTIndex] RETURNS [BOOLEAN] = BEGIN AddModuleSymbols[bcd, config, mti, mth]; RETURN[FALSE]; END; bcdseg: FileSegmentHandle _ BcdSegFromLoadState[config]; SegmentDefs.SwapIn[bcdseg]; bcd _ SegmentDefs.FileSegmentAddress[bcdseg]; [] _ BcdOps.ProcessSegs[bcd, EnterFile]; LookupFiles[]; [] _ BcdOps.ProcessModules[bcd, DoModule]; ClearFileObjectTable[]; RETURN[FALSE] END; [] _ InputLoadState[]; [] _ EnumerateBcds[recentfirst, LookupEachBcd]; ReleaseLoadState[]; RETURN END; SymbolSegForFrame: PUBLIC PROCEDURE [frame: GlobalFrameHandle] RETURNS [seg: FileSegmentHandle] = BEGIN SymbolFile: PROCEDURE [f: FrameItem] RETURNS [BOOLEAN] = BEGIN RETURN[f.frame = frame]; END; FindModule: PROCEDURE [mth: MTHandle, mti: BcdDefs.MTIndex] RETURNS [BOOLEAN] = BEGIN RETURN[mth.gfi = cgfi]; END; f: FrameItem; bcdseg: FileSegmentHandle; config: ConfigIndex; cgfi: GFTIndex; mth: MTHandle; mti: MTIndex; sgb: BcdDefs.Base; IF frame = ControlDefs.NullGlobalFrame THEN RETURN[NIL]; IF (f _ EnumFrameObjects[SymbolFile]) # NIL THEN RETURN[f.symbols]; IF VirtualGlobalFrame[frame].copied THEN BEGIN original: GlobalFrameHandle _ FindOriginal[frame]; seg _ SymbolSegForFrame[original]; AddSymbolsForCopy[frame, original]; RETURN END; [] _ InputLoadState[]; [config: config, cgfi: cgfi] _ MapRealToConfig[VirtualGlobalFrame[frame].gfi]; bcdseg _ BcdSegFromLoadState[config]; SegmentDefs.SwapIn[bcdseg]; bcd _ SegmentDefs.FileSegmentAddress[bcdseg]; [mth: mth, mti: mti] _ BcdOps.ProcessModules[bcd, FindModule]; sgb _ LOOPHOLE[bcd + bcd.sgOffset]; AddSymbolFileName[bcd, mth.sseg, @sgb[mth.sseg]]; LookupFiles[]; AddModuleSymbols[bcd, config, mti, mth]; ClearFileObjectTable[]; ReleaseLoadState[]; SegmentDefs.Unlock[bcdseg]; SegmentDefs.DeleteFileSegment[bcdseg]; RETURN[EnumFrameObjects[SymbolFile].symbols] END; CodeSegForFrame: PUBLIC PROCEDURE [frame: GlobalFrameHandle] RETURNS [seg: FileSegmentHandle, offset: CARDINAL] = BEGIN CodeFile: PROCEDURE [f: FrameItem] RETURNS [BOOLEAN] = BEGIN RETURN[f.frame = frame]; END; f: FrameItem; IF frame = ControlDefs.NullGlobalFrame THEN RETURN[NIL, 0]; IF (f _ EnumFrameObjects[CodeFile]) # NIL THEN RETURN[f.code, f.offset]; [] _ SymbolSegForFrame[frame]; f _ EnumFrameObjects[CodeFile]; RETURN[f.code, f.offset]; END; FindOriginal: PUBLIC PROCEDURE [copy: GlobalFrameHandle] RETURNS [GlobalFrameHandle] = BEGIN Original: PROCEDURE [f: GlobalFrameHandle] RETURNS [BOOLEAN] = BEGIN RETURN[ f # copy AND ~VirtualGlobalFrame[ f].copied AND SameCode[ copy, f] = identical]; END; RETURN[EnumerateGlobalFrames[Original]] END; CodeMatch: TYPE = {identical, same, different}; SameCode: PROCEDURE [f1, f2: GlobalFrameHandle] RETURNS [CodeMatch] = BEGIN Size: CARDINAL = SIZE[ControlDefs.FrameCodeBase]; fcb1, fcb2: ControlDefs.FrameCodeBase; CopyRead[to: @fcb1, from: @f1.code, nwords: Size]; CopyRead[to: @fcb2, from: @f2.code, nwords: Size]; IF fcb1.highByte = 0 OR fcb2.highByte = 0 THEN BEGIN fcb1.out _ fcb2.out _ FALSE; RETURN[IF fcb1 = fcb2 THEN identical ELSE different]; END; IF fcb1.handle # fcb2.handle THEN RETURN[different]; RETURN[IF GetOffset[fcb1] = GetOffset[fcb2] THEN identical ELSE same]; END; GetOffset: PROCEDURE [fcb: ControlDefs.FrameCodeBase] RETURNS [offset: CARDINAL] = BEGIN OPEN SegmentDefs; seg: SegmentDefs.FileSegmentHandle _ VirtualFileSegment[fcb.handle]; IF ~seg.swappedin THEN BEGIN fcb.out _ FALSE; RETURN[fcb.offset]; END; RETURN[fcb.shortbase - SegmentDefs.AddressFromPage[seg.VMpage]] END; FrameToModuleName: PUBLIC PROCEDURE [frame: GlobalFrameHandle, name: STRING] = BEGIN cgfi: GFTIndex; config: ConfigIndex; ns: BcdOps.NameString; bcdseg: FileSegmentHandle; bcd: BcdBase; FindModule: PROCEDURE [mth: MTHandle, mti: MTIndex] RETURNS [BOOLEAN] = BEGIN ss: String.SubStringDescriptor; IF cgfi IN [mth.gfi..mth.gfi + mth.ngfi) THEN BEGIN ss _ [base: @ns.string, offset: mth.name, length: ns.size[mth.name]]; String.AppendSubString[name, @ss]; RETURN[TRUE]; END; RETURN[FALSE]; END; IF frame = ControlDefs.NullGlobalFrame THEN RETURN; IF VirtualGlobalFrame[frame].copied THEN BEGIN original: GlobalFrameHandle _ FindOriginal[frame]; FrameToModuleName[original, name]; RETURN END; [] _ InputLoadState[]; [config: config, cgfi: cgfi] _ MapRealToConfig[VirtualGlobalFrame[frame].gfi]; bcdseg _ BcdSegFromLoadState[config]; SegmentDefs.SwapIn[bcdseg]; bcd _ SegmentDefs.FileSegmentAddress[bcdseg]; ns _ LOOPHOLE[bcd + bcd.ssOffset]; [] _ BcdOps.ProcessModules[bcd, FindModule]; ReleaseLoadState[]; SegmentDefs.Unlock[bcdseg]; SegmentDefs.DeleteFileSegment[bcdseg]; RETURN END; segment: SegmentDefs.FileSegmentObject; VirtualFileSegment: PUBLIC PROCEDURE [seg: FileSegmentHandle] RETURNS [FileSegmentHandle] = BEGIN OPEN SegmentDefs; CopyRead[to: @segment, from: seg, nwords: SIZE[FileSegmentObject]]; RETURN[@segment] END; AddSymbolsForCopy: PROCEDURE [copy, original: GlobalFrameHandle] = BEGIN copyFI, originalFI: FrameItem; FindMatch: PROCEDURE [f: FrameItem] RETURNS [BOOLEAN] = BEGIN RETURN[f.frame = original]; END; originalFI _ EnumFrameObjects[FindMatch]; copyFI _ GetFrameItem[]; copyFI^ _ originalFI^; copyFI.frame _ copy; RETURN END; -- File Lookup Object Management FileObject: TYPE = RECORD [ fileHandle: SegmentDefs.FileHandle, fti: BcdDefs.FTIndex]; FileItem: TYPE = POINTER TO FileObject; FileObjectTable: TYPE = RECORD [ link: POINTER TO FileObjectTable, subTable: ARRAY [0..0) OF FileObject]; fileTable: POINTER TO FileObjectTable _ NIL; nFiles: CARDINAL _ MaxNFileObjects; MaxNFileObjects: CARDINAL = (AltoDefs.PageSize - 1)/SIZE[FileObject]; GetFileObject: PROCEDURE RETURNS [f: FileItem] = BEGIN newTable: POINTER TO FileObjectTable; IF nFiles < MaxNFileObjects THEN BEGIN f _ @fileTable.subTable[nFiles]; nFiles _ nFiles + 1; END ELSE BEGIN newTable _ Storage.Pages[1]; newTable.link _ fileTable; fileTable _ newTable; nFiles _ 1; RETURN[@fileTable.subTable[0]]; END; END; EnumFileObjects: PROCEDURE [proc: PROCEDURE [f: FileItem] RETURNS [BOOLEAN]] RETURNS [f: FileItem] = BEGIN i: CARDINAL; table: POINTER TO FileObjectTable; IF fileTable = NIL THEN RETURN[NIL]; IF nFiles < MaxNFileObjects THEN FOR i IN [0..nFiles) DO IF proc[f _ @fileTable.subTable[i]] THEN RETURN; ENDLOOP; FOR table _ fileTable.link, table.link UNTIL table = NIL DO FOR i IN [0..MaxNFileObjects) DO IF proc[f _ @table.subTable[i]] THEN RETURN; ENDLOOP; ENDLOOP; RETURN[NIL]; END; ClearFileObjectTable: PROCEDURE = BEGIN table: POINTER TO FileObjectTable; UNTIL fileTable = NIL DO table _ fileTable; fileTable _ fileTable.link; Storage.FreePages[table]; ENDLOOP; nFiles _ MaxNFileObjects; RETURN END; -- Frame Symbol Table Management FrameObject: TYPE = RECORD [ frame: GlobalFrameHandle, symbols, code: FileSegmentHandle, offset: CARDINAL]; FrameItem: TYPE = POINTER TO FrameObject; FrameObjectTable: TYPE = RECORD [ link: POINTER TO FrameObjectTable, subTable: ARRAY [0..0) OF FrameObject]; frameTable: POINTER TO FrameObjectTable _ NIL; nFrames: CARDINAL _ MaxNFrameObjects; MaxNFrameObjects: CARDINAL = (AltoDefs.PageSize - 1)/SIZE[FrameObject]; GetFrameItem: PROCEDURE RETURNS [f: FrameItem] = BEGIN newTable: POINTER TO FrameObjectTable; IF nFrames < MaxNFrameObjects THEN BEGIN f _ @frameTable.subTable[nFrames]; nFrames _ nFrames + 1; END ELSE BEGIN newTable _ Storage.Pages[1]; newTable.link _ frameTable; frameTable _ newTable; nFrames _ 1; RETURN[@frameTable.subTable[0]]; END; END; EnumFrameObjects: PROCEDURE [proc: PROCEDURE [f: FrameItem] RETURNS [BOOLEAN]] RETURNS [f: FrameItem] = BEGIN i: CARDINAL; table: POINTER TO FrameObjectTable; IF frameTable = NIL THEN RETURN[NIL]; IF nFrames < MaxNFrameObjects THEN FOR i IN [0..nFrames) DO IF proc[f _ @frameTable.subTable[i]] THEN RETURN; ENDLOOP; FOR table _ frameTable.link, table.link UNTIL table = NIL DO FOR i IN [0..MaxNFrameObjects) DO IF proc[f _ @table.subTable[i]] THEN RETURN; ENDLOOP; ENDLOOP; RETURN[NIL]; END; ClearFrameObjectTable: PROCEDURE = BEGIN table: POINTER TO FrameObjectTable; UNTIL frameTable = NIL DO table _ frameTable; frameTable _ frameTable.link; Storage.FreePages[table]; ENDLOOP; nFrames _ MaxNFrameObjects; RETURN END; -- Directory Lookup of File Table nFilesToFind: CARDINAL; LookupFiles: PUBLIC PROCEDURE = BEGIN name: STRING _ [40]; CountFilesToLookup: PROCEDURE [f: FileItem] RETURNS [BOOLEAN] = BEGIN nFilesToFind _ nFilesToFind + 1; RETURN[FALSE]; END; nFilesToFind _ 0; [] _ EnumFileObjects[CountFilesToLookup]; DirectoryDefs.EnumerateDirectory[CheckOne]; RETURN END; CheckOne: PROCEDURE [fp: POINTER TO AltoFileDefs.FP, name: STRING] RETURNS [found: BOOLEAN] = BEGIN OPEN String; i: CARDINAL; dirName: SubStringDescriptor; bcd: SubStringDescriptor _ [base: "bcd"L, offset: 0, length: 3]; file: FileItem; FOR i IN [0..name.length) DO IF name[i] = '. THEN BEGIN IF name.length - i # 5 THEN GOTO UseWholeName; dirName _ [base: name, offset: i + 1, length: 3]; IF ~EquivalentSubStrings[@dirName, @bcd] THEN GOTO UseWholeName; dirName.offset _ 0; dirName.length _ i; GOTO HasBCDExtension; END; REPEAT UseWholeName => NULL; HasBCDExtension => BEGIN [found, file] _ FindFileName[@dirName]; IF found THEN RETURN[ThisIsTheOne[fp, file]]; END; ENDLOOP; dirName _ [base: name, offset: 0, length: name.length - 1]; [found, file] _ FindFileName[@dirName]; RETURN[IF found THEN ThisIsTheOne[fp, file] ELSE FALSE]; END; ThisIsTheOne: PROCEDURE [fp: POINTER TO AltoFileDefs.FP, file: FileItem] RETURNS [BOOLEAN] = BEGIN file.fileHandle _ SegmentDefs.InsertFile[fp, SegmentDefs.Read]; RETURN[(nFilesToFind _ nFilesToFind - 1) = 0]; END; FindFileName: PUBLIC PROCEDURE [name: String.SubString] RETURNS [found: BOOLEAN, file: FileItem] = BEGIN OPEN String; ns: BcdOps.NameString _ LOOPHOLE[bcd + bcd.ssOffset]; ftb: BcdDefs.Base _ LOOPHOLE[bcd + bcd.ftOffset]; filename: SubStringDescriptor _ [base: @ns.string, offset:, length:]; MatchName: PROCEDURE [f: FileItem] RETURNS [BOOLEAN] = BEGIN IF f.fti = BcdDefs.FTNull THEN RETURN[FALSE]; IF f.fti = BcdDefs.FTSelf THEN RETURN[FALSE]; filename.offset _ ftb[f.fti].name; filename.length _ ns.size[ftb[f.fti].name]; IF LastCharIsDot[@filename] THEN name.length _ name.length + 1; RETURN[EquivalentSubStrings[@filename, name]]; END; f: FileItem; f _ EnumFileObjects[MatchName]; RETURN[f # NIL, f]; END; LastCharIsDot: PUBLIC PROCEDURE [name: String.SubString] RETURNS [BOOLEAN] = BEGIN RETURN[name.base[name.offset + name.length - 1] = '.]; END; END...