-- Transport Mechanism Filestore - restart --

-- REMOVE LOOPHOLES(COMMENTS) AROUND NSString/NSCommand CALLS

-- [Idun]<WServices>MS>StableStorageRestart.mesa

-- Wobber		 9-Aug-82 15:07:04 --
-- BLyon		30-Oct-81 17:21:47 --
-- Randy Gobbel		 2-Aug-82 18:42:09 --
-- Andrew Birrell	 3-Jun-81 18:22:21 --

DIRECTORY
  Alloc USING [shortTerm, longTerm],
  Bitmap USING [Create, MapIndex, Set],
  HeapString USING [AppendString, Free, Pickle],
  Inline USING [LowHalf],
  NSString USING [nullString, String, StringFromMesaString],
  Object USING [Offset, objectStart],
  ObjectDir --EXPORT only-- ,
  ObjectDirImpl --LOCK--,  -- Beware of re-compilation constraints --
  ObjectDirInternal,
  ObjectInternal USING [PageHeader, ObjectHeader],
  Policy USING [AmountOfFreeHeap],
  ServerLog USING [Handle, WriteLogEntry],
  StableStorageImpl,
  VMDefs USING [AllocateBlock, FileHandle, FullAddress, GetFileLength, MapPage,
    MarkStart, MarkStartWait, OpenFile, Page, PageAddress, PageIndex, PageNumber,
    pageSize, ReadPage, Release, RemapPage, SetFileLength, UnmapPage, UsePage,
    WaitFile];

StableStorageRestart: MONITOR LOCKS ObjectDirImpl.LOCK
  --for RestartObject--
  IMPORTS Alloc, Bitmap, HeapString, Inline, NSString, Policy,
    ObjectDirImpl, ServerLog, StableStorageImpl, VMDefs
  EXPORTS ObjectDir --RestartObject, Client-- , Object --HeapRestart--
  SHARES ObjectDirImpl, StableStorageImpl =
BEGIN

lz: UNCOUNTED ZONE = Alloc.longTerm;

-- ??? msID: Clearinghouse.TypeID = ClearinghouseDefinitions.fileserver;

Client: TYPE = ObjectDirInternal.Client;
ClientObject: PUBLIC TYPE = ObjectDirInternal.ClientObject;

S: PROCEDURE [s: LONG STRING] RETURNS [NSString.String] =
  {RETURN[NSString.StringFromMesaString[s]]};

-- Opens files concerned with heap:
--   "<client>.objectDir"  The object directory; initialised on every restart
--                         and extended dynamically as needed during run.
--   "<client>.data"       The file containing the heap objects is considered
--                         as a sequence of separate segments, numbered
--                        [FIRST[ObjectDirInternal.Segment]..LAST[ObjectDirInternal.Segment]]
--                         These segments are of equal size.  the file should
--                         be pre-allocated on consecutive pages of physical
--                         disk.
--   "<client>.Chain"      Ordering for segments of the data file; two pages only.
-- The object directory and ancillary variables are set to correspond to
-- the heap objects found to exist in the heap file, read in the
-- order given by the chain file, with their reference counts set to zero
-- pending restart of steering list queues and mailboxes.  After restart of
-- steering list queues and mailboxes, the Compactor should be started, and
-- will then run as an asynchronous activity.

IllegalObject: SIGNAL = CODE;

RestartObject: PUBLIC ENTRY PROCEDURE
  [obj: ObjectDirInternal.ObjectNumber, client: Client] =
  BEGIN OPEN ObjectDirImpl, ObjectDirInternal, client;
    -- executes inside the ObjectDirImpl monitor --
  either: LONG POINTER TO DirData = FindData[obj, client];
  WITH data: either SELECT FROM
    used =>
      BEGIN
      IF data.count = LAST[ObjectCount] THEN ERROR ObjectCountTooBig;
      data.count ← data.count + 1;
      END;
    ENDCASE => ERROR ObjectNotInUse[];
  ReleaseData[dirty, client];
  END;


BadChainSize: ERROR = CODE;
NoWrittenHeapSegment: ERROR = CODE;
TooManySegments: ERROR = CODE;
InitialisingHeap: PUBLIC SIGNAL = CODE;  -- resume if you really mean it!--

InitChain: PROCEDURE [client: Client] =
  BEGIN OPEN client, ObjectDirInternal, StableStorageImpl;
  bodyChainTitle: NSString.String ← NSString.nullString;
  chainSize: CARDINAL = (segmentCount+segmentsPerPage-1) / segmentsPerPage;
  index: CARDINAL;

  segmentCeiling ← segmentCount + chainSize*headerSize - 1;
  bodyChainTitle ← HeapString.AppendString[bodyChainTitle, name];
  bodyChainTitle ← HeapString.AppendString[bodyChainTitle, S[".chain"L]];
  chainHandle ← VMDefs.OpenFile[
    options: oldOrNew, name: bodyChainTitle, cacheFraction: 0];
  VMDefs.SetFileLength[chainHandle, [chainSize*2, 0]];
  chain ← LOOPHOLE[VMDefs.AllocateBlock[chainSize]];
  FOR index IN [0..chainSize) DO
    VMDefs.RemapPage[
      LOOPHOLE[chain + index*VMDefs.pageSize],
      [chainHandle, index*2]];
    ENDLOOP;
  FOR index IN [0..segmentCeiling] DO
    chain.next[index] ← noSegment ENDLOOP;
  FOR index IN [0..chainSize) DO
    chain.header[index].serialNumber ← 0;
    IF index = 0 THEN chain.header[index].chainHead ← headerSize;
    VMDefs.MarkStartWait[LOOPHOLE[chain+index*VMDefs.pageSize]];
    ENDLOOP;
  FOR index IN [0..chainSize) DO
    VMDefs.RemapPage[
      LOOPHOLE[chain + index*VMDefs.pageSize],
      [chainHandle, index*2+1]];
    chain.header[index].serialNumber ← 1;
    IF index = 0 THEN chain.header[index].chainHead ← headerSize;
    VMDefs.MarkStartWait[LOOPHOLE[chain+index*VMDefs.pageSize]];
    ENDLOOP;
  IF chain.header[0].chainHead = noSegment THEN ERROR NoWrittenHeapSegment[];
  InitFreeMap[client, chainSize];
  HeapString.Free[@bodyChainTitle];
  END;
    
ReadChain: PROCEDURE [client: Client] =
  BEGIN OPEN client, ObjectDirInternal, StableStorageImpl;
  bodyChainTitle: NSString.String ← NSString.nullString;
  chain0, chain1: LONG POINTER TO SerialAndHead;
  chainSize: CARDINAL = (segmentCount+segmentsPerPage-1) / segmentsPerPage;

  segmentCeiling ← segmentCount + chainSize*headerSize - 1;
  bodyChainTitle ← HeapString.AppendString[bodyChainTitle, name];
  bodyChainTitle ← HeapString.AppendString[bodyChainTitle, S[".chain"L]];
  chainHandle ← VMDefs.OpenFile[
    options: old, name: bodyChainTitle, cacheFraction: 0];
  IF VMDefs.GetFileLength[chainHandle].page # chainSize*2 THEN ERROR BadChainSize;
  chain ← LOOPHOLE[VMDefs.AllocateBlock[chainSize]];
  FOR index: CARDINAL IN [0..chainSize) DO
    chainAddr: VMDefs.PageAddress;
    chain0 ← LOOPHOLE[VMDefs.ReadPage[[chainHandle, index*2], 3]];
    chain1 ← LOOPHOLE[VMDefs.ReadPage[[chainHandle, index*2+1], 3]];
    IF chain1.serialNumber > chain0.serialNumber THEN
      chainAddr ← [chainHandle, index*2+1]
    ELSE chainAddr ← [chainHandle, index*2];
    VMDefs.UnmapPage[LOOPHOLE[chain0]]; VMDefs.UnmapPage[LOOPHOLE[chain1]];
    VMDefs.MapPage[LOOPHOLE[chain + index*VMDefs.pageSize], chainAddr];
    ENDLOOP;
  IF chain.header[0].chainHead = noSegment THEN ERROR NoWrittenHeapSegment[];
  InitFreeMap[client, chainSize];
  HeapString.Free[@bodyChainTitle];
  END;
  
InitFreeMap: PROCEDURE [client: Client, chainSize: CARDINAL] =
  BEGIN OPEN client, ObjectDirInternal;
  freeCount ← segmentCount;
  freeMap ← Bitmap.Create[segmentCeiling+1];
  FOR index: CARDINAL IN [0..chainSize) DO
    -- make nonexistent seg indices look 'busy'
    firstBit: Bitmap.MapIndex = index * VMDefs.pageSize;
    FOR j: CARDINAL IN [0..headerSize) DO
      Bitmap.Set[freeMap, firstBit+j] ENDLOOP;
    ENDLOOP;
  FOR s: CARDINAL ← chain.header[0].chainHead, chain.next[s]
    UNTIL s = noSegment DO
    Bitmap.Set[freeMap, s]; freeCount ← freeCount - 1;
    ENDLOOP;
  Policy.AmountOfFreeHeap[(freeCount*100 + segmentCount/2)/segmentCount];
  END;


HeapDataTooSmall: SIGNAL = CODE;

EmptyPage: PROCEDURE [where: VMDefs.PageAddress] =
  BEGIN OPEN VMDefs, ObjectInternal;
  page: Page = UsePage[where];
  header: LONG POINTER TO PageHeader =
    LOOPHOLE[page, LONG POINTER] + FIRST[PageIndex];
  obj: LONG POINTER TO ObjectHeader =
    LOOPHOLE[page, LONG POINTER] + FIRST[PageIndex] + SIZE[PageHeader];
  header.offset ← Object.objectStart;
  obj.number ← ObjectDirInternal.gapObjectNumber;
  obj.size ←
    1 + LAST[PageIndex] -
      (FIRST[PageIndex] + SIZE[PageHeader] + SIZE[ObjectHeader]);
  MarkStart[page];
  Release[page];
  END;


InitStableStorage: PROCEDURE [client: Client, fileSize: LONG CARDINAL] =
  BEGIN OPEN client, ObjectDirInternal, StableStorageImpl;
  dataName: NSString.String ← NSString.nullString;
  page: VMDefs.PageNumber;
  dataName ← HeapString.AppendString[dataName, name];
  dataName ← HeapString.AppendString[dataName, S[".data"L]];
  handle ← VMDefs.OpenFile[options: oldOrNew, name: dataName, cacheFraction: 20];
  VMDefs.SetFileLength[handle, [page: fileSize, byte: 0]];
  segmentCount ← Inline.LowHalf[fileSize / segmentSize];
  FOR page IN [0..fileSize) DO EmptyPage[[handle, page]] ENDLOOP;
  VMDefs.WaitFile[handle];
  -- firstFree not yet initialised --
  -- firstWritten not yet initialised --
  HeapString.Free[@dataName];
  END;
    
RestartStableStorage: PROCEDURE [client: Client] =
  BEGIN OPEN client, ObjectDirInternal, StableStorageImpl;
  dataName: NSString.String ← NSString.nullString;
  dataName ← HeapString.AppendString[dataName, name];
  dataName ← HeapString.AppendString[dataName, S[".data"L]];
  handle ← VMDefs.OpenFile[options: old, name: dataName, cacheFraction: 20];
  segmentCount ← Inline.LowHalf[VMDefs.GetFileLength[handle].page / segmentSize];
  -- firstFree not yet initialised --
  -- firstWritten not yet initialised --
  HeapString.Free[@dataName];
  END;


InitObjectDir: ENTRY PROCEDURE [client: Client] =
  BEGIN
  OPEN client, ObjectDirImpl, ObjectDirInternal;
  fileName: NSString.String ← NSString.nullString;
  fileName ← HeapString.AppendString[fileName, name];
  fileName ← HeapString.AppendString[fileName, S[".objectDir"L]];
  dirHandle ← VMDefs.OpenFile[
    options: oldOrNew, name: fileName, cacheFraction: 5];
  nextVirginPage ← Inline.LowHalf[VMDefs.GetFileLength[dirHandle].page];
  IF nextVirginPage = FIRST[VMDefs.PageNumber] THEN
    nextVirginPage ← nextVirginPage + 1;
  -- initialize each page with unchained "free" indexes,
  -- and put each page on free page chain.
  firstFreePage ← endOfChain;
  FOR reqd: CARDINAL IN [0..nextVirginPage) DO
    IF dpPage # NIL THEN VMDefs.Release[LOOPHOLE[dpPage, VMDefs.Page]];
    dpPage ← LOOPHOLE[VMDefs.UsePage[[dirHandle, dpNumber ← reqd]]];
    FOR index: DirIndex IN DirIndex DO
      dpPage.data[index] ← [free[next: 0, dopc: 0]] ENDLOOP;
    dpPage.header ← [nextFreePage: unchained, nextFreeIndex: noFreeIndex];
    VMDefs.MarkStart[LOOPHOLE[dpPage, VMDefs.Page]];  --may cause file extension for page 0--
    ReleaseData[dirty, client];
    ENDLOOP;
  HeapString.Free[@fileName];
  END;


ReadSegments: PROCEDURE [client: Client] =
  BEGIN OPEN client, StableStorageImpl, ObjectDirInternal;
  page: VMDefs.Page;
  usedSegment: SegmentIndex ← lastChained ← chain.header[0].chainHead;
  pos: VMDefs.FullAddress ← Address[lastChained, client];
  usedPos: VMDefs.FullAddress ← pos;
  offset: Object.Offset;
  current: ObjectDirInternal.ObjectNumber ← ObjectDirInternal.gapObjectNumber;

  DefineObject: ENTRY PROCEDURE
    [obj: ObjectDirInternal.ObjectNumber, client: Client] =
    BEGIN OPEN client, ObjectDirImpl;
    data: LONG POINTER TO DirData = FindData[obj, client];
    data↑ ← [
      used[
      page: pos.page.page, word: pos.word, type: obj.type, count: zeroCount]];
    ReleaseData[dirty, client];
    END;

  DefineObject[ObjectDirInternal.gapObjectNumber, client];

  DO  -- Consider any page header --
    IF pos.word = FIRST[VMDefs.PageIndex] THEN
      BEGIN
      pageHead: LONG POINTER TO ObjectInternal.PageHeader;
      page ← VMDefs.ReadPage[pos.page, 2];
      pageHead ← LOOPHOLE[page, LONG POINTER] + pos.word;
      pos.word ← pos.word + SIZE[ObjectInternal.PageHeader];
      offset ← pageHead.offset;
      END
    ELSE offset ← Object.objectStart;
    BEGIN
    object: LONG POINTER TO ObjectInternal.ObjectHeader =
      LOOPHOLE[page, LONG POINTER] + pos.word;

    -- check for non-empty segment --
    IF object.number # ObjectDirInternal.gapObjectNumber THEN
      BEGIN usedPos ← pos; usedSegment ← lastChained; END;

    IF
      (current # ObjectDirInternal.gapObjectNumber
        --Inside an object, looking for continuation sub-object--
        -- If a duplicate start is found for an object, then the
        -- later start is chosen, so that all earlier starts are
        -- overwritten before the object number is freed by the
        -- compactor.  Otherwise, confusion could arise by the
        -- object number being re-used and a restart occuring
        -- before the duplicate start has been overwritten.
        AND object.number = current AND offset = Object.objectStart)
      OR
        (object.number # ObjectDirInternal.gapObjectNumber
          AND offset = Object.objectStart) THEN
      BEGIN  -- start of a new object --
      DefineObject[object.number, client];
      current ← object.number;
      END
    -- ELSE we have one of:
    --         continuation of ignorable object,
    --         imbedded object which should be ignored,
    --         unexpected partial object,
    --         gap object -- ;
    pos.word ← pos.word + SIZE[ObjectInternal.ObjectHeader];
    pos.word ← pos.word + object.size;
    END;
    IF pos.word + SIZE[ObjectInternal.ObjectHeader] > LAST[VMDefs.PageIndex]
      THEN
      BEGIN
      VMDefs.Release[page];
      -- similar to StableStorage.NextPage, but different --
      IF pos.page.page MOD segmentSize < segmentSize - 1 THEN
        BEGIN
        pos.page.page ← pos.page.page + 1;
        pos.word ← FIRST[VMDefs.PageIndex];
        END
      ELSE
        IF chain.next[lastChained] # noSegment THEN
          BEGIN
          old: SegmentIndex = lastChained;
          pos ← Address[lastChained ← chain.next[old], client];
          IF usedSegment # old THEN
            BEGIN  -- 'old' is entirely empty
            chain.next[usedSegment] ← lastChained;
            AddToFreeList[old, client];
            END
          END
        ELSE EXIT -- note "pos" is last page in written chain ;
      END
    ELSE  -- end of any current object
      current ← ObjectDirInternal.gapObjectNumber;
    ENDLOOP;

  lastPage ← pos.page;
  lastWritten ← lastChained;

  END;


TidyObjectDir: ENTRY PROCEDURE [client: Client] =
  -- Construct object directory page free chains --
  -- Add pages with non-empty free chains to page free chain
  BEGIN OPEN client, ObjectDirImpl, ObjectDirInternal;
  FOR p: ObjDirPageNumber DECREASING IN [0..nextVirginPage) DO
    GetDirPage[p, client];
    dpPage.header.nextFreeIndex ← noFreeIndex;
    FOR index: DirIndex IN DirIndex DO
      WITH data: dpPage.data[index] SELECT FROM
        free =>
          BEGIN
          dpPage.data[index] ← [
            free[next: dpPage.header.nextFreeIndex, dopc: lastDofpc]];
          dpPage.header.nextFreeIndex ← index;
          END;
        ENDCASE => NULL;
      ENDLOOP;
    IF dpPage.header.nextFreeIndex # noFreeIndex THEN {
      dpPage.header.nextFreePage ← firstFreePage; firstFreePage ← dpNumber};
    ReleaseData[dirty, client];
    ENDLOOP;
  END;


InitClient: PUBLIC PROCEDURE
  [clientName: NSString.String, fileSize: LONG CARDINAL, log: ServerLog.Handle,
  zone: UNCOUNTED ZONE]
  RETURNS [client: Client] =
  BEGIN
  s: NSString.String ← NSString.nullString;
  client ← lz.NEW[ObjectDirInternal.ClientObject];
  client.log ← log;
  client.name ← NSString.nullString;
  client.zone ← zone;
  client.name ← HeapString.AppendString[client.name, clientName];
  client.name ← HeapString.Pickle[client.name];
  s ← HeapString.AppendString[s, S["Initializing Object Store for client "L]];
  s ← HeapString.AppendString[s, clientName];
  ServerLog.WriteLogEntry[s, client.log];
  -- ??? NSCommand.PutAsyncMessage[s, msID];
  HeapString.Free[@s];
  InitStableStorage[client, fileSize];
  InitChain[client];
  InitObjectDir[client];
  ReadSegments[client];
  TidyObjectDir[client];
  END;

RestartClient: PUBLIC PROCEDURE
  [clientName: NSString.String, log: ServerLog.Handle, zone: UNCOUNTED ZONE]
  RETURNS [client: Client] =
  BEGIN
  s: NSString.String ← NSString.nullString;
  client ← lz.NEW[ObjectDirInternal.ClientObject];
  client.log ← log;
  client.name ← NSString.nullString;
  client.zone ← zone;
  client.name ← HeapString.AppendString[client.name, clientName];
  client.name ← HeapString.Pickle[client.name];
  s ← HeapString.AppendString[s, S["Restarting Object Store for client "L]];
  s ← HeapString.AppendString[s, clientName];
  -- ??? NSCommand.PutAsyncMessage[s, msID];
  HeapString.Free[@s];
  RestartStableStorage[client];
  ReadChain[client];
  InitObjectDir[client];
  ReadSegments[client];
  TidyObjectDir[client];
  END;

END.