-- VolAllocMapImpl.mesa (last edited by: Jose on: March 23, 1981  5:28 PM)  

DIRECTORY
  Environment USING [bitsPerWord, wordsPerPage],
  File USING [Capability, ID, PageNumber, Type],
  FileInternal USING [Descriptor, FilePtr, maxPermissions, PageGroup],
  FMPrograms,
  Inline USING [BITAND, BITSHIFT, BITXOR],
  LabelTransfer USING [VerifyLabels, WriteLabels],
  LogicalVolume USING [Free, Handle, PageNumber, Vam, Vfm],
  PhysicalVolume USING [ErrorType],
  PilotFileTypes USING [PilotVFileType, tFreePage],
  SimpleSpace USING [Create, ForceOut, Map, Page, Unmap],
  Space USING [Handle, WindowOrigin],
  Utilities USING [LongPointerFromPage, ShortCARDINAL],
  VolAllocMap,
  Volume USING [ID, nullID];

VolAllocMapImpl: MONITOR -- to protect VAM buffer; volume page is protected by callers
  IMPORTS Inline, LabelTransfer, LogicalVolume, SimpleSpace, Utilities
  EXPORTS FMPrograms, PhysicalVolume, VolAllocMap
  SHARES File =
  BEGIN OPEN Inline;
  -- The following is exported to PhysicalVolume as compiler "features" prevent it from
  -- being exported by PhysicalVolumeImpl.
  Error: PUBLIC ERROR [PhysicalVolume.ErrorType] = CODE;

  FilePtr: TYPE = FileInternal.FilePtr;
  GroupPtr: TYPE = POINTER TO FileInternal.PageGroup;
  LvHandle: TYPE = LogicalVolume.Handle;
  -- common buffer used by procedures to Access VAM; protected by monitor
  bufferHandle: Space.Handle = SimpleSpace.Create[1, hyperspace];
  bufferPointer: LONG POINTER = Utilities.LongPointerFromPage[
    SimpleSpace.Page[bufferHandle]];
  -- Whenever lvID#Volume.nullID, the buffer is (being) mapped.   Therefore, if the ID is
  -- wrong and non-null, one can automatically unmap.  See Close, MapInternal.
  currentBufferContents: RECORD [
    lvID: Volume.ID, page: LogicalVolume.PageNumber] ← [Volume.nullID, ];
  bitsPerPage: CARDINAL = (Environment.bitsPerWord*Environment.wordsPerPage);
  VolAllocMapImplError: ERROR [{setSizeToZeroPageGroup}] = CODE;

  -- This code assumes various powers of 2 (because of the truncation).
  AccessVAM: PUBLIC PROCEDURE [
    vol: LvHandle, volumePage: LogicalVolume.PageNumber, set: BOOLEAN,
    clear: BOOLEAN] RETURNS [busy: BOOLEAN] =
    -- set, clear or read one bit of vam, always returns the (previous) value;
    BEGIN
    bit: WORD;
    vamPage: LogicalVolume.PageNumber;
    word: LONG POINTER TO WORD;
    DoIt: ENTRY PROCEDURE = INLINE
      BEGIN
      MapVamPageInternal[vol.vID, vamPage, Vam[vol]];
      busy ← (0 # Inline.BITAND[word↑, bit]);
      IF (busy AND clear) OR (~busy AND set) THEN
         BEGIN
         word↑ ← Inline.BITXOR[word↑, bit];
         vol.freePageCount ← vol.freePageCount + (IF busy THEN 1 ELSE -1);
         END;
      IF clear THEN vol.lowerBound ← MIN[volumePage, vol.lowerBound];
      -- keep lowerBound to help allocator 
      IF set AND volumePage = vol.lowerBound THEN vol.lowerBound ← vol.lowerBound+1;
      END;
    -- The bit and word calculations can correctly be done by truncating first
    bit ← Inline.BITSHIFT[
      1, Utilities.ShortCARDINAL[volumePage] MOD Environment.bitsPerWord];
    word ←
      bufferPointer +
   (Utilities.ShortCARDINAL[volumePage]/Environment.bitsPerWord) MOD
     Environment.wordsPerPage;
    vamPage ← vol.vamStart + volumePage/bitsPerPage;
    DoIt[];
    END;

  -- Allocate a group of pages and write labels (group is a modifiable hint)

  AllocPageGroup: PUBLIC PROCEDURE [
    vol: LvHandle, filePtr: FilePtr, groupPtr: GroupPtr, createFile: BOOLEAN] =
    BEGIN OPEN groupPtr;
    skip: [0..1] = IF ~createFile AND filePage = 0 THEN 1 ELSE 0;
    offset: LONG CARDINAL;
    startPage: LogicalVolume.PageNumber = MAX[volumePage, vol.lowerBound];
    IF createFile OR filePage # 0 THEN
      BEGIN
      volumePage ← startPage;
      WHILE AccessVAM[vol, volumePage, TRUE, FALSE] DO
         --search for unbusy page
         IF (volumePage ← volumePage + 1) >= vol.volumeSize - 1 THEN
           volumePage ← vol.lowerBound;
         -- avoid the following error rather thatn backing out
         IF volumePage = startPage THEN ERROR; --Volume.InsufficientSpace--
         ENDLOOP;
      END;
    FOR offset ← 0, offset + 1 WHILE offset < nextFilePage - filePage DO
      IF volumePage + offset >= vol.volumeSize THEN EXIT;
      IF offset # 0 THEN
         IF AccessVAM[vol, volumePage + offset, TRUE, FALSE] THEN EXIT; --first is set
      ENDLOOP;
    WITH filePtr SELECT FROM
      local =>
         IF type IN PilotFileTypes.PilotVFileType THEN filePage ← volumePage;
      ENDCASE;
    nextFilePage ← filePage + offset; -- side effect
    IF createFile AND nextFilePage = 0 -- creating only the attribute label page
      THEN offset ← 1;
    [] ← LabelTransfer.VerifyLabels[
      filePtr↑, FileInternal.PageGroup[0, volumePage, skip], FALSE, TRUE];
    [] ← LabelTransfer.VerifyLabels[
      FreeDescriptor[vol],
      [volumePage + skip, volumePage + skip, volumePage + offset],
      FALSE, TRUE];
    -- set new file size
    WITH filePtr SELECT FROM local => size ← nextFilePage; ENDCASE => ERROR;
    [] ← LabelTransfer.WriteLabels[
      filePtr↑, [filePage, volumePage, filePage + offset]];
    END;

  FreeDescriptor: PROCEDURE [v: LvHandle] RETURNS [FileInternal.Descriptor] =
    INLINE
    BEGIN
    RETURN[
      [LogicalVolume.Free[v], v.vID,
       local[FALSE, FALSE, v.volumeSize, PilotFileTypes.tFreePage]]];
    END;

  -- free a page group (assumes calls to label operations with zero sizes are noops)
  -- This is closely related to SetSize and Delete in FileImpl, as well as VolFileMap.DeletePageGroup

  FreePageGroup: PUBLIC PROCEDURE [
    vol: LvHandle, filePtr: FilePtr, groupPtr: GroupPtr, deleteFile: BOOLEAN] =
    BEGIN
    Blast: PROCEDURE [g: FileInternal.PageGroup] =
      BEGIN
      p, nextVolumePage: LogicalVolume.PageNumber;
      nextVolumePage ← g.volumePage + g.nextFilePage - g.filePage;
      FOR p ← g.volumePage, p + 1 WHILE p < nextVolumePage DO
         [] ← AccessVAM[vol, p, FALSE, TRUE];
      ENDLOOP;
      [] ← LabelTransfer.WriteLabels[
         FreeDescriptor[vol], [g.volumePage, g.volumePage, nextVolumePage]];
      END; -- Always verify labels; max is for zero size file [0, ?, 0]
    [] ← LabelTransfer.VerifyLabels[
      filePtr↑,
      [groupPtr.filePage, groupPtr.volumePage,
       MAX[groupPtr.nextFilePage, 1]], FALSE, TRUE];
    WITH filePtr SELECT FROM local => size ← groupPtr.filePage; ENDCASE => ERROR;
    SELECT TRUE FROM
      groupPtr.filePage # 0 => -- if group start#0, always blast
         Blast[groupPtr↑];
      groupPtr.nextFilePage # 0 -- AND g.filePage=0-- => -- Set to zero size
         BEGIN
         Blast[
           [groupPtr.filePage + 1, groupPtr.volumePage + 1,
             groupPtr.nextFilePage]];
         -- we could skip the write, but then we'd have to mess around with correcting
         -- zero size files
         [] ← LabelTransfer.WriteLabels[filePtr↑, [0, groupPtr.volumePage, 1]];
         END;
      ENDCASE -- g.filePage=g.nextPage=0-- =>
         IF deleteFile THEN Blast[[0, groupPtr.volumePage, 1]] --delete it--
         ELSE ERROR VolAllocMapImplError[setSizeToZeroPageGroup];
    END;

  Vam: PROCEDURE [v: LvHandle] RETURNS [File.ID] = INLINE
    { RETURN[LogicalVolume.Vam[v]]; };

  Vfm: PROCEDURE [v: LvHandle] RETURNS [File.ID] = INLINE
    { RETURN[LogicalVolume.Vfm[v]]; };

  -- ENTRY PROCEDURES (Also see local proc in AccessVAM)
  -- Force out/ deallocate allocation map

  Close: PUBLIC ENTRY PROCEDURE [final: BOOLEAN] =
    BEGIN
    IF currentBufferContents.lvID # Volume.nullID THEN
      IF final THEN
         BEGIN
         SimpleSpace.Unmap[bufferHandle];
         currentBufferContents.lvID ← Volume.nullID;
         END
      ELSE SimpleSpace.ForceOut[bufferHandle];
    END;

  -- INTERNAL PROCEDURE

  MapVamPageInternal: INTERNAL PROCEDURE [
    vID: Volume.ID, vamPage: LogicalVolume.PageNumber, vamID: File.ID] = INLINE
    BEGIN
    IF currentBufferContents.lvID # vID THEN
      BEGIN
      IF currentBufferContents.lvID # Volume.nullID THEN
         SimpleSpace.Unmap[bufferHandle];
      currentBufferContents.lvID ← vID; -- and fall through for map...
      END
    ELSE
      IF currentBufferContents.page # vamPage THEN SimpleSpace.Unmap[bufferHandle]
   -- and fall through for map...

      ELSE -- vids equal and pages equal, so just.... -- RETURN;
    currentBufferContents.page ← vamPage;
    SimpleSpace.Map[
      bufferHandle, Space.WindowOrigin[
      File.Capability[vamID, FileInternal.maxPermissions], vamPage], FALSE];
    END;

  END.
LOG
Time: April 13, 1978  3:32 PM   By: Purcell   Action: Created file
Time: June 23, 1978  12:27 PM   By: Purcell   Action: page 0 special case in alloc/free group
Time: September 27, 1978  5:15 PM   By: Purcell   Action: don't raise signals and limit bound
Time: October 19, 1978  2:45 PM   By: Purcell   Action: CR 20.103: Use FileTypes directly
Time: March 20, 1979  9:30 PM   By: Redell   Action: Convert to Mesa 5.0
Time: August 1, 1979  3:10 PM   By: Redell   Action: Convert to use FilePageLabel
Time: August 8, 1979  12:05 PM   By: Redell   Action: Bug fix: wraparound condition in AllocPageGroup was off by one: ran off end of volume.
Time: August 29, 1979  10:05 AM   By: Forrest   Action: Changed to match new interface and new FileMgr organization.
Time: September 3, 1979  12:08 PM   By: Forrest   Action: Used LogicalVolume.free, vam, vfm vs Opens.
Time: September 4, 1979  11:04 AM   By: Forrest   Action: Fixed up buffer allocation/deallocation.
Time: October 1, 1979  5:56 PM   By: Forrest   Action: Moved out Open to scavanger.
Time: January 9, 1980  5:43 PM   By: Gobbel   Action: Change for new LabelTransfer interface: VerifyLabels now returns two values.
Time: March 7, 1980  8:49 PM   By: Forrest   Action: Lexically change types for New Logical Volume; deleted handle from Close.
Time: June 17, 1980  10:12 AM   By: Luniewski   Action: Exprt PhysicalVolume.Error.
Time: January 12, 1981  1:37 PM   By: Luniewski   Action: New LabelTransfer interface.
Time: March 23, 1981  5:28 PM   By: Jose   Action: Remove signal LabelError, remove handling of error from VerifyLabels.