-- RTGetSymbolsImpl.mesa
-- Last Modified By Paul Rovner On December 21, 1982 2:57 pm

DIRECTORY
AMTypes USING[Error],
BcdDefs USING[VersionStamp, NameRecord, MTIndex, SGIndex, SGNull, FTNull,
FTSelf, VersionID, FTIndex, NullVersion],
BcdOps USING[BcdBase, NameString, MTHandle, SGHandle, ProcessModules,
FTHandle, ProcessFiles],
CIFS USING[GetFC, Open, read, dontCheck, OpenFile],
ConvertUnsafe USING[AppendRope, ToRope],
File USING[Capability, nullCapability],
LongString USING[AppendString],
PilotLoadStateFormat USING[ModuleInfo, ConfigIndex],
PilotLoadStateOps USING[InputLoadState, GetModule, AcquireBcd, ReleaseBcd,
ReleaseLoadState, EnumerateBcds],
PrincOps USING[GlobalFrameHandle],
RCMapOps USING[EstablishOuter],
Rope USING[ROPE, Flatten, Length, Fetch, Concat],
RTFiles USING[], -- EXPORTS only
RTFlags USING[checking],
RTOS USING[EnumerateGlobalFrames, SameCode, UnRavelUSUs],
RTSymbolDefs USING[SymbolTableBase, SymbolTableHandle, nullHandle,
SymbolModuleIndex, nullModuleIndex,
rootBodyIndex, nullSymbolIndex, SymbolIdIndex],
RTSymbolOps USING[], -- EXPORTS only
RTSymbols USING[], -- EXPORTS only
RTSymbolsPrivate USING[], -- EXPORTS only
RTTypesBasicPrivate USING[SymbolTableIndex, MapStiStd, FindSTI, STDesc],
Runtime USING[CallDebugger, ValidateGlobalFrame],
Space USING[Map, LongPointer, Unmap, GetAttributes, GetWindow, GetHandle,
PageFromLongPointer, Handle, Create, virtualMemory, Delete],
Strings USING[SubStringDescriptor, AppendSubString, AppendString,
EqualSubStrings],
SymbolTable USING[Release, Acquire, Forget, anySpan],
Table USING[Base],
VersionMap USING[RestoreMapFromFile, VersionToName, MapList];

RTGetSymbolsImpl: MONITOR
IMPORTS AMTypes, BcdOps, CIFS, ConvertUnsafe, LongString,
PilotLoadStateOps, RCMapOps, Rope, RTOS, RTTypesBasicPrivate, Runtime,
Space, Strings, SymbolTable, VersionMap
EXPORTS RTFiles, RTSymbolOps, RTSymbols, RTSymbolsPrivate
SHARES File
= BEGIN OPEN RTSymbolDefs;

-- Variables protected by the monitor
onePageSpace: Space.Handle = Space.Create[size: 1, parent: Space.virtualMemory];
standardBCDSpaces: LIST OF Space.Handle ← NIL;
standardBCDSpacePages: NAT = 20;
standardBCDSpacesInUse: NAT ← 0;
standardBCDSpacesHeld: NAT ← 0;
biggerBCDSpacesInUse: NAT ← 0;

versionMapList: VersionMap.MapList ← NIL;

OZ: BOOLFALSE;

ROPE: TYPE = Rope.ROPE;

-- PUBLIC PROCS --

Outer: PUBLIC PROC[stb: SymbolTableBase,
mdi: SymbolModuleIndex,
inner: PROC[base: SymbolTableBase],
mesaSymbolsOK: BOOLEANFALSE] =
{stb1: SymbolTableBase = AcquireSTBFromMDI[stb, mdi, mesaSymbolsOK];
inner[stb1 ! UNWIND => ReleaseSTB[stb1]];
ReleaseSTB[stb1]};

AcquireSTBForDefs: PUBLIC PROC[fileName: ROPE--ends with .bcd--]
RETURNS[SymbolTableBase] =
{ mn: STRING = [100];
dotFound: BOOLEANFALSE;
FOR i: NAT IN [0..Rope.Length[fileName])
DO IF Rope.Fetch[fileName, i] = '. THEN {dotFound ← TRUE; EXIT};
ENDLOOP;
IF NOT dotFound THEN fileName ← Rope.Concat[fileName, ".bcd"];
mn.length ← 0;
ConvertUnsafe.AppendRope[from: fileName, to: LONG[mn]];
IF mn.length<=4 THEN ERROR AMTypes.Error[reason: noSymbols, msg: fileName];
mn.length ← mn.length-4;
RETURN[AcquireSTB[GetSTHForModule[stamp: BcdDefs.NullVersion,
fileName: fileName,
moduleName: ConvertUnsafe.ToRope[LONG[mn]]]]]};

AcquireSTB: PUBLIC PROC[sth: SymbolTableHandle, mesaSymbolsOK: BOOLEANFALSE]
RETURNS[stb: SymbolTableBase] =
{stb ← SymbolTable.Acquire[sth];
IF mesaSymbolsOK THEN RETURN[stb];
-- groan. backward compatibility for limited usage
-- (for RTSymbols clients only, NOT for AMTypes)
IF NOT stb.stHandle.extended -- symbol table made by an old version of the compiler
OR stb.bb[rootBodyIndex].type = nullSymbolIndex
THEN {modName: ROPE = STBToModuleName[stb ! UNWIND => ReleaseSTB[stb]];
ReleaseSTB[stb];
ERROR AMTypes.Error[reason: noSymbols, msg: modName]}
ELSE RETURN[stb]};

STBToModuleName: PUBLIC PROC[stb: SymbolTableBase] RETURNS[ROPE] =
{modName: STRING = [100];
STBToModName[stb, modName];
RETURN[ConvertUnsafe.ToRope[LONG[modName]]]};

STBToModName: PROC[stb: SymbolTableBase, modName: LONG STRING] =
{modName.length ← 0;
FOR isei: SymbolIdIndex ← stb.FirstCtxSe[stb.stHandle.directoryCtx]
, stb.NextSe[isei] UNTIL isei = nullSymbolIndex
DO IF stb.seb[isei].public
THEN {modNameDesc: Strings.SubStringDescriptor;
stb.SubStringForHash[@modNameDesc, stb.seb[isei].hash];
Strings.AppendSubString[modName, @modNameDesc];
RETURN};
ENDLOOP};

ReleaseSTB: PUBLIC PROC[stb: SymbolTableBase] = {SymbolTable.Release[stb]};

STPages: PUBLIC PROC[sth: SymbolTableHandle] RETURNS[CARDINAL] =
{RETURN[sth.span.pages]};

-- this may return nullHandle but will not raise any signals
AcquireSTHFromSTX: PUBLIC PROC[stx: RTTypesBasicPrivate.SymbolTableIndex]
RETURNS[sth: SymbolTableHandle ← nullHandle, moduleName: ROPENIL] =
{[sth, moduleName] ← DoAcquireSTHFromSTX[stx, TRUE]};

DoAcquireSTHFromSTX: PROC[stx: RTTypesBasicPrivate.SymbolTableIndex,
invokeGetSTHForModule: BOOLFALSE]
RETURNS[sth: SymbolTableHandle ← nullHandle, moduleName: ROPENIL] =
{ symbolsStamp: BcdDefs.VersionStamp;
bcd: BcdOps.BcdBase;

IF RTTypesBasicPrivate.MapStiStd[stx].sth # nullHandle
THEN RETURN[sth: RTTypesBasicPrivate.MapStiStd[stx].sth];

symbolsStamp ← RTTypesBasicPrivate.MapStiStd[stx].symbolsStamp;
bcd ← RTTypesBasicPrivate.MapStiStd[stx].bcd;
IF bcd = NIL
THEN {IF invokeGetSTHForModule
THEN sth ← GetSTHForModule[stamp: symbolsStamp,
fileName: NIL,
moduleName: NIL
! ANY => CONTINUE]
ELSE RETURN}
ELSE
{sgb: Table.Base = LOOPHOLE[bcd + bcd.sgOffset];
ftb: Table.Base = LOOPHOLE[bcd + bcd.ftOffset];
ssb: BcdOps.NameString = LOOPHOLE[bcd + bcd.ssOffset];
fti: BcdDefs.FTIndex;
sgi: BcdDefs.SGIndex = RTTypesBasicPrivate.MapStiStd[stx].sgi; -- maybe SGNull
-- (=> search the BCD's file table)
IF sgi = BcdDefs.SGNull -- search the BCD's file table (find a DEFs)
THEN { findSymbolFTI: PROC[ffth: BcdOps.FTHandle, ffti: BcdDefs.FTIndex]
RETURNS[stop: BOOLEAN] =
{ IF ffth.version # symbolsStamp THEN RETURN[FALSE];
moduleName ← GetModuleName[ssb, ffth.name];
sth ← GetSTHForModule[stamp: symbolsStamp,
fileName: Rope.Concat[moduleName, ".bcd"],
moduleName: moduleName
! AMTypes.Error => CONTINUE];
RETURN[TRUE]};

IF NOT invokeGetSTHForModule THEN RETURN;

[] ← BcdOps.ProcessFiles[bcd, findSymbolFTI];
IF sth = nullHandle
THEN sth ← GetSTHForModule[stamp: symbolsStamp,
fileName: NIL,
moduleName: NIL
! ANY => CONTINUE];
RTTypesBasicPrivate.MapStiStd[stx].sth ← sth;
RETURN}
ELSE fti ← sgb[sgi].file;

IF fti = BcdDefs.FTSelf
THEN {OPEN Space;
file: File.Capability = FileFromBcdBase[bcd];
sth ← [file: file,
span: [base: sgb[sgi].base, pages: sgb[sgi].pages + sgb[sgi].extraPages]]}
ELSE { -- look for the specified file
fileName: ROPE = GetFileName[ssb, ftb[fti].name];
moduleName ← GetModuleName[ssb, ftb[fti].name];
sth ← NewSymbolHandle[fileName: fileName,
version: ftb[sgb[sgi].file].version,
-- of indicated bcd. Should match bcd.version
base: sgb[sgi].base,
pages: sgb[sgi].pages + sgb[sgi].extraPages
! AMTypes.Error => CONTINUE]};
}; -- end ELSE (i.e. bcd # NIL)
RTTypesBasicPrivate.MapStiStd[stx].sth ← sth;
}; -- end DoAcquireSTHFromSTX

-- version must match bcd.version
NewSymbolHandle: PUBLIC PROC[fileName: ROPE,
base: CARDINAL,
pages: CARDINAL,
version: BcdDefs.VersionStamp ← BcdDefs.NullVersion]
RETURNS[SymbolTableHandle] =
{ file: File.Capability ← File.nullCapability;
fileVersionIdent: CARDINAL;
fileVersionStamp: BcdDefs.VersionStamp;
sth: SymbolTableHandle ← nullHandle;
stb: SymbolTableBase;
symbolsStamp: BcdDefs.VersionStamp;

file ← GetReadCapability[fileName ! ANY => CONTINUE];

IF file = File.nullCapability AND version # BcdDefs.NullVersion
THEN {cifsFile: CIFS.OpenFile = VersionToReadableFile[versionStamp: version,
shortNameHint: fileName];
IF cifsFile = NIL THEN GOTO noFile;
file ← CIFS.GetFC[cifsFile
! ANY => {IF OZ THEN Runtime.CallDebugger["CIFS.GetFC failed"];
CONTINUE}];
IF file = File.nullCapability THEN GOTO noFile};
IF file = File.nullCapability THEN GOTO noFile;

-- now check the version info within the file
[fileVersionIdent, fileVersionStamp] ← GetBCDVersionInfo[file];
IF fileVersionIdent # BcdDefs.VersionID THEN GOTO noFile;
IF version # BcdDefs.NullVersion AND version # fileVersionStamp
THEN GOTO noFile;

-- here if file is found and version matches
sth ← [file: file, span: [base: base, pages: pages]];
stb ← AcquireSTB[sth];
symbolsStamp ← stb.stHandle.version;
ReleaseSTB[stb];
[] ← RTTypesBasicPrivate.FindSTI[[symbolsStamp: symbolsStamp, sth: sth]];
RETURN[sth];

EXITS noFile => ERROR AMTypes.Error[reason: noSymbols, msg: fileName];
}; -- end NewSymbolHandle

RCMapOuter: PROC[stb: SymbolTableBase,
mdi: SymbolModuleIndex,
   inner: PROC[base: SymbolTableBase]] =
{stb1: SymbolTableBase = AcquireSTBFromMDI[stb, mdi];
inner[stb1 ! UNWIND => ReleaseSTB[stb1]];
ReleaseSTB[stb1]};

AcquireSTBFromMDI: PUBLIC PROC[stb: SymbolTableBase,
mdi: SymbolModuleIndex,
mesaSymbolsOK: BOOLEANFALSE]
RETURNS[SymbolTableBase] =
{ fileNameDesc, modNameDesc: Strings.SubStringDescriptor;
fileName: STRING = [100];
modName: STRING = [100];

IF mdi = nullModuleIndex THEN ERROR;
fileName.length ← 0;
modName.length ← 0;

stb.SubStringForHash[@modNameDesc, stb.mdb[mdi].moduleId];
Strings.AppendSubString[modName, @modNameDesc];

stb.SubStringForHash[@fileNameDesc, stb.mdb[mdi].fileId];
Strings.AppendSubString[fileName, @fileNameDesc];

RETURN[AcquireSTB[GetSTHForModule[stb.mdb[mdi].stamp,
ConvertUnsafe.ToRope[LONG[fileName]],
ConvertUnsafe.ToRope[LONG[modName]]],
mesaSymbolsOK]]};

GetSTHForModule: PUBLIC PROC[stamp: BcdDefs.VersionStamp,
fileName: ROPE, -- maybe NIL
moduleName: ROPE -- maybe NIL
]
RETURNS[sth: SymbolTableHandle ← nullHandle] =
{ bcd: BcdOps.BcdBase;
newFile: File.Capability ← File.nullCapability;
sgb: Table.Base;
ftb: Table.Base;
ntb: BcdOps.NameString;

mth: BcdOps.MTHandle ← NIL;
sSeg: BcdDefs.SGIndex;
modNameDesc: Strings.SubStringDescriptor
← [base: LOOPHOLE[Rope.Flatten[moduleName]],
offset: 0,
length: Rope.Length[moduleName]];
someNameMatched: BOOLEANFALSE;

FindVersionStampedModule: PROC[mth: BcdOps.MTHandle, mti: BcdDefs.MTIndex]
RETURNS[stop: BOOLEAN] =
{ umid: BcdDefs.VersionStamp = IF mth.file = BcdDefs.FTSelf
THEN bcd.version
ELSE ftb[mth.file].version;
nextModNameDesc: Strings.SubStringDescriptor;
namesMatch: BOOLEANTRUE;

IF moduleName # NIL
THEN {nextModNameDesc ← [base: @ntb.string,
offset: mth.name,
length: ntb.size[mth.name]];
namesMatch ← Strings.EqualSubStrings[@modNameDesc, @nextModNameDesc];
someNameMatched ← someNameMatched OR namesMatch};

RETURN[namesMatch AND (stamp = BcdDefs.NullVersion OR stamp = umid)]
}; -- end FindVersionStampedModule


-- START GetSTHForModule loop body here

IF stamp # BcdDefs.NullVersion
THEN {found: BOOLEAN;
stx: RTTypesBasicPrivate.SymbolTableIndex;
[stx, found] ← FindSTX[stamp];
IF found THEN sth ← DoAcquireSTHFromSTX[stx].sth;
IF sth # nullHandle THEN RETURN[sth]};

IF fileName # NIL THEN newFile ← GetReadCapability[fileName ! ANY => CONTINUE];

IF newFile = File.nullCapability
THEN {IF stamp = BcdDefs.NullVersion
THEN GOTO noFile
ELSE {shortName: ROPE = IF moduleName = NIL
THEN NIL
ELSE Rope.Concat[moduleName, ".bcd"];
cifsFile: CIFS.OpenFile =
VersionToReadableFile[versionStamp: stamp,
shortNameHint: shortName];
IF cifsFile = NIL THEN GOTO noFile;
newFile ← CIFS.GetFC[cifsFile
! ANY => {IF OZ THEN Runtime.CallDebugger
["CIFS.GetFC failed"];
CONTINUE}];
IF newFile = File.nullCapability THEN GOTO noFile}};

bcd ← AcquireBCDFromCap[newFile];
sgb ← LOOPHOLE[bcd + bcd.sgOffset];
ftb ← LOOPHOLE[bcd + bcd.ftOffset];
ntb ← LOOPHOLE[bcd + bcd.ssOffset];
IF bcd.versionIdent # BcdDefs.VersionID THEN GOTO oldSymbols;

mth ← BcdOps.ProcessModules[bcd, FindVersionStampedModule].mth;
IF mth = NIL
THEN IF someNameMatched
THEN GOTO wrongVersion
ELSE GOTO noSymbols;

sSeg ← mth.sseg;
IF sSeg = BcdDefs.SGNull
OR sgb[sSeg].pages = 0
OR ((NOT bcd.definitions) AND (sgb[sSeg].extraPages = 0)) -- compressed symbols
OR sgb[sSeg].file # BcdDefs.FTSelf
THEN GOTO noSymbols;

sth ← [file: newFile, span: [base: sgb[sSeg].base,
pages: sgb[sSeg].pages + sgb[sSeg].extraPages]];
IF stamp = BcdDefs.NullVersion
THEN {stb: SymbolTableBase = AcquireSTB[sth];
stamp ← stb.stHandle.version;
ReleaseSTB[stb]};
[] ← RTTypesBasicPrivate.FindSTI[[symbolsStamp: stamp, sth: sth]];
ReleaseBCD[bcd];
RETURN[sth];

EXITS
noFile => ERROR AMTypes.Error[reason: noSymbols, msg: fileName];
oldSymbols => ERROR AMTypes.Error[reason: noSymbols, msg: moduleName];
noSymbols => ERROR AMTypes.Error[reason: noSymbols,
msg: IF moduleName = NIL
THEN fileName
ELSE moduleName];
wrongVersion => ERROR AMTypes.Error[reason: noSymbols, msg: moduleName];
}; -- end GetSTHForModule

-- raises Error if there are no symbols for the specified module
AcquireSTBFromGFH: PUBLIC PROC[gfh: PrincOps.GlobalFrameHandle,
mesaSymbolsOK: BOOLEANFALSE]
RETURNS[stb: SymbolTableBase ← NIL] =
BEGIN
bcd: BcdOps.BcdBase;
bcdns: BcdOps.NameString;
moduleName: ROPE;
module: PilotLoadStateFormat.ModuleInfo; -- a loadstate.gft entry
mth: BcdOps.MTHandle;
umid: BcdDefs.VersionStamp; -- functional version stamp of the module
ftb: Table.Base;
sth: SymbolTableHandle ← nullHandle;

FindOriginal: PROCEDURE [f: PrincOps.GlobalFrameHandle] RETURNS [BOOLEAN] =
{RETURN[(f # gfh) AND (RTOS.SameCode[gfh, f] = identical) AND (~f.copied)]};

FindModule: PROC [mth: BcdOps.MTHandle, mti: BcdDefs.MTIndex] RETURNS [BOOLEAN] =
{RETURN[(mth.gfi <= module.gfi) AND (module.gfi < (mth.gfi + mth.ngfi))]};

IF RTFlags.checking THEN Runtime.ValidateGlobalFrame[gfh];

IF gfh.copied THEN gfh ← RTOS.EnumerateGlobalFrames[FindOriginal];

[] ← PilotLoadStateOps.InputLoadState[];
module ← PilotLoadStateOps.GetModule[gfh.gfi];
bcd ← PilotLoadStateOps.AcquireBcd[module.config];
PilotLoadStateOps.ReleaseLoadState[];

bcdns ← LOOPHOLE[bcd + bcd.ssOffset];
ftb ← LOOPHOLE[bcd + bcd.ftOffset];
[mth,] ← BcdOps.ProcessModules[bcd, FindModule];
IF mth = NIL THEN ERROR;

umid ← (IF mth.file = BcdDefs.FTSelf THEN bcd.version ELSE ftb[mth.file].version);

-- First look in the umid -> sth map
{found: BOOLEAN;
stx: RTTypesBasicPrivate.SymbolTableIndex;
[stx, found] ← FindSTX[umid];
IF found THEN sth ← AcquireSTHFromSTX[stx].sth};

IF sth = nullHandle -- then try for the one referenced in the bcd
THEN sth ← AcquireSTHFromSGI[bcd, mth.sseg];

IF sth = nullHandle -- then try for the named module's file (locally)
THEN sth ← GetSTHForModule[stamp: umid,
fileName: GetFileName[ssb: bcdns, n: ftb[mth.file].name],
moduleName: GetModuleName[ssb: bcdns, n: mth.name]
! ANY => CONTINUE];

IF sth = nullHandle -- then try for the original one
THEN [sth: sth, moduleName: moduleName]
← AcquireSTHFromSTX[RTTypesBasicPrivate.FindSTI[[symbolsStamp: umid,
bcd: bcd]]];

IF sth = nullHandle -- then give up.
THEN {sgb: Table.Base = LOOPHOLE[bcd + bcd.sgOffset];
msg: ROPE ← moduleName;
IF msg = NIL THEN msg ← GetFileName[ssb: bcdns, n: ftb[sgb[mth.sseg].file].name];
ERROR AMTypes.Error[reason: noSymbols, msg: msg]};
stb ← AcquireSTB[sth, mesaSymbolsOK];
RETURN[stb];
END; -- AcquireSTBFromGFH

AcquireSTBFromSGI: PUBLIC PROC[bcd: BcdOps.BcdBase,
sgi: BcdDefs.SGIndex, -- of the symbol segment
mesaSymbolsOK: BOOLEANFALSE]
RETURNS[stb: SymbolTableBase ← NIL] =
{ ftb: Table.Base = LOOPHOLE[bcd + bcd.ftOffset];
sgh: BcdOps.SGHandle = @LOOPHOLE[bcd + bcd.sgOffset, Table.Base][sgi];
sth: SymbolTableHandle ← AcquireSTHFromSGI[bcd, sgi]; -- try for the referenced one

IF sth = nullHandle
THEN {ssb: BcdOps.NameString = LOOPHOLE[bcd + bcd.ssOffset];
ERROR AMTypes.Error[reason: noSymbols,
msg: GetFileName[ssb: ssb, n: ftb[sgh.file].name]]};
RETURN[AcquireSTB[sth]]};

IsFileInUse: PUBLIC PROC[file: File.Capability] RETURNS[found: BOOLEANFALSE] =
{ loadStateHeld: BOOLEAN;
p: PROC[c: PilotLoadStateFormat.ConfigIndex] RETURNS[stop: BOOLEAN] =
{OPEN Space;
ptr: LONG POINTER = LOOPHOLE[PilotLoadStateOps.AcquireBcd[c]];
space: Handle ← RTOS.UnRavelUSUs[GetHandle[PageFromLongPointer[ptr]]];
PilotLoadStateOps.ReleaseBcd[LOOPHOLE[ptr]];
IF file.fID = GetWindow[space].file.fID
THEN {found ← TRUE; RETURN[TRUE]}
ELSE RETURN[FALSE];
};

-- flush the SymbolCache
SymbolTable.Forget[[file: file, span: SymbolTable.anySpan]
! ANY => {found ← TRUE; CONTINUE}];
IF found THEN RETURN;

-- enumerate the loadstate
[] ← PilotLoadStateOps.InputLoadState[];
loadStateHeld ← TRUE;

[] ← PilotLoadStateOps.EnumerateBcds[recentfirst, p
! ANY => {loadStateHeld ← FALSE;
PilotLoadStateOps.ReleaseLoadState[]}];
IF loadStateHeld THEN PilotLoadStateOps.ReleaseLoadState[];
IF found THEN RETURN;

-- enumerate MapStiStd
FOR x: NAT IN [1..RTTypesBasicPrivate.MapStiStd.length)
DO IF RTTypesBasicPrivate.MapStiStd[x] = NIL THEN EXIT;
IF RTTypesBasicPrivate.MapStiStd[x].sth = nullHandle THEN LOOP;
IF RTTypesBasicPrivate.MapStiStd[x].sth.file.fID = file.fID THEN RETURN[TRUE];
ENDLOOP;
};

-- PRIVATE PROCS --

FindSTX: PROC[stamp: BcdDefs.VersionStamp]
RETURNS[stx: RTTypesBasicPrivate.SymbolTableIndex, found: BOOLEAN] =
{FOR stx IN [1..RTTypesBasicPrivate.MapStiStd.length)
DO IF RTTypesBasicPrivate.MapStiStd[stx] = NIL THEN EXIT;
IF RTTypesBasicPrivate.MapStiStd[stx].symbolsStamp = stamp THEN RETURN[stx, TRUE];
ENDLOOP;
RETURN[0, FALSE]};

-- returns nullHandle if there are no symbols
AcquireSTHFromSGI: PROC [bcd: BcdOps.BcdBase, sgi: BcdDefs.SGIndex]
RETURNS [sth: SymbolTableHandle ← nullHandle] =
{ sgb: Table.Base = LOOPHOLE[bcd + bcd.sgOffset];
ftb: Table.Base = LOOPHOLE[bcd + bcd.ftOffset];
ssb: BcdOps.NameString = LOOPHOLE[bcd + bcd.ssOffset];
sgh: BcdOps.SGHandle ← @sgb[sgi];

IF sgh.class # symbols THEN ERROR;
IF (sgh.pages = 0) -- empty symbol segment
OR ((NOT bcd.definitions) AND (sgh.extraPages = 0)) -- compressed symbols
OR sgh.file = BcdDefs.FTNull
THEN RETURN;

IF sgh.file = BcdDefs.FTSelf
THEN {stb: SymbolTableBase;
symbolsStamp: BcdDefs.VersionStamp;
sth ← [file: FileFromBcdBase[bcd],
span: [base: sgh.base, pages: sgh.pages+sgh.extraPages]];
stb ← AcquireSTB[sth];
symbolsStamp ← stb.stHandle.version;
ReleaseSTB[stb];
[] ← RTTypesBasicPrivate.FindSTI[[symbolsStamp: symbolsStamp, sth: sth]];
}
ELSE
{ fileName: ROPE ← GetFileName[ssb, ftb[sgh.file].name];
sth ← NewSymbolHandle[fileName: fileName,
base: sgh.base,
pages: sgh.pages+sgh.extraPages,
version: ftb[sgh.file].version
-- version of the specified file, not necessarily of the module
! ANY => CONTINUE]}
}; -- end AcquireSTHFromSGI

GetModuleName: PROC[ssb: BcdOps.NameString, n: BcdDefs.NameRecord] RETURNS[ROPE] =
{nameString: STRING = [100];
ssd: Strings.SubStringDescriptor ← [base: @ssb.string, offset: n, length: MIN[ssb.size[n], 100]];
nameString.length ← 0;
Strings.AppendSubString[nameString, @ssd];
FOR i: CARDINAL IN [0..nameString.length) DO
IF nameString[i] = '. THEN {nameString.length ← i; EXIT};
ENDLOOP;
RETURN[ConvertUnsafe.ToRope[LONG[nameString]]]};

GetFileName: PROC[ssb: BcdOps.NameString, n: BcdDefs.NameRecord] RETURNS[ROPE] =
{nameString: STRING = [100];
ssd: Strings.SubStringDescriptor ← [base: @ssb.string, offset: n, length: MIN[ssb.size[n], 100]];
dot: BOOLEANFALSE;
nameString.length ← 0;
Strings.AppendSubString[nameString, @ssd];
FOR i: CARDINAL IN [0..nameString.length) DO
IF nameString[i] = '. THEN {dot ← TRUE; EXIT};
ENDLOOP;
IF ~dot THEN Strings.AppendString[nameString, ".bcd"];
RETURN[ConvertUnsafe.ToRope[LONG[nameString]]]};

FileFromBcdBase: PROC[bcd: BcdOps.BcdBase]
RETURNS[fc: File.Capability ← File.nullCapability] =
{ ssb: BcdOps.NameString = LOOPHOLE[bcd + bcd.ssOffset];
fc ← AcquireFCFromVersion[versionStamp: bcd.version,
shortFileNameHint: Rope.Concat[GetModuleName[ssb, bcd.source],
".bcd"]]};

-- may return NIL
AcquireFCFromVersion: PROC[versionStamp: BcdDefs.VersionStamp,
shortFileNameHint: ROPENIL]
RETURNS[file: File.Capability ← File.nullCapability] =
{ cifsFile: CIFS.OpenFile;

IF shortFileNameHint # NIL THEN file ← GetReadCapability[shortFileNameHint];

IF file # File.nullCapability
THEN {IF GetBCDVersionInfo[file].fileVersionStamp = versionStamp
THEN RETURN[file];
file ← File.nullCapability};

cifsFile ← VersionToReadableFile[versionStamp: versionStamp];
IF cifsFile = NIL THEN RETURN;
file ← CIFS.GetFC[cifsFile ! ANY => {IF OZ THEN Runtime.CallDebugger["CIFS.GetFC failed"];
CONTINUE}];
};

-- may return NIL
AcquireBCDFromVersion: PUBLIC PROC[versionStamp: BcdDefs.VersionStamp,
shortFileNameHint: ROPENIL]
RETURNS[bcd: BcdOps.BcdBase] =
{ file: File.Capability ← AcquireFCFromVersion[versionStamp, shortFileNameHint];
IF file = File.nullCapability
THEN RETURN[NIL]
ELSE RETURN[AcquireBCDFromCap[file]]};

AcquireBCDFromCap: ENTRY PROC[file: File.Capability] RETURNS[bcd: BcdOps.BcdBase] =
{ ENABLE UNWIND => NULL;
space: Space.Handle;
size: NAT; -- pages

Space.Map[onePageSpace, [file: file, base: 1]];
bcd ← LOOPHOLE[Space.LongPointer[onePageSpace]];
IF bcd.extended
THEN size ← bcd.nPages - bcd.rtPages.pages
ELSE size ← bcd.nPages;
Space.Unmap[onePageSpace];
IF size <= standardBCDSpacePages
THEN {IF standardBCDSpaces = NIL
THEN space ← Space.Create[size: standardBCDSpacePages, parent: Space.virtualMemory]
ELSE {space ← standardBCDSpaces.first;
standardBCDSpaces ← standardBCDSpaces.rest;
standardBCDSpacesHeld ← standardBCDSpacesHeld - 1};
standardBCDSpacesInUse ← standardBCDSpacesInUse + 1
}
ELSE {space ← Space.Create[size: size, parent: Space.virtualMemory];
biggerBCDSpacesInUse ← biggerBCDSpacesInUse + 1};
Space.Map[space, [file: file, base: 1]];
bcd ← LOOPHOLE[Space.LongPointer[space]];
};

ReleaseBCD: PUBLIC ENTRY PROC[bcd: BcdOps.BcdBase] =
{ ENABLE UNWIND => NULL;
space: Space.Handle = RTOS.UnRavelUSUs[Space.GetHandle[Space.PageFromLongPointer[bcd]]];
size: NAT ← Space.GetAttributes[space].size;
Space.Unmap[space];
IF size # standardBCDSpacePages
THEN {Space.Delete[space]; biggerBCDSpacesInUse ← biggerBCDSpacesInUse - 1}
ELSE {standardBCDSpaces ← CONS[space, standardBCDSpaces];
standardBCDSpacesInUse ← standardBCDSpacesInUse - 1;
standardBCDSpacesHeld ← standardBCDSpacesHeld + 1};
};

GetBCDVersionInfo: ENTRY PROC[file: File.Capability]
RETURNS[fileVersionIdent: CARDINAL,
fileVersionStamp: BcdDefs.VersionStamp] =
{ ENABLE UNWIND => NULL;
bcd: BcdOps.BcdBase;
Space.Map[onePageSpace, [file: file, base: 1]];
bcd ← LOOPHOLE[Space.LongPointer[onePageSpace]];
fileVersionIdent ← bcd.versionIdent;
fileVersionStamp ← bcd.version;
Space.Unmap[onePageSpace];
};

GetReadCapability: PROC[name: ROPE] RETURNS[f: File.Capability ← File.nullCapability] =
{cifsFile: CIFS.OpenFile ← NIL;
cifsFile ← CIFS.Open[Rope.Concat["/local/", name], CIFS.read
! ANY => CONTINUE];
IF cifsFile = NIL THEN RETURN;
f ← CIFS.GetFC[cifsFile
! ANY => {IF OZ THEN Runtime.CallDebugger["CIFS.GetFC failed"];
CONTINUE}]};

VersionToReadableFile: PUBLIC PROC[versionStamp: BcdDefs.VersionStamp,
shortNameHint: ROPENIL]
RETURNS[file: CIFS.OpenFile ← NIL] =
{mn: ROPENIL;
IF versionMapList = NIL THEN IncludeSymbolsVersionMap["CedarSymbols.VersionMap"];
IF versionMapList = NIL THEN RETURN;
mn ← VersionMap.VersionToName[list: versionMapList,
stamp: versionStamp
! ANY => {IF OZ THEN Runtime.CallDebugger["VersionMap.VersionToName failed"];
CONTINUE}].name;
IF mn = NIL THEN RETURN;
file ← CIFS.Open[mn, CIFS.read + CIFS.dontCheck
! ANY => {IF OZ THEN Runtime.CallDebugger["CIFS.Open failed"];
CONTINUE}];
};

IncludeSymbolsVersionMap: PUBLIC PROC[fileName: ROPE] =
{versionMapList ← CONS[VersionMap.RestoreMapFromFile
[name: fileName, assumeImmutable: TRUE
! ANY => {IF OZ THEN Runtime.CallDebugger
["VersionMap.RestoreMapFromFile failed"];
CONTINUE}],
versionMapList]};

GetMapList: PUBLIC PROC RETURNS[VersionMap.MapList] = {RETURN[versionMapList]};

-- MODULE INITIALIZATION
RCMapOps.EstablishOuter[RCMapOuter];

END.