-- Utilities.Mesa  Edited by Sandman on July 10, 1980  7:52 AM
-- Copyright  Xerox Corporation 1979, 1980

DIRECTORY
  AltoFileDefs USING [FP],
  BcdDefs USING [
    Base, CTIndex, CTNull, CTRecord, FTIndex, FTNull, FTSelf, MTIndex, MTNull,
    MTRecord, NTIndex, NTNull, NTRecord, SGIndex, SGNull, SGRecord],
  BcdOps USING [
    BcdBase, CTHandle, MTHandle, NameString, NTHandle, ProcessModules,
    ProcessSegs, SGHandle],
  CacheOps USING [CopyRead, CopyWrite, READ, WRITE],
  BootmesaOps USING [BootData, BootmesaError, dataObject, VirtualGlobalFrame],
  ControlDefs USING [
    ControlLink, GFTIndex, GFTNull, GlobalFrame, GlobalFrameHandle,
    NullGlobalFrame],
  DirectoryDefs USING [EnumerateDirectory],
  LoaderOps USING [],
  LoadStateOps USING [Map],
  SegmentDefs USING [
    DefaultBase, DeleteFileSegment, FileHandle, FileSegmentAddress,
    FileSegmentHandle, HardUp, InsertFile, LockFile, MakeSwappedIn,
    NewFileSegment, Read, ReleaseFile, SwapIn, SwapUp, Unlock, UnlockFile,
    VMtoFileSegment],
  SegOps USING [EnumerateSegs, NewSeg, Seg],
  String USING [
    AppendChar, AppendString, AppendSubString, EqualSubStrings,
    EquivalentSubStrings, SubString, SubStringDescriptor],
  Storage USING [Node, Free];

Utilities: PROGRAM
  IMPORTS
    DirectoryDefs, BcdOps, SegmentDefs, String, Storage, CacheOps, BootmesaOps,
    SegOps
  EXPORTS BootmesaOps, BcdOps, LoaderOps =PUBLIC
  
  BEGIN OPEN BcdOps, BcdDefs, ControlDefs;
  
  data: POINTER TO BootmesaOps.BootData ← @BootmesaOps.dataObject;
  
  BcdBase: PRIVATE TYPE = BcdOps.BcdBase;
  SubStringDescriptor: TYPE = String.SubStringDescriptor;
  
  currentCti: CTIndex ← CTNull;
  
  FileItem: TYPE = POINTER TO FileObject;
  
  FileObject: TYPE = RECORD [
    fti: BcdDefs.FTIndex,
    ext: BOOLEAN,
    handle: SegmentDefs.FileHandle,
    link: FileItem];
  
  files: FileItem ← NIL;
  loadee: BcdBase;
  ssb: NameString;
  ftb, mtb: Base;
  nfilestofind: CARDINAL ← 0;
  tableopen: BOOLEAN ← FALSE;
  
  FindFiles: PUBLIC PROCEDURE [bcd: BcdBase] =
    BEGIN EnterCodeFileNames[bcd]; LookupFileTable[]; END;
    
  EnterCodeFileNames: PROCEDURE [bcd: BcdBase] =
    BEGIN
    
    SegSearch: PROCEDURE [sgh: SGHandle, sgi: SGIndex] RETURNS [BOOLEAN] =
      BEGIN IF sgh.class = code THEN AddFileName[sgh.file]; RETURN[FALSE]; END;
      
    [] ← BcdOps.ProcessSegs[bcd, SegSearch];
    RETURN;
    END;
    
  AddFileName: PROCEDURE [file: FTIndex] =
    BEGIN
    p: FileItem;
    i, offset, length: CARDINAL;
    FOR p ← files, p.link UNTIL p = NIL DO IF file = p.fti THEN RETURN; ENDLOOP;
    p ← Storage.Node[SIZE[FileObject]];
    p↑ ← [fti: file, handle: NIL, ext: FALSE, link: files];
    files ← p;
    IF file = FTSelf THEN
      BEGIN p.handle ← SegmentDefs.VMtoFileSegment[loadee].file; RETURN END;
    IF file = FTNull THEN BEGIN p.handle ← NIL; RETURN END;
    offset ← ftb[file].name;
    length ← ssb.size[ftb[file].name];
    FOR i IN [offset..offset + length) DO
      IF ssb.string.text[i] = '. THEN BEGIN p.ext ← TRUE; EXIT END; ENDLOOP;
    nfilestofind ← nfilestofind + 1;
    RETURN;
    END;
    
  FindFileName: PROCEDURE [name: String.SubString, ext: BOOLEAN]
    RETURNS [found: BOOLEAN, item: FileItem] =
    BEGIN
    file: SubStringDescriptor ← [base: @ssb.string, offset:, length:];
    FOR item ← files, item.link UNTIL item = NIL DO
      file.offset ← ftb[item.fti].name;
      file.length ← ssb.size[ftb[item.fti].name];
      IF LastCharIsDot[@file] THEN name.length ← name.length + 1;
      IF ext = item.ext AND String.EquivalentSubStrings[@file, name] THEN
	RETURN[TRUE, item];
      ENDLOOP;
    RETURN[FALSE, NIL];
    END;
    
  LastCharIsDot: PROCEDURE [name: String.SubString] RETURNS [BOOLEAN] =
    BEGIN RETURN[name.base[name.offset + name.length - 1] = '.]; END;
    
  FileNotFound: PUBLIC SIGNAL [name: STRING] = CODE;
  
  LookupFileTable: PROCEDURE =
    BEGIN
    p: FileItem;
    ssd: String.SubStringDescriptor;
    name: STRING ← [40];
    IF nfilestofind # 0 THEN DirectoryDefs.EnumerateDirectory[CheckOne];
    FOR p ← files, p.link UNTIL p = NIL DO
      IF p.handle = NIL AND p.fti # FTNull THEN
	BEGIN
	ssd ←
	  [base: @ssb.string, offset: ftb[p.fti].name,
	    length: ssb.size[ftb[p.fti].name]];
	name.length ← 0;
	String.AppendSubString[name, @ssd];
	IF p.ext THEN String.AppendString[name, ".bcd"L];
	SIGNAL FileNotFound[name];
	END;
      ENDLOOP;
    END;
    
  CheckOne: PROCEDURE [fp: POINTER TO AltoFileDefs.FP, name: STRING]
    RETURNS [found: BOOLEAN] =
    BEGIN
    i: CARDINAL;
    dirName: SubStringDescriptor;
    bcd: SubStringDescriptor ← [base: "bcd"L, offset: 0, length: 3];
    item: 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 ~String.EquivalentSubStrings[@dirName, @bcd] THEN GOTO UseWholeName;
	dirName.offset ← 0;
	dirName.length ← i;
	GOTO HasBCDExtension;
	END;
      REPEAT
	UseWholeName => NULL;
	HasBCDExtension =>
	  BEGIN
	  [found, item] ← FindFileName[@dirName, FALSE];
	  IF found THEN RETURN[ThisIsTheOne[fp, item]];
	  END;
      ENDLOOP;
    dirName ← [base: name, offset: 0, length: name.length - 1];
    -- ignore dot on end
    [found, item] ← FindFileName[@dirName, TRUE];
    RETURN[IF found THEN ThisIsTheOne[fp, item] ELSE FALSE];
    END;
    
  ThisIsTheOne: PROCEDURE [fp: POINTER TO AltoFileDefs.FP, item: FileItem]
    RETURNS [BOOLEAN] =
    BEGIN
    item.handle ← SegmentDefs.InsertFile[fp, SegmentDefs.Read];
    nfilestofind ← nfilestofind - 1;
    CheckFileVersion[item];
    RETURN[nfilestofind = 0];
    END;
    
  CheckFileVersion: PROCEDURE [item: FileItem] =
    BEGIN OPEN SegmentDefs;
    seg: FileSegmentHandle ← NewFileSegment[item.handle, 1, 1, Read];
    bcd: BcdBase;
    msg: STRING ← [80];
    SwapIn[seg];
    bcd ← FileSegmentAddress[seg];
    IF bcd.version # ftb[item.fti].version THEN
      BEGIN
      AppendFileName[msg, item];
      String.AppendString[msg, " has incorrect version!"L];
      BootmesaOps.BootmesaError[msg];
      END;
    Unlock[seg];
    LockFile[item.handle];
    DeleteFileSegment[seg];
    UnlockFile[item.handle];
    END;
    
  AppendFileName: PROCEDURE [msg: STRING, item: FileItem] =
    BEGIN
    filename: SubStringDescriptor ←
      [base: @ssb.string, offset: ftb[item.fti].name,
	length: ssb.size[ftb[item.fti].name]];
    String.AppendSubString[msg, @filename];
    END;
    
  FileHandleFromTable: PROCEDURE [fti: FTIndex]
    RETURNS [file: SegmentDefs.FileHandle] =
    BEGIN
    p: FileItem;
    FOR p ← files, p.link UNTIL p = NIL DO
      IF p.fti = fti THEN RETURN[p.handle]; ENDLOOP;
    RETURN[NIL];
    END;
    
  ModuleName: PROCEDURE [frame: GlobalFrameHandle, name: STRING] =
    BEGIN
    bname: SubStringDescriptor;
    mth: MTHandle;
    mti: MTIndex;
    cth: CTHandle;
    ctb: Base = LOOPHOLE[loadee + loadee.ctOffset];
    gfi: GFTIndex = VirtualGlobalFrame[frame].gfi;
    
    FindInstance: PROCEDURE [nth: NTHandle, nti: NTIndex] RETURNS [BOOLEAN] =
      BEGIN
      WITH n: nth.item SELECT FROM
	module => IF n.mti # mti THEN RETURN[FALSE];
	ENDCASE => RETURN[FALSE];
      bname.offset ← nth.name;
      bname.length ← ssb.size[nth.name];
      String.AppendSubString[name, @bname];
      String.AppendChar[name, ':];
      RETURN[TRUE];
      END;
      
    FindModule: PROCEDURE [mth: MTHandle, mti: MTIndex] RETURNS [BOOLEAN] =
      BEGIN RETURN[mth.gfi = gfi]; END;
      
    bname.base ← @ssb.string;
    [mth, mti] ← ProcessModules[loadee, FindModule];
    IF mth.namedInstance THEN [] ← ProcessNames[loadee, FindInstance];
    IF mth.config # FIRST[CTIndex] THEN
      BEGIN
      cth ← @ctb[mth.config];
      bname.offset ← cth.name;
      bname.length ← ssb.size[cth.name];
      String.AppendSubString[name, @bname];
      String.AppendChar[name, '>];
      END;
    bname.offset ← mth.name;
    bname.length ← ssb.size[mth.name];
    String.AppendSubString[name, @bname];
    RETURN
    END;
    
  ModuleGFI: PROCEDURE [name: STRING] RETURNS [GFTIndex] =
    BEGIN
    ss: SubStringDescriptor;
    bname: SubStringDescriptor;
    nmti: MTIndex ← MTNull;
    
    CheckName: PROCEDURE [nth: NTHandle, nti: NTIndex] RETURNS [BOOLEAN] =
      BEGIN
      bname.offset ← nth.name;
      bname.length ← ssb.size[nth.name];
      IF String.EqualSubStrings[@ss, @bname] THEN
	WITH n: nth.item SELECT FROM module => nmti ← n.mti; ENDCASE;
      RETURN[FALSE];
      END;
      
    CheckModule: PROCEDURE [mth: MTHandle, mti: MTIndex] RETURNS [BOOLEAN] =
      BEGIN
      IF currentCti # CTNull AND mth.config # currentCti THEN RETURN[FALSE];
      bname.offset ← mth.name;
      bname.length ← ssb.size[mth.name];
      IF String.EqualSubStrings[@ss, @bname] THEN
	IF nmti = MTNull THEN nmti ← mti ELSE SIGNAL MultipleNames;
      RETURN[FALSE];
      END;
      
    ss ← [base: name, offset: 0, length: name.length];
    bname.base ← @ssb.string;
    [] ← ProcessNames[loadee, CheckName];
    [] ← ProcessModules[loadee, CheckModule];
    RETURN[IF nmti = MTNull THEN GFTNull ELSE mtb[nmti].gfi]
    END;
    
  MultipleNames: PUBLIC SIGNAL = CODE;
  
  FrameForGFI: PROCEDURE [gfi: GFTIndex] RETURNS [GlobalFrameHandle] =
    BEGIN
    RETURN[IF gfi = GFTNull THEN NullGlobalFrame ELSE data.moduleTable[gfi].frame]
    END;
    
  Frame: PROCEDURE [name: STRING] RETURNS [GlobalFrameHandle] =
    BEGIN RETURN[FrameForGFI[ModuleGFI[name]]] END;
    
  SetConfig: PROCEDURE [name: STRING] =
    BEGIN
    ss: SubStringDescriptor;
    bname: SubStringDescriptor;
    cti: CTIndex;
    
    CheckConfig: PROCEDURE [cth: CTHandle, cti: CTIndex] RETURNS [BOOLEAN] =
      BEGIN
      bname.offset ← cth.name;
      bname.length ← ssb.size[cth.name];
      RETURN[String.EqualSubStrings[@ss, @bname]];
      END;
      
    ss ← [base: name, offset: 0, length: name.length];
    bname.base ← @ssb.string;
    cti ← ProcessConfigs[loadee, CheckConfig].cti;
    IF cti = CTNull THEN RETURN;
    currentCti ← cti;
    RETURN
    END;
    
  ResetConfig: PROCEDURE = BEGIN currentCti ← CTNull; RETURN END;
    
  FindCode: PUBLIC PROCEDURE [bcd: BcdBase, map: LoadStateOps.Map] =
    BEGIN
    ssb: NameString ← LOOPHOLE[loadee + loadee.ssOffset];
    
    ModuleSearch: PROCEDURE [mth: MTHandle, mti: MTIndex] RETURNS [BOOLEAN] =
      BEGIN OPEN SegmentDefs;
      i: CARDINAL;
      frame: GlobalFrameHandle ← data.moduleTable[mth.gfi].frame;
      codeseg: SegOps.Seg;
      gf: GlobalFrame;
      
      FindShared: PROCEDURE [smth: MTHandle, smti: MTIndex] RETURNS [BOOLEAN] =
	BEGIN RETURN[smth # mth AND smth.code.sgi = mth.code.sgi] END;
        
      codeseg ← FindCodeSegment[mth, frame];
      FOR i IN [mth.gfi..mth.gfi + mth.ngfi) DO
	data.moduleTable[i].code ← codeseg; ENDLOOP;
      codeseg.data ← FALSE;
      codeseg.link ← NIL;
      codeseg.resident ← FALSE;
      IF BcdOps.ProcessModules[loadee, FindShared].mth # NIL THEN
	BEGIN
	CacheOps.CopyRead[to: @gf, from: frame, size: SIZE[GlobalFrame]];
	gf.shared ← TRUE;
	CacheOps.CopyWrite[from: @gf, to: frame, size: SIZE[GlobalFrame]];
	END;
      RETURN[FALSE];
      END;
      
    [] ← BcdOps.ProcessModules[loadee, ModuleSearch];
    END;
    
  FindCodeSegment: PROCEDURE [mth: MTHandle, frame: GlobalFrameHandle]
    RETURNS [seg: SegOps.Seg] =
    BEGIN OPEN SegOps, SegmentDefs;
    file: FileHandle;
    sgh: SGHandle ← @LOOPHOLE[loadee + loadee.sgOffset, Base][mth.code.sgi];
    i: CARDINAL;
    pages: CARDINAL;
    
    FindSegment: PROCEDURE [fs: Seg] RETURNS [BOOLEAN] =
      BEGIN
      RETURN[fs.realFile = file AND fs.base = sgh.base AND fs.pages = pages];
      END;
      
    FOR i IN [1..LENGTH[data.moduleTable]) DO
      IF data.moduleTable[i].mth.code.sgi = mth.code.sgi THEN
	IF (seg ← data.moduleTable[i].code) # NIL THEN RETURN[seg];
      ENDLOOP;
    file ← FileHandleFromTable[sgh.file];
    pages ← sgh.pages + sgh.extraPages;
    seg ← EnumerateSegs[FindSegment];
    IF seg = NIL THEN
      BEGIN
      seg ← NewSeg[file, sgh.base, pages];
      seg.link2 ← NewFileSegment[file, sgh.base, pages, Read];
      END;
    RETURN
    END;
    
  ProcessConfigs: PROCEDURE [
    bcd: BcdBase, proc: PROCEDURE [CTHandle, CTIndex] RETURNS [BOOLEAN]]
    RETURNS [cth: CTHandle, cti: CTIndex] =
    BEGIN
    ctb: Base = LOOPHOLE[bcd + bcd.ctOffset];
    FOR cti ← FIRST[CTIndex], cti + SIZE[CTRecord] + cth.nControls*SIZE[MTIndex]
      UNTIL cti = bcd.ctLimit DO
      cth ← @ctb[cti]; IF proc[cth, cti] THEN RETURN; ENDLOOP;
    RETURN[NIL, CTNull];
    END;
    
  ProcessModules: PROCEDURE [
    bcd: BcdBase, proc: PROCEDURE [MTHandle, MTIndex] RETURNS [BOOLEAN]]
    RETURNS [mth: MTHandle, mti: MTIndex] =
    BEGIN
    mtb: Base = LOOPHOLE[bcd + bcd.mtOffset];
    FOR mti ← FIRST[MTIndex], mti + SIZE[MTRecord] + mth.frame.length UNTIL mti =
      bcd.mtLimit DO mth ← @mtb[mti]; IF proc[mth, mti] THEN RETURN; ENDLOOP;
    RETURN[NIL, MTNull];
    END;
    
  ProcessNames: PROCEDURE [
    bcd: BcdBase, proc: PROCEDURE [NTHandle, NTIndex] RETURNS [BOOLEAN]]
    RETURNS [nth: NTHandle, nti: NTIndex] =
    BEGIN
    ntb: Base = LOOPHOLE[bcd + bcd.ntOffset];
    FOR nti ← FIRST[NTIndex], nti + SIZE[NTRecord] UNTIL nti = bcd.ntLimit DO
      nth ← @ntb[nti]; IF proc[nth, nti] THEN RETURN; ENDLOOP;
    RETURN[NIL, NTNull];
    END;
    
  ProcessSegs: PROCEDURE [
    bcd: BcdBase, proc: PROCEDURE [SGHandle, SGIndex] RETURNS [BOOLEAN]]
    RETURNS [sgh: SGHandle, sgi: SGIndex] =
    BEGIN
    sgb: Base = LOOPHOLE[bcd + bcd.sgOffset];
    FOR sgi ← FIRST[SGIndex], sgi + SIZE[SGRecord] UNTIL sgi = bcd.sgLimit DO
      sgh ← @sgb[sgi]; IF proc[sgh, sgi] THEN RETURN; ENDLOOP;
    RETURN[NIL, SGNull];
    END;
    
  FinalizeUtilities: PUBLIC PROCEDURE =
    BEGIN
    f: FileItem;
    FOR f ← files, files UNTIL f = NIL DO
      files ← f.link;
      IF f.handle.segcount = 0 THEN SegmentDefs.ReleaseFile[f.handle];
      Storage.Free[f];
      ENDLOOP;
    tableopen ← FALSE;
    END;
    
  InitializeMap: PUBLIC PROCEDURE [bcd: BcdBase] RETURNS [map: LoadStateOps.Map] =
    BEGIN OPEN Storage;
    i: CARDINAL;
    map ← DESCRIPTOR[Node[bcd.firstdummy], bcd.firstdummy];
    FOR i IN [0..bcd.firstdummy) DO map[i] ← i; ENDLOOP;
    END;
    
  DestroyMap: PUBLIC PROCEDURE [map: LoadStateOps.Map] =
    BEGIN IF BASE[map] # NIL THEN Storage.Free[BASE[map]]; END;
    
  -- Link management
  
  ls: POINTER TO ControlDefs.ControlLink;
  dirty: BOOLEAN;
  seg: SegmentDefs.FileSegmentHandle;
  
  OpenLinkSpace: PROCEDURE [frame: GlobalFrameHandle, mth: MTHandle] =
    BEGIN
    IF BootmesaOps.VirtualGlobalFrame[frame].codelinks THEN
      BEGIN OPEN SegmentDefs;
      SwapIn[seg ← data.moduleTable[mth.gfi].code.link2];
      ls ← FileSegmentAddress[seg] + mth.code.offset;
      END
    ELSE BEGIN seg ← NIL; ls ← LOOPHOLE[frame] END;
    ls ← ls - mth.frame.length;
    dirty ← FALSE;
    END;
    
  WriteLink: PROCEDURE [offset: CARDINAL, link: ControlDefs.ControlLink] =
    BEGIN
    dirty ← TRUE;
    IF seg = NIL THEN CacheOps.WRITE[ls + offset, link]
    ELSE (ls + offset)↑ ← link;
    END;
    
  ReadLink: PROCEDURE [offset: CARDINAL] RETURNS [link: ControlDefs.ControlLink] =
    BEGIN
    RETURN[IF seg = NIL THEN CacheOps.READ[ls + offset] ELSE (ls + offset)↑];
    END;
    
  CloseLinkSpace: PROCEDURE [frame: GlobalFrameHandle] =
    BEGIN OPEN SegmentDefs;
    IF seg # NIL THEN
      BEGIN
      Unlock[seg];
      IF dirty THEN BEGIN seg.write ← TRUE; SwapUp[seg]; seg.write ← FALSE; END;
      END;
    END;
    
  -- Utility Routines
  
  
  MakeResident: PUBLIC PROCEDURE [seg: SegmentDefs.FileSegmentHandle] =
    BEGIN OPEN SegmentDefs; MakeSwappedIn[seg, DefaultBase, HardUp]; RETURN END;
    
  globalFrame: GlobalFrame;
  currentFrame: GlobalFrameHandle ← NIL;
  
  VirtualGlobalFrame: PROCEDURE [f: GlobalFrameHandle]
    RETURNS [GlobalFrameHandle] =
    BEGIN
    IF f # currentFrame THEN
      BEGIN
      CacheOps.CopyRead[from: f, to: @globalFrame, size: SIZE[GlobalFrame]];
      currentFrame ← f;
      END;
    RETURN[@globalFrame];
    END;
    
  CodeSegment: PROCEDURE [f: GlobalFrameHandle] RETURNS [SegOps.Seg] =
    BEGIN RETURN[data.moduleTable[VirtualGlobalFrame[f].gfi].code]; END;
    
  InitializeUtilities: PUBLIC PROCEDURE [bcd: BcdBase] =
    BEGIN
    loadee ← bcd;
    mtb ← LOOPHOLE[loadee + loadee.mtOffset];
    ssb ← LOOPHOLE[loadee + loadee.ssOffset];
    ftb ← LOOPHOLE[loadee + loadee.ftOffset];
    IF tableopen THEN FinalizeUtilities[];
    tableopen ← TRUE;
    currentCti ← CTNull;
    RETURN
    END;
    
  
  END......