-- Copyright (C) 1984  by Xerox Corporation. All rights reserved. 
-- NewChainCruiser.mesa
-- HGM, 19-Nov-84 15:05:56
-- Brenda Hankins	  20-Sep-84 13:44:01

DIRECTORY
  BitMapDefs USING [Clear, Create, Map, MapIndex, Set, Test],
  File USING [File, nullFile],
  FormSW USING [
    AllocateItemDescriptor, BooleanItem, ClientItemsProcType, CommandItem,
    newLine, nextPlace, NumberItem, ProcType, StringItem],
  Inline USING [LongCOPY, LowHalf],
  MFile USING [Error, GetLength, Handle, ReadOnly, Release],
  HeapFile USING [
    ChainBlock, headerSize, noSegment, SegmentIndex, segmentsPerPage, segmentSize,
    SerialAndHead],
  Put USING [CR, Decimal, Line, Text],
  Runtime USING [IsBound],
  Space USING [CopyOut, InsufficientSpace, Interval, Kill, Map, nullInterval],
  SpecialMFile USING [GetCapaWithAccess],
  Storage USING [Free],
  Tool USING [
    Create, MakeFileSW, MakeFormSW, MakeMsgSW, MakeSWsProc, UnusedLogName],
  ToolWindow USING [TransitionProcType],
  VMDefs USING [PageNumber, pageSize],
  Window USING [Box, Handle];

NewChainCruiser: PROGRAM
  IMPORTS
    BitMapDefs, FormSW, Inline, MFile, Put, Runtime, Space, SpecialMFile,
    Storage, Tool
  SHARES HeapFile =

  BEGIN

  --VARIABLES:  

  toolData: MACHINE DEPENDENT RECORD [
    -- tool window stuff:
    msgSW(0): Window.Handle ← NIL,
    fileSW(2): Window.Handle ← NIL,
    formSW(4): Window.Handle ← NIL,
    -- vars:
    heapFileName(6): LONG STRING,
    chainFileName(8): LONG STRING,
    chainFile(10): File.File,
    chainFileHandle(17): MFile.Handle,
    segmentToChange(19): CARDINAL,
    segmentToPointTo(20): CARDINAL,
    segmentBound(21): CARDINAL,  -- last valid real segment no.
    heapSize(22): CARDINAL,
    chainSize(23): CARDINAL,
    filesOpened(24): BOOLEAN,
    permanentChange(25): BOOLEAN,
    chain(26): LONG POINTER TO HeapFile.ChainBlock,
    chainSpace(28): Space.Interval];

  --****************************************************************************

  OpenFiles: FormSW.ProcType =
    BEGIN  --looks for the files in Mesa File System.
    errorOnOpen: BOOLEAN ← FALSE;
    IF toolData.heapFileName # NIL THEN
      BEGIN
      ENABLE
        BEGIN
        MFile.Error => {errorOnOpen ← TRUE; CONTINUE};
        Space.InsufficientSpace => {
          Put.Line[toolData.msgSW, "Space.InsufficientSpace."L];
          errorOnOpen ← TRUE;
          CONTINUE};
        END;
      heapFileHandle: MFile.Handle ← MFile.ReadOnly[
        toolData.heapFileName, [NIL, NIL] !
        MFile.Error =>
          Put.Line[toolData.msgSW, "MFile error on open of data file."L]];
      toolData.heapSize ← Inline.LowHalf[
        MFile.GetLength[heapFileHandle] / 512 --bytes/pg-- ];
      MFile.Release[heapFileHandle];
      END;
    IF errorOnOpen THEN {
      Put.Line[toolData.fileSW, "Specify the heap size and try again."L]; RETURN};
    toolData.chainSize ←
      ((toolData.heapSize / HeapFile.segmentSize) +
         HeapFile.segmentsPerPage - 1) /
        HeapFile.segmentsPerPage;
    toolData.segmentBound ←
      (toolData.heapSize / HeapFile.segmentSize) +
        toolData.chainSize * HeapFile.headerSize - 1;
    IF toolData.chainFileName # NIL THEN
      BEGIN
      ENABLE MFile.Error => {errorOnOpen ← TRUE; CONTINUE};
      tempSize: CARDINAL ← 0;
      toolData.chainFileHandle ← MFile.ReadOnly[
        toolData.chainFileName, [NIL, NIL] !
        MFile.Error =>
          Put.Line[toolData.msgSW, "MFile error on open of chain file."L]];
      toolData.chainFile ← SpecialMFile.GetCapaWithAccess[
        toolData.chainFileHandle];
      Put.Line[toolData.fileSW, "Chain file opened."L];
      IF toolData.heapSize > toolData.chainSize * 2 THEN
        Put.Line[
          toolData.msgSW,
          "Warning: size of chainfile is larger than that of Heap."L];
      IF toolData.heapSize < toolData.chainSize * 2 THEN
        Put.Line[
          toolData.msgSW,  --  must stop
          "Size of chainfile is smaller than that of Heap - you should quit."L];
      -- build chain data structure:
      toolData.chainSpace ← Space.Map[
        window: [file: File.nullFile, base: 0, count: toolData.chainSize]];
      toolData.chain ← toolData.chainSpace.pointer;
      FOR index: CARDINAL IN [0..toolData.chainSize) DO
        tempSpace: Space.Interval;
        chain0, chain1, chainChoice: LONG POINTER TO
          HeapFile.SerialAndHead;
        -- look at the chain in duplicate page pairs and use the 'correct' one.
        tempSpace ← LOOPHOLE[Space.Map[
          window: [toolData.chainFile, index * 2 + 1, 2], access: readOnly]];
        -- remember to skip over leader page
        chain0 ← LOOPHOLE[tempSpace.pointer];
        chain1 ← LOOPHOLE[chain0 + VMDefs.pageSize];
        chainChoice ←
          IF chain1.serialNumber > chain0.serialNumber THEN chain1 ELSE chain0;
        Inline.LongCOPY[
          from: chainChoice, nwords: VMDefs.pageSize,
          to: toolData.chain + index * VMDefs.pageSize];
        Space.Kill[tempSpace];
        ENDLOOP;
      IF toolData.chain.header[0].chainHead = HeapFile.noSegment THEN
        Put.Line[
          toolData.msgSW, "There are no segments in chain (head pts to end)."L];
      END
    ELSE Put.Line[toolData.msgSW, "No chain file specified."L];
    IF ~errorOnOpen THEN toolData.filesOpened ← TRUE;
    END;  -- proc. OpenFiles

  --****************************************************************************

  CheckChain: FormSW.ProcType =
    BEGIN
    segmentCount: CARDINAL ← 0;
    lastSeg: CARDINAL;
    foundProblem: BOOLEAN ← FALSE;
    freeMap: BitMapDefs.Map;
    IF NOT toolData.filesOpened THEN {
      Put.Line[toolData.msgSW, "No open file."L]; RETURN};
    freeMap ← BitMapDefs.Create[toolData.segmentBound + 1];
    FOR index: CARDINAL IN [0..toolData.chainSize) DO
      -- make nonexistent seg indices look 'busy'
      firstBit: BitMapDefs.MapIndex = index * VMDefs.pageSize;
      FOR j: CARDINAL IN [0..HeapFile.headerSize) DO
        BitMapDefs.Set[freeMap, firstBit + j] ENDLOOP;
      ENDLOOP;
    FOR s: CARDINAL ← toolData.chain.header[0].chainHead, toolData.chain.next[s]
      UNTIL s = HeapFile.noSegment DO
      IF NOT BitMapDefs.Test[freeMap, s] THEN
        BEGIN
        BitMapDefs.Set[freeMap, s];
        lastSeg ← s;
        segmentCount ← segmentCount + 1;
        END
      ELSE  -- have found a circularity.
        BEGIN
        foundProblem ← TRUE;
        Put.Text[toolData.fileSW, "Logical segment number "L];
        Put.Decimal[toolData.fileSW, segmentCount];
        Put.Text[toolData.fileSW, " (segment index "L];
        Put.Decimal[toolData.fileSW, lastSeg];
        Put.Line[
          toolData.fileSW, ") should be changed to point to rest of chain."L];
        Put.Text[toolData.fileSW, "It now points to segment index "L];
        Put.Decimal[toolData.fileSW, s];
        Put.Line[toolData.fileSW, "."L];
        Put.Text[toolData.fileSW, "That change will leave "L];
        Put.Decimal[toolData.fileSW, segmentCount];
        Put.Text[toolData.fileSW, " segments in the heap."L];
        Put.Text[toolData.fileSW, "When it is full there are "L];
        Put.Decimal[
          toolData.fileSW, toolData.heapSize / HeapFile.segmentSize];
        Put.Line[toolData.fileSW, " segments in the heap."L];
        EXIT;
        END;
      ENDLOOP;
    IF NOT foundProblem THEN
      Put.Line[toolData.fileSW, "No circularity found in chain."L];
    BitMapDefsDelete[freeMap];
    END;  -- proc. CheckChain

  --****************************************************************************

  ChainChanges: FormSW.ProcType =
    BEGIN
    IF NOT toolData.filesOpened THEN {
      Put.Line[toolData.msgSW, "No open file."L]; RETURN};
    Put.Line[
      toolData.msgSW,
      "Segments are specified as real indices NOT logical values (2 is Chain head)."L];
    toolData.chain.next[toolData.segmentToChange] ← toolData.segmentToPointTo;
    IF toolData.permanentChange THEN
      BEGIN  -- update chain file:
      page: CARDINAL = toolData.segmentToChange / VMDefs.pageSize;
      chainPage: VMDefs.PageNumber =
        page * 2 +
          (IF toolData.chain.header[page].serialNumber MOD 2 = 0 THEN 0 ELSE 1);
      Put.Line[toolData.fileSW, "This will be a permanent change"L];
      toolData.chain.header[page].serialNumber ←
        toolData.chain.header[page].serialNumber + 1;

      --old: FileDefs.WritePageToFile[to: client.chainHandle,
      --old:  page: chainPage, from: @client.chain.header[page]];
      [] ← Space.CopyOut[
        pointer: @toolData.chain.header[page],
        window: [
        file: toolData.chainFile, base: chainPage + 1,  -- incr to skip over leader page
        count: 1]];
      END;
    END;  -- proc. ChainChanges

  --**************************************************************************

  ShowChainSeq: FormSW.ProcType =
    BEGIN  -- run thru and print out indices.
    segCount: CARDINAL ← 0;
    IF NOT toolData.filesOpened THEN {
      Put.Line[toolData.msgSW, "No open file."L]; RETURN};
    FOR s: CARDINAL ← toolData.chain.header[0].chainHead, toolData.chain.next[s]
      UNTIL s = HeapFile.noSegment DO
      Put.Decimal[toolData.fileSW, s];
      Put.Text[toolData.fileSW, "  "L];
      segCount ← segCount + 1;
      IF segCount > toolData.segmentBound THEN {
        Put.Line[toolData.fileSW, "Stopping due to an apparent circularity."L];
        EXIT};
      ENDLOOP;
    IF segCount <= toolData.segmentBound THEN
      Put.Line[toolData.fileSW, "end of chain."L];
    END;  -- proc. ShowChainSeq

  --**************************************************************************

  GenerateChainSeqs: FormSW.ProcType =
    BEGIN  -- look for all disjoint sequences in chain file, 
    -- it only prints subsequences not on chain.
    seg, lastSeg: CARDINAL;
    seqHeads, chainEntries: BitMapDefs.Map;
    IF NOT toolData.filesOpened THEN {
      Put.Line[toolData.msgSW, "No open file."L]; RETURN};
    seqHeads ← BitMapDefs.Create[toolData.segmentBound + 1];
    chainEntries ← BitMapDefs.Create[toolData.segmentBound + 1];
    FOR index: CARDINAL IN [0..toolData.chainSize) DO
      -- make nonexistent seg indices look 'busy'
      firstBit: BitMapDefs.MapIndex = index * VMDefs.pageSize;
      FOR j: CARDINAL IN [0..HeapFile.headerSize) DO
        BitMapDefs.Set[chainEntries, firstBit + j] ENDLOOP;
      ENDLOOP;
    seg ← toolData.chain.header[0].chainHead;
    UNTIL seg = HeapFile.noSegment DO
      noOfSegs: CARDINAL ← 0;
      FOR s: CARDINAL ← seg, toolData.chain.next[s] DO
        IF BitMapDefs.Test[chainEntries, s] OR s = HeapFile.noSegment
          THEN
          BEGIN  -- a circularity or can append another sequence
          Put.Text[toolData.fileSW, "Found a sequence "L];
          Put.Decimal[toolData.fileSW, noOfSegs];
          Put.Text[toolData.fileSW, " segments long starting at "L];
          Put.Decimal[toolData.fileSW, seg];
          Put.Text[toolData.fileSW, " and ending at "L];
          Put.Decimal[toolData.fileSW, s];
          Put.CR[toolData.fileSW];
          IF seg = toolData.chain.header[0].chainHead THEN
            Put.Line[toolData.fileSW, "	this is first segment in the chain"L];
          IF s = toolData.chain.header[0].chainHead THEN
            Put.Line[
              toolData.fileSW, "	this last points to current head of the chain"L];
          IF s # HeapFile.noSegment AND BitMapDefs.Test[seqHeads, s]
            AND s # toolData.chain.header[0].chainHead THEN
            BEGIN  -- s is head of a chain we've saved, replace that head with seg
            BitMapDefs.Clear[seqHeads, s];
            Put.Line[
              toolData.fileSW, "	it was prepended to a previous sequence."L];
            END;
          BitMapDefs.Set[seqHeads, seg];  -- store seg as another chain start
          seg ← FindFree[chainEntries, seg];
          EXIT;
          END
        ELSE {
          BitMapDefs.Set[chainEntries, s]; lastSeg ← s; noOfSegs ← noOfSegs + 1}
        ENDLOOP;
      ENDLOOP;
    BitMapDefsDelete[chainEntries];
    BitMapDefsDelete[seqHeads];
    END;  -- proc. GenerateChainSeqs

  --**************************************************************************

  FindFree: PROCEDURE [m: BitMapDefs.Map, near: HeapFile.SegmentIndex]
    RETURNS [new: BitMapDefs.MapIndex] =
    BEGIN  -- stolen from StableStorageImpl.FindSegment
    -- find 'nearest' free segment;
    high: BitMapDefs.MapIndex ← near;
    low: BitMapDefs.MapIndex ←
      (  -- skip over headers at chain page boundaries
        IF near > HeapFile.headerSize THEN
        near -
          (IF near MOD VMDefs.pageSize = HeapFile.headerSize THEN
           HeapFile.headerSize + 1 ELSE 1)
        ELSE HeapFile.headerSize);
    DO
      IF ~BitMapDefs.Test[m, high] THEN {new ← high; EXIT};
      IF ~BitMapDefs.Test[m, low] THEN {new ← low; EXIT};
      IF high = toolData.segmentBound AND low = HeapFile.headerSize THEN
        RETURN[HeapFile.noSegment];  -- ever get?
      high ← MIN[
        high +
        (IF high + 1 MOD VMDefs.pageSize = 0 THEN HeapFile.headerSize
         ELSE 1), toolData.segmentBound];
      low ←
        (IF low > HeapFile.headerSize THEN
         low -
           (IF low MOD VMDefs.pageSize = HeapFile.headerSize THEN
            HeapFile.headerSize + 1 ELSE 1)
         ELSE HeapFile.headerSize);
      ENDLOOP;
    END;

  --**************************************************************************

  BitMapDefsDelete: PROCEDURE [m: BitMapDefs.Map] =
    BEGIN  -- inverse of BitMapDefs.Create
    Storage.Free[m.data.BASE];
    m.data.BASE ← NIL;
    Storage.Free[m];
    m ← NIL;
    END;

  --**************************************************************************

  HelpProc: FormSW.ProcType =
    BEGIN
    Put.Line[
      toolData.fileSW,
      "Specify chain file name.  If data file is on this volume then specify its name else specify its size in pages.  Do 'Open Files'."L];
    Put.Line[
      toolData.fileSW,
      "Now do 'Check Chain', this will indicate whether there is a circularity in the chain."L];
    Put.Line[
      toolData.fileSW,
      "If there is, do 'Generate Chain Seqs' to see if you think any thing has been chopped off the chain  (anything real long will look suspicious)."L];
    Put.Line[
      toolData.fileSW,
      "Do 'Chain Changes' to propose changes to the chain and when you're satisfied with all the changes you've made, do them all again with the boolean 'Permanent Changes' set."L];
    Put.Line[
      toolData.fileSW,
      "Show Chain Seq will show you what the chain looks like."L];
    END;  -- proc. HelpProc

  --**************************************************************************

  -- Tool needed routines:

  ClientTransition: ToolWindow.TransitionProcType =
    BEGIN
    SELECT TRUE FROM
      new = active =>
        BEGIN
        toolData.heapFileName ← NIL;
        toolData.chainFileName ← NIL;
        toolData.chainFileHandle ← NIL;
        toolData.segmentToChange ← 2;  -- head of chain.
        toolData.segmentToPointTo ← LAST[CARDINAL];  -- end of chain.
        toolData.chainSpace ← Space.nullInterval;
        toolData.chain ← NIL;
        toolData.heapSize ← 0;
        toolData.filesOpened ← FALSE;
        toolData.permanentChange ← FALSE;
        END;
      old = active =>
        BEGIN
        IF toolData.chainSpace # Space.nullInterval THEN
          Space.Kill[toolData.chainSpace];
        IF toolData.chainFileHandle # NIL THEN
          MFile.Release[toolData.chainFileHandle];
        END;
      ENDCASE;
    END;  -- proc. ClientTransition

  --***************************************************************************

  MakeTool: PROCEDURE [initialBox: Window.Box] =
    BEGIN
    window: Window.Handle ← Tool.Create[
      makeSWsProc: MakeSWs, initialState: default, initialBox: initialBox,
      clientTransition: ClientTransition, name: "NewChainCruiser"L,
      tinyName1: "NewChain"L, tinyName2: "Cruiser"L];
    END;  -- proc. MakeTool

  --******************************************************************************

  tIndex: TYPE = {
    heapFileName, chainFileName, openFiles, heapSize, permanentChange,
    segmentToChange, segmentToPointTo, checkChain, chainChanges, showChainSeq,
    generateChainSeqs, help};
  noToolIndices: CARDINAL = tIndex.LAST.ORD + 1;

  MakeCommon: FormSW.ClientItemsProcType =
    BEGIN OPEN FormSW;
    items ← AllocateItemDescriptor[noToolIndices];
    items[tIndex.heapFileName.ORD] ← StringItem[
      tag: "Heap File Name"L, place: newLine, string: @toolData.heapFileName,
      inHeap: TRUE];
    items[tIndex.chainFileName.ORD] ← StringItem[
      tag: "Chain File Name"L, place: newLine, string: @toolData.chainFileName,
      inHeap: TRUE];
    items[tIndex.openFiles.ORD] ← CommandItem[
      tag: "OpenFiles"L, proc: OpenFiles, place: newLine];
    items[tIndex.heapSize.ORD] ← NumberItem[
      tag: "HeapSize (in pages)"L, value: @toolData.heapSize, radix: decimal,
      place: nextPlace];
    items[tIndex.permanentChange.ORD] ← BooleanItem[
      tag: "Make Permanent Change"L, switch: @toolData.permanentChange,
      place: nextPlace];
    items[tIndex.segmentToChange.ORD] ← NumberItem[
      tag: "Segment Index To Change"L, value: @toolData.segmentToChange,
      radix: decimal, place: newLine];
    items[tIndex.segmentToPointTo.ORD] ← NumberItem[
      tag: "Segment Index To Point To"L, value: @toolData.segmentToPointTo,
      radix: decimal, place: nextPlace];
    items[tIndex.checkChain.ORD] ← CommandItem[
      tag: "Check Chain"L, proc: CheckChain, place: newLine];
    items[tIndex.chainChanges.ORD] ← CommandItem[
      tag: "Chain Changes"L, proc: ChainChanges, place: nextPlace];
    items[tIndex.showChainSeq.ORD] ← CommandItem[
      tag: "Show Chain Seq"L, proc: ShowChainSeq, place: nextPlace];
    items[tIndex.generateChainSeqs.ORD] ← CommandItem[
      tag: "Generate Chain Seqs"L, proc: GenerateChainSeqs, place: nextPlace];
    items[tIndex.help.ORD] ← CommandItem[
      tag: "Help"L, proc: HelpProc, place: nextPlace];
    RETURN[items: items, freeDesc: TRUE];
    END;  --  proc. MakeCommon.

  --****************************************************************************

  MakeSWs: Tool.MakeSWsProc =
    BEGIN
    logName: STRING ← [40];
    Tool.UnusedLogName[unused: logName, root: "ChainCruiser.log"L];
    toolData.msgSW ← Tool.MakeMsgSW[window: window, h: 32];
    toolData.formSW ← Tool.MakeFormSW[
      window: window, formProc: MakeCommon, h: 96];
    toolData.fileSW ← Tool.MakeFileSW[window: window, name: logName, h: 350];
    END;

  --************************************************************************

  -- mainline code:

  IF Runtime.IsBound[LOOPHOLE[BitMapDefs.Create]] THEN
    MakeTool[[[0, 35], [482, 650]]]
  ELSE Put.Line[s: "BitMapDefsImpl not available!"L];

  END.
  

LOG: 
 4-Sep-84 12:31:29	 converted from MSScanTool