-- VolumeImpl.mesa (last edited by: Levin on: August 26, 1982 9:14 am)

DIRECTORY
  Boot USING [Location, LVBootFiles, nullDiskFileID, VolumeType],
  Device USING [Type],
  DiskChannel USING [DiskPageCount, Drive, GetAttributes, GetDriveAttributes],
  Environment USING [wordsPerPage],
  File USING [
    Capability, ID, nullCapability, nullID, PageCount, PageNumber, Permissions,
    Type],
  FileCache USING [
    FlushFile, FlushFilesOnVolume, GetFilePtrs, PinnedAction, SetFile,
    SetPageGroup],
  FileInternal USING [Descriptor, maxPermissions, PageGroup],
  FMPrograms USING [],
  Inline USING [LowHalf],
  KernelFile USING [],
  LabelTransfer USING [ReadLabel, WriteLabels],
  LogicalVolume USING [
    CloseVolumeAndFlushFiles, currentVersion, EraseVolume, Handle, nullBoot, OpenStatus,
    OpenVolumeAndDeleteTemps, PageNumber, ReadOnlyVolume, rootPageNumber,
    ScavengeContext, ScavengeVolume, seal, SetFree, SetVam, SetVfm, VolumeAccess,
    VolumeAccessProc, VolumeAccessStatus],
  MarkerPage USING [CreateMarkerPage, Find, UpdateLogicalMarkerPages],
  PhysicalVolume USING [Error, ErrorType, ID, PageNumber],
  PhysicalVolumeFormat USING [
    descriptorSize, Handle, maxSubVols, PageCount, PageNumber,
    rootPageNumber, SubVolumeDesc],
  PilotFileTypes USING [
    PilotRootFileType, tFreePage, tLogicalVolumeRootPage, tVolumeAllocationMap,
    tVolumeFileMap],
  PilotDisk USING [GetLabelFilePage, GetLabelType, Label],
  PilotSwitches USING [switches--k,u,z--],
  Process USING [InitializeCondition, Ticks],
  Scavenger USING [Error, ErrorType, Scavenge],
  SimpleSpace USING [Create, ForceOut, Handle, Map, Page, Unmap],
  SpecialVolume USING [],
  SubVolume USING [Find, Handle, OnLine],
  System USING [GetUniversalID, NetworkAddress, UniversalID],
  Utilities USING [LongPointerFromPage],
  VolAllocMap USING [Close],
  VolFileMap USING [Close],
  Volume USING [
    ID, NeedsScavenging, NotOpen, nullID, onlyEnumerateCurrentType, Open,
    PageCount, Status, systemID, Type, TypeSet, Unknown],
  VolumeExtras USING [],
  VolumeImplInterface USING [AccessPhysicalVolumeRootPage],
  VolumeInternal USING [PageNumber];

VolumeImpl: MONITOR [ -- to protect activeVolume and LVT
  bootFile: LONG POINTER TO disk Boot.Location,
  pLVBootFiles: POINTER TO Boot.LVBootFiles,
  debuggerDeviceType: POINTER TO Device.Type,
  debuggerDeviceOrdinal: POINTER TO CARDINAL]
  IMPORTS
    DiskChannel, FileCache, PilotDisk, Inline, LabelTransfer, LogicalVolume,
    MarkerPage, PhysicalVolume, PilotSwitches, Process, Scavenger, SimpleSpace,
    SubVolume, System, Utilities, VolAllocMap, VolFileMap, Volume,
    VolumeImplInterface
  EXPORTS
    FMPrograms, KernelFile, LogicalVolume, SpecialVolume, Volume,
    VolumeExtras, VolumeImplInterface
  SHARES File =
  BEGIN
  LvHandle: TYPE = LogicalVolume.Handle;
  PvHandle: TYPE = PhysicalVolumeFormat.Handle;
  scavengingComplete: CONDITION;
  debugClass: Boot.VolumeType;
  debugSearchState: {tooSoonToLook, looking, done} ← tooSoonToLook;
  lvRootPage: LogicalVolume.PageNumber = LogicalVolume.rootPageNumber;
  pvRootPage: PhysicalVolumeFormat.PageNumber =
    PhysicalVolumeFormat.rootPageNumber;

  -- The following is used as a buffer for the scavenger.  It must be large enough to hold
  -- the largest possible physical volume root page+bad page list
  extraBuffer: SimpleSpace.Handle = SimpleSpace.Create[
      PhysicalVolumeFormat.descriptorSize/Environment.wordsPerPage, hyperspace, 1];

  -- The "active volume" whose LogicalVolume.Descriptor (root page) is buffered in memory
  logicalRootPageBuffer: SimpleSpace.Handle = SimpleSpace.Create[1, hyperspace];
  activeVolume: LvHandle = Utilities.LongPointerFromPage[
    SimpleSpace.Page[logicalRootPageBuffer]];
  activeVID: Volume.ID ← Volume.nullID;
  -- active volume (nullID => no active volume)

  -- Logical Volume Table (lists logical volumes which are on-line)
  LVTHandle: TYPE = --LONG--POINTER TO Volume.ID;
  VolumeStatus: TYPE = RECORD [
    lvID: Volume.ID,
    beingScavenged, onLine, open, readOnly: BOOLEAN,
    -- readOnly is a user settable property of the volume.  A volume is read-only if readOnly is TRUE OR if the volume is of a "higher" type than the system volume.
    type: Volume.Type,
    pieceCount: CARDINAL [0..777B)];
  nullVolumeStatus: VolumeStatus =
    [Volume.nullID, FALSE, FALSE, FALSE, FALSE, nonPilot, 0];
  LVT: ARRAY LVTIndex OF VolumeStatus ← ALL[nullVolumeStatus];
  LVTIndex: TYPE = [0..maxLVs);
  maxLVs: CARDINAL = 12; -- Maximum logical volumes allowed on-line at once
  VolImplError: ERROR [{
    impossibleSelectError, impossibleSubvolumeNotFound, notFoundInLVT,
    lvTableFull, illegalReturnCode}] = CODE;

  -- Volume

  systemID: PUBLIC Volume.ID ← Volume.nullID;
  InsufficientSpace: PUBLIC ERROR = CODE;
  NeedsScavenging: PUBLIC ERROR = CODE;
  NotOpen: PUBLIC ERROR [volume: Volume.ID] = CODE;
  Unknown: PUBLIC ERROR [volume: Volume.ID] = CODE;

  Close: PUBLIC PROCEDURE [volume: Volume.ID] =
      { LogicalVolume.CloseVolumeAndFlushFiles[@volume] };
    
  GetAttributes: PUBLIC SAFE PROCEDURE [volume: Volume.ID]
    RETURNS [
      volumeSize, freePageCount: Volume.PageCount, rootFile: File.Capability] = TRUSTED
    BEGIN
    GetAttributes1: PROCEDURE [vol: LvHandle] =
      BEGIN
      volumeSize ← vol.volumeSize;
      freePageCount ← vol.freePageCount;
      rootFile ← vol.clientRootFile;
      END;
    GetLogicalRootPage[@volume, GetAttributes1];
    END;
    
  GetLabelString: PUBLIC PROCEDURE [volume: Volume.ID, s: STRING] =
    BEGIN
    badVersion: BOOLEAN;
    GetLabelString1: PROCEDURE [vol: LvHandle] =
      BEGIN
      i: CARDINAL;
      s.length ← 0;
      IF badVersion ← (vol.version # LogicalVolume.currentVersion) THEN RETURN;
      s.length ← MIN[s.maxlength, vol.labelLength, LENGTH[vol.label]];
      FOR i IN [0..s.length) DO s[i] ← vol.label[i]; ENDLOOP;
      END;
    IF s # NIL THEN
      BEGIN
      GetLogicalRootPage[@volume, GetLabelString1];
      IF badVersion THEN ERROR Volume.NeedsScavenging;
      END;
    END;
    
  GetNext: PUBLIC ENTRY SAFE PROCEDURE [
    volume: Volume.ID, includeWhichVolumes: Volume.TypeSet ← []]
    RETURNS [Volume.ID] = TRUSTED
    BEGIN
    volumeFound: BOOLEAN ← volume = Volume.nullID;
    lv: LVTIndex;
    systemVolumeType: Volume.Type = LVGetStatus[Volume.systemID].type;
    IF IsUtilityPilot[] THEN
       includeWhichVolumes ← [normal: TRUE, debugger: TRUE, debuggerDebugger: TRUE]
    ELSE IF includeWhichVolumes = Volume.onlyEnumerateCurrentType THEN
       includeWhichVolumes[systemVolumeType] ← TRUE; -- all others are already FALSE
    FOR lv IN LVTIndex DO
      IF volumeFound AND LVT[lv].lvID # Volume.nullID
         AND LVT[lv].onLine AND includeWhichVolumes[LVT[lv].type] THEN
         RETURN[LVT[lv].lvID];
      IF LVT[lv].lvID = volume THEN volumeFound ← TRUE;
      ENDLOOP;
    IF volumeFound THEN RETURN[Volume.nullID]
    ELSE RETURN WITH ERROR Volume.Unknown[volume];
    END;
    
  GetStatus: PUBLIC ENTRY SAFE PROCEDURE [vID: Volume.ID]
    RETURNS [status: Volume.Status] = TRUSTED
    BEGIN
    onLine, open: BOOLEAN;
    IF vID = Volume.nullID OR ~LogicalVolumeFind[@vID] THEN RETURN[unknown];
    [onLine: onLine, open: open] ← LVGetStatus[vID];
    IF ~onLine THEN status ← partiallyOnLine
    ELSE IF open THEN status ← open
    ELSE
      BEGIN -- only activate when absolutely necessary!
      Activate[vID];
      IF activeVolume.changing THEN status ← closedAndInconsistent
      ELSE status ← closedAndConsistent;
      END;
    END;
    
  GetType: PUBLIC SAFE PROCEDURE [lvID: Volume.ID] RETURNS [type: Volume.Type] = TRUSTED
    BEGIN
    GetTypeInner: PROCEDURE [vol: LvHandle] = {type ← vol.type};
    GetLogicalRootPage[@lvID, GetTypeInner];
    END;
    
  IsOnServer: PUBLIC SAFE PROCEDURE [
    volume: Volume.ID, netAddress: System.NetworkAddress] = TRUSTED
    {--not implemented--};
    
  Open: PUBLIC SAFE PROCEDURE [volume: Volume.ID] = TRUSTED
      BEGIN
      DeleteTemps: ENTRY PROCEDURE RETURNS[doDelete: BOOLEAN] = INLINE
         BEGIN
         IF volume = Volume.nullID OR ~LogicalVolumeFind[@volume] THEN
            RETURN WITH ERROR Volume.Unknown[volume];
         IF LVGetStatus[volume].type <= debugClass -- type of system volume -- THEN
           RETURN[TRUE]
         ELSE RETURN[FALSE];
         END;

      IF ~DeleteTemps[] THEN
           SELECT OpenLogicalVolume[@volume] FROM
              ok, wasOpen => NULL;
              Unknown => ERROR Volume.Unknown[volume];
              VolumeNeedsScavenging => ERROR Volume.NeedsScavenging;
           ENDCASE => ERROR
      ELSE LogicalVolume.OpenVolumeAndDeleteTemps[@volume];
      END;
    
  SetRootFile: PUBLIC PROCEDURE [volume: Volume.ID, file: File.Capability] =
    BEGIN
    SetRootFile1: LogicalVolume.VolumeAccessProc =
      BEGIN
      volume.clientRootFile ← file;
      updateMarkers ← TRUE;
      END;
    SignalVolumeAccess[@volume, SetRootFile1];
    END;

  --
  -- VolumeExtras
  OpenVolume: PUBLIC PROCEDURE [volume: Volume.ID, readOnly: BOOLEAN] =
      BEGIN
      DeleteTemps: ENTRY PROCEDURE RETURNS[doDelete: BOOLEAN] = INLINE
         BEGIN
         IF volume = Volume.nullID OR ~LogicalVolumeFind[@volume] THEN
            RETURN WITH ERROR Volume.Unknown[volume];
         IF readOnly THEN RETURN[FALSE];
         IF LVGetStatus[volume].type <= debugClass -- type of system volume -- THEN
           RETURN[TRUE]
         ELSE RETURN[FALSE];
         END;

      IF ~DeleteTemps[] THEN
           SELECT OpenLogicalVolumeInternal[@volume, readOnly] FROM
              ok, wasOpen => NULL;
              Unknown => ERROR Volume.Unknown[volume];
              VolumeNeedsScavenging => ERROR Volume.NeedsScavenging;
           ENDCASE => ERROR
      ELSE LogicalVolume.OpenVolumeAndDeleteTemps[@volume];
      END;
    
  --
  -- SpecialVolume

  GetLogicalVolumeBootFiles: PUBLIC PROCEDURE [
    lvID: Volume.ID, pBootFiles: LONG POINTER TO Boot.LVBootFiles] =
    BEGIN
    GetLVBootFiles1: PROCEDURE [vol: LvHandle] =
       { pBootFiles↑ ← vol.bootingInfo };
    IF pBootFiles # NIL THEN GetLogicalRootPage[@lvID, GetLVBootFiles1];
    END;
    
  SetLogicalVolumeBootFiles: PUBLIC PROCEDURE [
    lvID: Volume.ID, pBootFiles: LONG POINTER TO Boot.LVBootFiles] =
    BEGIN
    SetLVBootFiles1: LogicalVolume.VolumeAccessProc =
      BEGIN
      volume.bootingInfo ← pBootFiles↑;
      updateMarkers ← TRUE;
      END;
    IF pBootFiles # NIL THEN SignalVolumeAccess[@lvID, SetLVBootFiles1];
    END;
    
  --
  -- KernelFile

  GetRootFile: PUBLIC PROCEDURE [type: File.Type, volume: Volume.ID]
    RETURNS [file: File.Capability] =
    BEGIN
    GetRootFile1: PROCEDURE [vol: LvHandle] = {
      file ← [vol.rootFileID[type], FileInternal.maxPermissions]};
    IF type NOT IN PilotFileTypes.PilotRootFileType THEN ERROR;
    GetLogicalRootPage[@volume, GetRootFile1];
    IF file.fID = File.nullCapability.fID THEN file ← File.nullCapability;
    END;
    
  PutRootFile: PUBLIC PROCEDURE [
    pVID: POINTER TO READONLY Volume.ID, type: File.Type,
    file: POINTER TO READONLY File.Capability] =
    BEGIN
    PutProc: LogicalVolume.VolumeAccessProc =
      { volume.rootFileID[type] ← file.fID; updateMarkers ← TRUE };
    IF type IN PilotFileTypes.PilotRootFileType THEN
      SignalVolumeAccess[pVID, PutProc];
    END;
    
  --
  -- LogicalVolume

  ReadOnlyVolume: PUBLIC ERROR = CODE;
  BeginScavenging: PUBLIC ENTRY PROCEDURE [pVID: POINTER TO READONLY Volume.ID,
      context: LogicalVolume.ScavengeContext] =
    BEGIN
    theError: Scavenger.ErrorType;
    BEGIN
    IF pVID↑ = Volume.nullID OR ~LogicalVolumeFind[pVID] THEN
      RETURN WITH ERROR Unknown[pVID↑];
    IF LVGetStatus[pVID↑].open THEN RETURN WITH ERROR Scavenger.Error[volumeOpen];
    IF IsReadOnly[pVID↑] THEN RETURN WITH ERROR LogicalVolume.ReadOnlyVolume;
    Activate[pVID↑];
    activeVolume.changing ← TRUE;
    SimpleSpace.ForceOut[logicalRootPageBuffer];
    LVSetScavenged[pVID↑, TRUE];
    LogicalVolume.ScavengeVolume[activeVolume, extraBuffer, context !
      Scavenger.Error => {theError ← error; GOTO ScavengerError}];
    VolFileMap.Close[TRUE];
    VolAllocMap.Close[TRUE];
    activeVolume.changing ← FALSE;
    Deactivate[];
    EXITS
    ScavengerError => RETURN WITH ERROR Scavenger.Error[theError];
    END;
    END;
    
  CloseLogicalVolume: PUBLIC ENTRY PROCEDURE [
    pVID: POINTER TO READONLY Volume.ID] =
    BEGIN
    IF pVID↑ = Volume.nullID OR ~LogicalVolumeFind[pVID] THEN
      RETURN WITH ERROR Unknown[pVID↑];
    Activate[pVID↑];
    FileCache.FlushFilesOnVolume[pVID↑, keep];
    -- Let all I/O on the volume quiesce
    VolFileMap.Close[TRUE];
    VolAllocMap.Close[TRUE];
    FileCache.FlushFilesOnVolume[pVID↑, keep];
    -- Wait for any I/O that slipped in through the cracks to finish
    LVSetOpen[pVID↑, FALSE];
    Deactivate[];
    END;
    
  EndScavenging: PUBLIC ENTRY PROCEDURE [pVID: POINTER TO READONLY Volume.ID] =
     { LVSetScavenged[pVID↑, FALSE]; BROADCAST scavengingComplete };
    
  GetContainingPhysicalVolume: PUBLIC PROCEDURE [lvID: Volume.ID]
    RETURNS [pvID: PhysicalVolume.ID] =
    BEGIN
    RETURN[
      MarkerPage.Find[
   [drive[
     DiskChannel.GetAttributes[
     SubVolume.Find[lvID, lvRootPage].subVolume.channel]]]].physicalID↑]
    END;
    
  LogicalVolumeCreate: PUBLIC ENTRY PROCEDURE [
    pvID: PhysicalVolume.ID, size: Volume.PageCount, name: STRING,
    type: Volume.Type, minPVPageNumber: PhysicalVolume.PageNumber]
    RETURNS [Volume.ID] =
    BEGIN
    i: CARDINAL;
    lvID: Volume.ID ← [System.GetUniversalID[]];
    minVolumeSize: PhysicalVolumeFormat.PageCount = 50;
    bareVolumeSize: PhysicalVolumeFormat.PageCount = 1 + 1 + 6;
    thisSv: CARDINAL;
    thisSvStart: PhysicalVolumeFormat.PageNumber;
    error: PhysicalVolume.ErrorType;
    isError: BOOLEAN ← FALSE;
    proc1: PROCEDURE [p: PvHandle] =
      BEGIN
      goodPages: PhysicalVolumeFormat.PageCount ← 0;
      pg: PhysicalVolumeFormat.PageCount;
      WordsToPages: PROCEDURE [words: PhysicalVolumeFormat.PageNumber]
           RETURNS [PhysicalVolumeFormat.PageNumber] = INLINE
          { RETURN [(words+Environment.wordsPerPage-1)/Environment.wordsPerPage]};
      IsBadPage: PROCEDURE [pg: PhysicalVolumeFormat.PageNumber]
       RETURNS [BOOLEAN] =
         BEGIN
         i: CARDINAL;
         -- ASSUME that there are <= MAX[CARDINAL] bad pages.
         FOR i IN [0..Inline.LowHalf[p.badPageCount]) DO
         IF p.badPageList[i] = pg THEN RETURN[TRUE]; ENDLOOP;
         RETURN[FALSE];
         END;
      thisSv ← p.subVolumeCount;
      IF (isError ← thisSv >= PhysicalVolumeFormat.maxSubVols) THEN
         { error ← tooManySubvolumes; RETURN; };
      IF thisSv # 0 THEN
         thisSvStart ←
             p.subVolumes[thisSv - 1].pvPage + p.subVolumes[thisSv - 1].nPages + 1
      ELSE thisSvStart ← pvRootPage +
                WordsToPages[PhysicalVolumeFormat.descriptorSize];
      thisSvStart ← MAX[thisSvStart, minPVPageNumber];
      WHILE IsBadPage[thisSvStart] DO
         thisSvStart ← thisSvStart + 1;
         IF (size ← size - 1) = 0 THEN EXIT;
         ENDLOOP;
      WHILE IsBadPage[thisSvStart + size] DO
         IF (size ← size - 1) = 0 THEN EXIT; ENDLOOP;
      FOR pg ← thisSvStart, pg + 1 WHILE pg < thisSvStart + size DO
         IF ~IsBadPage[pg] THEN -- should really look for contig VAM 
            IF (goodPages ← goodPages + 1) >= bareVolumeSize THEN RETURN;
         ENDLOOP;
      error ← subvolumeHasTooManyBadPages;
      isError ← TRUE;
      END;
    proc2: PROCEDURE [p: PvHandle] =
      BEGIN
      p.subVolumes[thisSv] ← [lvID, size, 0, thisSvStart, size];
      p.subVolumeCount ← thisSv + 1;
      MarkerPage.CreateMarkerPage[p, activeVolume, thisSv];
      END;
    IF name = NIL OR name.length = 0 THEN
      RETURN WITH ERROR PhysicalVolume.Error[nameRequired];
    IF size < minVolumeSize THEN
      RETURN WITH ERROR PhysicalVolume.Error[pageCountTooSmallForVolume];
    IF ~SubVolume.Find[LOOPHOLE[pvID], pvRootPage].success THEN
      RETURN WITH ERROR PhysicalVolume.Error[physicalVolumeUnknown];
    VolumeImplInterface.AccessPhysicalVolumeRootPage[pvID, proc1];
    -- Can not raise any errors due to check in previous statement
    IF isError THEN RETURN WITH ERROR PhysicalVolume.Error[error];
    IF thisSvStart + size >= DriveSize[pvID] THEN
      RETURN WITH ERROR PhysicalVolume.Error[insufficientSpace];
    RegisterLogicalSubvolume[[lvID, size, 0, thisSvStart, size], pvID];
    [] ← LabelTransfer.WriteLabels[
      [LOOPHOLE[lvID], lvID, local[
       FALSE, FALSE, 1, PilotFileTypes.tLogicalVolumeRootPage]],
       [lvRootPage, lvRootPage, lvRootPage + 1]];
    Activate[lvID];
    activeVolume↑ ← [vID: lvID, type: type, volumeSize: size];
    LogicalVolume.SetFree[activeVolume, [System.GetUniversalID[]]];
    LogicalVolume.SetVam[activeVolume, [System.GetUniversalID[]]];
    LogicalVolume.SetVfm[activeVolume, [System.GetUniversalID[]]];
    activeVolume.labelLength ← MIN[name.length, LENGTH[activeVolume.label]];
    FOR i IN [0..activeVolume.labelLength) DO
      activeVolume.label[i] ← name[i]; ENDLOOP;
    SimpleSpace.ForceOut[logicalRootPageBuffer];
    EnterLV[lvID];
    LogicalVolumeCheck[lvID];
    IF type # nonPilot THEN
      LogicalVolume.EraseVolume[activeVolume, extraBuffer];
    SimpleSpace.ForceOut[logicalRootPageBuffer];
    VolumeImplInterface.AccessPhysicalVolumeRootPage[pvID, proc2, readWrite];
    -- Can not raise any errors due to check in previous statement
    VolFileMap.Close[TRUE];
    VolAllocMap.Close[TRUE];
    activeVolume.changing ← FALSE;
    Deactivate[];
    RETURN[lvID];
    END;
    
  LogicalVolumeErase: PUBLIC ENTRY PROCEDURE [lvID: Volume.ID] =
    BEGIN
    -- May be performed on an Open volume! (in which case, the volume is left Open)
    t: PilotFileTypes.PilotRootFileType;
    IF lvID = Volume.nullID OR ~LogicalVolumeFind[@lvID] THEN
      RETURN WITH ERROR Unknown[lvID];
    IF IsReadOnly[lvID] THEN RETURN WITH ERROR LogicalVolume.ReadOnlyVolume;
    Activate[lvID];
    activeVolume.changing ← TRUE;
    FOR t IN
      [FIRST[PilotFileTypes.PilotRootFileType]..
       LAST[PilotFileTypes.PilotRootFileType]] DO
      SELECT t FROM
         PilotFileTypes.tVolumeAllocationMap, PilotFileTypes.tVolumeFileMap,
            PilotFileTypes.tFreePage => LOOP;
         ENDCASE => activeVolume.rootFileID[t] ← File.nullID;
      ENDLOOP;
    activeVolume.clientRootFile ← File.nullCapability;
    activeVolume.bootingInfo ← LogicalVolume.nullBoot;
    SimpleSpace.ForceOut[logicalRootPageBuffer];
    LogicalVolume.EraseVolume[activeVolume, extraBuffer];
    VolFileMap.Close[TRUE];
    VolAllocMap.Close[TRUE];
    activeVolume.changing ← FALSE;
    Deactivate[];
    END;
    
  OpenLogicalVolume: PUBLIC PROCEDURE [pVID: POINTER TO READONLY Volume.ID]
    RETURNS [LogicalVolume.OpenStatus] =
    { RETURN[OpenLogicalVolumeInternal[pVID, FALSE]]; };
    
  VolumeAccess: PUBLIC ENTRY PROCEDURE [
     pVID: POINTER TO READONLY Volume.ID, proc: LogicalVolume.VolumeAccessProc,
     modify: BOOLEAN]
    RETURNS [LogicalVolume.VolumeAccessStatus] =
    BEGIN
    open, beingScavenged, updateMarkers: BOOLEAN;
    IF pVID↑ = Volume.nullID OR ~LogicalVolumeFind[pVID] THEN
      RETURN[volumeUnknown];
    IF modify AND IsReadOnly[pVID↑] THEN RETURN[volumeReadOnly];
    Activate[pVID↑];
    -- Only allow access if the volume is open or being scavenged.
    -- The being scavenged option should only be exercised by the Scavenger.
    [open: open, beingScavenged: beingScavenged] ← LVGetStatus[pVID↑];
    IF ~(open OR beingScavenged) THEN
      { Deactivate[]; RETURN[volumeNotOpen]; };
    IF modify THEN
      BEGIN
      activeVolume.changing ← TRUE;
      SimpleSpace.ForceOut[logicalRootPageBuffer];
      END;
    updateMarkers ← proc[activeVolume];
    IF ~modify THEN RETURN[ok];
    IF updateMarkers THEN MarkerPage.UpdateLogicalMarkerPages[activeVolume];
    activeVolume.changing ← FALSE;
    Deactivate[];
    RETURN[ok];
    END;
    
  --
  -- VolumeImplInterface

  CheckLogicalVolume: PUBLIC ENTRY PROCEDURE [vID: Volume.ID] = {
    LogicalVolumeCheck[vID]; Deactivate[]};
    
  FindLogicalVolume: PUBLIC ENTRY PROCEDURE [vID: POINTER TO READONLY Volume.ID]
    RETURNS [BOOLEAN] = {RETURN[LogicalVolumeFind[vID]]};
    
  GetLVStatus: PUBLIC ENTRY PROCEDURE [vID: Volume.ID]
    RETURNS [onLine, open: BOOLEAN] = {[open, onLine] ← LVGetStatus[vID]};
    
  OpenInitialVolumes: PUBLIC PROCEDURE =
    BEGIN
    volumeID: Volume.ID;
    GetNextOnLineVolume: ENTRY PROCEDURE [volume: Volume.ID]
      RETURNS [Volume.ID] =
      BEGIN
      volumeFound: BOOLEAN ← volume = Volume.nullID;
      lv: LVTIndex;
      FOR lv IN LVTIndex DO
         IF volumeFound AND LVT[lv].lvID # Volume.nullID AND LVT[lv].onLine
            AND LVT[lv].type = debugClass THEN RETURN[LVT[lv].lvID];
         IF LVT[lv].lvID = volume THEN volumeFound ← TRUE;
         ENDLOOP;
      IF volumeFound THEN RETURN[Volume.nullID]
      ELSE RETURN WITH ERROR Volume.Unknown[volume];
      END;
    IF IsUtilityPilot[] THEN RETURN;
    FOR volumeID ← GetNextOnLineVolume[Volume.nullID],
         GetNextOnLineVolume[volumeID] UNTIL volumeID = Volume.nullID DO
      IF volumeID=systemID OR PilotSwitches.switches.k=up THEN
		  Volume.Open[volumeID ! Volume.NeedsScavenging =>
         {[] ← Scavenger.Scavenge[volumeID, volumeID, TRUE]; RETRY}];
      ENDLOOP;
    END;
    
  PinnedFileEnter: PUBLIC PROCEDURE [
    fd: FileInternal.Descriptor, grp: FileInternal.PageGroup] =
    BEGIN
    IF ~FileCache.GetFilePtrs[0, fd.fileID].success THEN {
      FileCache.SetFile[fd, TRUE]; FileCache.SetPageGroup[fd.fileID, grp, TRUE]};
    END;
    
  PinnedFileFlush: PUBLIC PROCEDURE [file: File.ID] = {FileCache.FlushFile[file]};
    
  RegisterLogicalSubvolume: PUBLIC PROCEDURE [
    sv: PhysicalVolumeFormat.SubVolumeDesc, pvID: PhysicalVolume.ID] =
    BEGIN
    IF ~SubVolume.Find[sv.lvID, sv.lvPage].success THEN
      SubVolume.OnLine[
   sv, SubVolume.Find[[LOOPHOLE[pvID]], pvRootPage].subVolume.channel];
    IF sv.lvPage = lvRootPage THEN
      -- Set the volume file to cover just the root page;
      VFileEnter[
   sv.lvID, LOOPHOLE[sv.lvID], lvRootPage, lvRootPage + 1,
   PilotFileTypes.tLogicalVolumeRootPage];
    END;
    
  RegisterVFiles: PUBLIC PROCEDURE [v: LvHandle] =
    BEGIN
    RegisterVFile: PROCEDURE [t: File.Type] = INLINE {
      VFileEnter[v.vID, v.rootFileID[t], 1, v.volumeSize, t]};
    RegisterVFile[PilotFileTypes.tFreePage];
    RegisterVFile[PilotFileTypes.tVolumeAllocationMap];
    RegisterVFile[PilotFileTypes.tVolumeFileMap];
    END;
    
  -- perhaps this should use GetLogicalRootPage???
  SignalVolumeAccess: PUBLIC PROCEDURE [
    pVID: POINTER TO READONLY Volume.ID, proc: LogicalVolume.VolumeAccessProc] =
    BEGIN
    SELECT LogicalVolume.VolumeAccess[pVID, proc, TRUE] FROM
      ok => RETURN;
      volumeUnknown => ERROR Volume.Unknown[pVID↑];
      volumeNotOpen => ERROR Volume.NotOpen[pVID↑];
      volumeReadOnly => ERROR LogicalVolume.ReadOnlyVolume;
      ENDCASE => ERROR VolImplError[illegalReturnCode];
    END;
    
  SubvolumeOffline: PUBLIC ENTRY PROCEDURE [lvID: Volume.ID, root: BOOLEAN] =
    BEGIN
    IF root THEN
       BEGIN
       Activate[lvID];
       UnregisterVFiles[activeVolume];
       Deactivate[flush];
       END;
    LVDecrementPieceCount[lvID];
    END;
    
  SubvolumeOnline: PUBLIC ENTRY PROCEDURE [lvID: Volume.ID, root: BOOLEAN] =
    BEGIN -- we ignore the root argument.  It is provided for consistency with
            -- SubvolumeOffline where it is currently needed.  Eventually we may
            -- use it here.
    IF LogicalVolumeFind[@lvID] THEN LVIncrementPieceCount[lvID]
    ELSE EnterLV[lvID];
    RETURN;
    END;
    
  UnregisterVFiles: PUBLIC PROCEDURE [v: LvHandle] =
    BEGIN
    UnregisterVFile: PROCEDURE [t: File.Type] = INLINE {
      PinnedFileFlush[v.rootFileID[t]]};
    UnregisterVFile[PilotFileTypes.tFreePage];
    UnregisterVFile[PilotFileTypes.tVolumeAllocationMap];
    UnregisterVFile[PilotFileTypes.tVolumeFileMap];
    END;
    
  VFileEnter: PUBLIC PROCEDURE [
    volume: Volume.ID, file: File.ID, fileAndVolPage: File.PageNumber,
    nextPage: VolumeInternal.PageNumber, type: File.Type] =
    BEGIN
    PinnedFileEnter[
      [file, volume, local[FALSE, FALSE, nextPage, type]],
      [fileAndVolPage, fileAndVolPage, nextPage]];
    END;
    
  --
  -- Logical volume table management (private)

  EnterLV: INTERNAL PROCEDURE [vID: Volume.ID] =
    BEGIN
    FOR lv: LVTIndex IN LVTIndex DO
      IF LVT[lv].lvID = Volume.nullID THEN
         BEGIN
         -- readOnly must be FALSE in the following so that scavenging a volume
         -- at Open time works correctly.  This may have to be changed if Open
         -- ever takes an access mode argument.
         LVT[lv] ←
             [lvID: vID, pieceCount: 1, beingScavenged: FALSE, open: FALSE,
              onLine: FALSE, readOnly: FALSE, type: nonPilot];
         RETURN
         END;
      ENDLOOP;
    ERROR VolImplError[lvTableFull];
    END;
    
  LogicalVolumeFind: INTERNAL PROCEDURE [vID: POINTER TO READONLY Volume.ID]
    RETURNS [found: BOOLEAN] =
    BEGIN
    FOR lv: LVTIndex IN LVTIndex DO
      IF LVT[lv].lvID = vID↑ THEN RETURN[LVT[lv].onLine]; ENDLOOP;
    RETURN[FALSE]
    END;
    
  LogicalVolumeOffLine: INTERNAL PROCEDURE [vID: Volume.ID] =
    BEGIN
    FOR lv: LVTIndex IN LVTIndex DO
      IF LVT[lv].lvID = vID THEN
         BEGIN -- Move remaining entries to front so enumeration works correctly
         FOR lvMove: LVTIndex IN [lv + 1..LAST[LVTIndex]) DO
            LVT[lvMove - 1] ← LVT[lvMove]; ENDLOOP;
         LVT[LAST[LVTIndex]] ← nullVolumeStatus;
         RETURN;
         END;
      ENDLOOP;
    ERROR VolImplError[notFoundInLVT];
    END;
    
  LVDecrementPieceCount: INTERNAL PROCEDURE [vID: Volume.ID] =
    BEGIN
    p: POINTER TO VolumeStatus = LVGetEntryPointer[vID];
    p.pieceCount ← p.pieceCount - 1;
    IF p.pieceCount = 0 THEN LogicalVolumeOffLine[vID];
    END;
    
  LVGetEntryPointer: INTERNAL PROCEDURE [vID: Volume.ID]
    RETURNS [POINTER TO VolumeStatus] =
    BEGIN
    lv: LVTIndex;
    FOR lv IN LVTIndex DO IF LVT[lv].lvID = vID THEN RETURN[@LVT[lv]] ENDLOOP;
    ERROR VolImplError[notFoundInLVT];
    END;
    
  LVGetStatus: INTERNAL PROCEDURE [vID: Volume.ID]
    RETURNS [beingScavenged, open, onLine, readOnly: BOOLEAN, type: Volume.Type] =
    BEGIN
    p: POINTER TO VolumeStatus = LVGetEntryPointer[vID];
    RETURN[p.beingScavenged, p.open, p.onLine, p.readOnly, p.type]
    END;
    
  LVIncrementPieceCount: INTERNAL PROCEDURE [vID: Volume.ID] = INLINE
    BEGIN
    p: POINTER TO VolumeStatus = LVGetEntryPointer[vID];
    p.pieceCount ← p.pieceCount + 1;
    END;
    
  LVSetOnLine: INTERNAL PROCEDURE [vID: Volume.ID, onLine: BOOLEAN] = INLINE
    BEGIN
    p: POINTER TO VolumeStatus = LVGetEntryPointer[vID];
    p.onLine ← onLine;
    END;
    
  LVSetOpen: INTERNAL PROCEDURE [vID: Volume.ID, open: BOOLEAN] = INLINE
    BEGIN p: POINTER TO VolumeStatus = LVGetEntryPointer[vID]; p.open ← open; END;
    
  LVSetReadOnly: INTERNAL PROCEDURE [vID: Volume.ID, readOnly: BOOLEAN] = INLINE
    BEGIN
    p: POINTER TO VolumeStatus = LVGetEntryPointer[vID];
    p.readOnly ← readOnly;
    END;
    
  LVSetScavenged: INTERNAL PROCEDURE [vID: Volume.ID, beingScavenged: BOOLEAN] =
    INLINE
    BEGIN
    p: POINTER TO VolumeStatus = LVGetEntryPointer[vID];
    p.beingScavenged ← beingScavenged;
    END;
    
  LVSetType: INTERNAL PROCEDURE [vID: Volume.ID, type: Volume.Type] = INLINE
    BEGIN p: POINTER TO VolumeStatus = LVGetEntryPointer[vID]; p.type ← type; END;
    
  --
  -- Various utility procedures

  DriveSize: PROCEDURE [pvID: PhysicalVolume.ID]
    RETURNS [DiskChannel.DiskPageCount] =
    BEGIN
    RETURN[
      DiskChannel.GetDriveAttributes[
   MarkerPage.Find[[physicalID[pvID]]].drive].nPages];
    END;
    
  IsReadOnly: INTERNAL PROCEDURE [vID: Volume.ID] RETURNS [BOOLEAN] = INLINE {
    RETURN[
      (~IsUtilityPilot[]) AND
      ((LVGetStatus[vID].type > debugClass) OR LVGetStatus[vID].readOnly)]};
    
  IsUtilityPilot: PROCEDURE RETURNS [BOOLEAN] = INLINE {
    RETURN[PilotSwitches.switches.u = down]};
    
  -- Similar to VolumeAccess, but only requires the Root Page Subvolume to be around.
  -- Intended for use by read-only procedures.
  GetLogicalRootPage: ENTRY PROCEDURE [
    pVID: POINTER TO READONLY Volume.ID, proc: PROCEDURE [LvHandle]] =
    BEGIN
    IF pVID↑ = Volume.nullID OR ~SubVolume.Find[pVID↑, lvRootPage].success THEN
      RETURN WITH ERROR Unknown[pVID↑];
    Activate[pVID↑];
    IF activeVolume.seal # LogicalVolume.seal THEN {
      Deactivate[flush]; RETURN WITH ERROR Unknown[pVID↑]};
    proc[activeVolume];
    END;
    
  -- Provides access to volume files of one volume at a time
  Activate: INTERNAL PROCEDURE [vID: Volume.ID] =
    BEGIN
    IF activeVID # vID THEN
      BEGIN
      Deactivate[flush];
      SimpleSpace.Map[
         logicalRootPageBuffer,
         [[LOOPHOLE[vID], FileInternal.maxPermissions], lvRootPage], FALSE];
      activeVID ← vID;
      END;
    END;
    
  -- Deactivates the active volume.  If the volume is Open, clear the changing bit.
  Deactivate: INTERNAL PROCEDURE [mode: {forceout, flush} ← forceout] =
    BEGIN
    IF activeVID = Volume.nullID THEN RETURN;
    VolFileMap.Close[mode = flush];
    VolAllocMap.Close[mode = flush];
    IF mode = flush THEN
      BEGIN
      SimpleSpace.Unmap[logicalRootPageBuffer];
      activeVID ← Volume.nullID;
      END
    ELSE SimpleSpace.ForceOut[logicalRootPageBuffer];
    END;
    
  -- When LogicalVolumeCheck is called, the Subvolume is Online; if the LvRoot subvolume
  -- is online, the LvRoot page file cache entries are set.  If the Root SV is accessible
  -- and of the right type, Vam, free and Vfm files are registered.
  -- Called from CreateLogicalVolume, CheckLogicalVolume
  LogicalVolumeCheck: INTERNAL PROCEDURE [vID: Volume.ID] =
    BEGIN
    good: BOOLEAN;
    size: Volume.PageCount;
    svH: SubVolume.Handle;
    [good, svH] ← SubVolume.Find[vID, lvRootPage];
    IF good THEN good ← ReadAndCheckLogicalRootLabel[vID];
    IF good THEN
      BEGIN
      Activate[vID];
      good ← activeVolume.seal = LogicalVolume.seal
               AND activeVolume.version = LogicalVolume.currentVersion
               AND vID = activeVolume.vID;
      IF good THEN
         BEGIN
         good ← LogicalVolumeLike[vID];
         LVSetType[vID, activeVolume.type];
         END;
      size ← activeVolume.volumeSize;
      -- ~good volumes can be debuggers
      IF LogicalVolumeDebuggerCheck[activeVolume] THEN
         -- Set the debugger boot pointers back in Pilot Control
         BEGIN
         IF pLVBootFiles ~= NIL THEN pLVBootFiles↑ ← activeVolume.bootingInfo;
         [deviceType: debuggerDeviceType↑, deviceOrdinal: debuggerDeviceOrdinal↑] ←
            DiskChannel.GetDriveAttributes[
               DiskChannel.GetAttributes[svH.channel].drive];
         debugSearchState ← done;
         END;
      END;
    WHILE good AND size # svH.lvPage + svH.nPages DO
      [good, svH] ← SubVolume.Find[vID, svH.lvPage + svH.nPages]; ENDLOOP;
    IF good THEN
      BEGIN
      -- The theory is that we don't have to check to see if the volume is already on line
      -- since this subvolume is new
      RegisterVFiles[activeVolume];
      LVSetOnLine[vID, TRUE];
      END
    -- Deactivate is done in caller
    END;
    
  -- This may have to get smarter when we have boot messages
  LogicalVolumeDebuggerCheck: PROCEDURE [vol: LvHandle]
    RETURNS [isMyDebugger: BOOLEAN] =
    BEGIN
    IF debugSearchState # looking OR vol.seal # LogicalVolume.seal
       OR vol.version <= 1 OR vol.bootingInfo[debugger] = Boot.nullDiskFileID
       OR vol.bootingInfo[debuggee] = Boot.nullDiskFileID
       THEN RETURN[FALSE];
    SELECT debugClass FROM
      nonPilot, debuggerDebugger => RETURN[FALSE]; -- these guys have no debugger
      normal => RETURN[vol.type = debugger];
      debugger => RETURN[vol.type = debuggerDebugger];
      ENDCASE => ERROR;
    END;
    
  LogicalVolumeLike: INTERNAL PROCEDURE [vID: Volume.ID] RETURNS [good: BOOLEAN] =
    BEGIN
    SELECT TRUE FROM
      IsUtilityPilot[] => good ← activeVolume.type # nonPilot;
        -- don't put non-Pilot volumes online
      debugSearchState = tooSoonToLook =>
         -- PhysicalVolumeOnLine has arranged that the first subvolume we find will be
         -- the system volume
         BEGIN
         systemID ← vID;
         debugClass ← activeVolume.type;
         debugSearchState ← looking;
         good ← TRUE;
         END;
      ENDCASE => good ← activeVolume.type # nonPilot;
    END;
    
  OpenLogicalVolumeInternal: ENTRY PROCEDURE [
       pVID: POINTER TO READONLY Volume.ID, readOnly: BOOLEAN]
    RETURNS [LogicalVolume.OpenStatus] =
    BEGIN
    IF pVID↑ = Volume.nullID OR ~LogicalVolumeFind[pVID] THEN RETURN[Unknown];
    WHILE LVGetStatus[pVID↑].beingScavenged DO WAIT scavengingComplete; ENDLOOP;
    IF LVGetStatus[pVID↑].open THEN RETURN[wasOpen];
    Activate[pVID↑];
    LVSetOpen[pVID↑, TRUE];
    LVSetReadOnly[pVID↑, readOnly];
    RegisterVFiles[activeVolume];
    IF activeVolume.changing THEN
      BEGIN
      LVSetOpen[pVID↑, FALSE];
      Deactivate[];
      RETURN[VolumeNeedsScavenging]
      END;
    Deactivate[];
    RETURN[ok];
    END;
    
  ReadAndCheckLogicalRootLabel: PROCEDURE [lvID: Volume.ID] RETURNS [BOOLEAN] =
    BEGIN
    label: PilotDisk.Label ← LabelTransfer.ReadLabel[
       [LOOPHOLE[lvID], lvID, local[
       FALSE, FALSE, 1, PilotFileTypes.tLogicalVolumeRootPage]], lvRootPage,
       lvRootPage].label;
    BEGIN OPEN label;
       -- could also check pad2...
       RETURN[
         fileID = LOOPHOLE[lvID] AND PilotDisk.GetLabelFilePage[@label] = lvRootPage
         AND ~immutable AND ~temporary AND ~zeroSize AND pad1 = 0 AND
         PilotDisk.GetLabelType[@label] = PilotFileTypes.tLogicalVolumeRootPage];
       END;
    END;
    
  Process.InitializeCondition[@scavengingComplete, LAST[Process.Ticks]];
  END.

(For earlier log entries see Pilot 3.0 archive version.)
January 25, 1980  2:54 PM   McJones
	Delete InstallBootVolume; add Get/SetPhysicalVolumeBootFiles,
	GetContainingPhysicalVolume; add minPVPageNumber to
	CreateLogicalVolume

February 3, 1980  3:57 PM   McJones
	Add device types to module parameters, results

March 7, 1980  11:52 PM   Forrest
	re-arranged opens to call up into fileImpl and from there to here
	(see comments in LogicalVolume).  Made Openning smarter about always
	setting Open bit.  Changed FileVolumeAccess to not pass on parameters
	deleted from FileAccess Procs.

March 11, 1980  10:37 AM   Forrest
	Added display of cDeleteTemps.

June 5, 1980  3:13 PM   Luniewski
	PhysicalVolume => PhysicalVolumeFormat.  Added removable volume
	support including a new logical volume table format, a GetHints
	operation for PhysicalVolumeImpl to use, the Volume.GetStatus
	operation and recognition of the ability to unpin file cache entries.
	Modified to move all physical volume implementation stuff into
	PhysicalVolumeImpl.

June 19, 1980  2:47 PM   Luniewski
	Added BeginScavenging and EndScavenging.

July 17, 1980  5:13 PM   Forrest
	Added second arg to GetNext procedure; added GetLabelType..

August 1, 1980  10:00 AM   Luniewski
	Made less conservative user of Deactivate (performance improvement).
	Mods to permit read-up and read-write down of volumes.  Hooks to
	permit some operations to explicitly specify read-only/read-write
	access.

August 15, 1980  2:54 PM   McJones
	VolumeSet=>TypeSet

September 3, 1980  4:40 PM  Luniewski
	Fix EnterLV to permit scavenging at Open time.  Clear bootingInfo on
	erasing a logical volume.

September 18, 1980  7:22 PM  Luniewski
	Changes for new logical volume format.  Open volumes of higher type
	than system volume without deleting temps.  

September 22, 1980  12:02 PM  Luniewski
	Do delete temps on volumes of type <= system volume.

October 9, 1980  11:15 AM  Luniewski
	Implement VolumeExtras.OpenVolume

October 21, 1980  1:54 PM  Jose
	Increase maxLVs from 8 to 12

January 8, 1981  10:20 AM  Gobbel
	'K' keyswitch: only open system logical volume

January 12, 1981  3:55 PM  Luniewski
	New LabelTransfer interface.  Change Open to enforce read-onliness.

January 21, 1981  6:07 PM  Fay
	Changed LogicalVolumeCreate/Erase to call EraseVolume rather than ScavengeVolume;
	changed BeginScavenging to accept and pass a context to ScavengeVolume and to catch
	and turn Scavenger.Error into a RETURN WITH ERROR.

August 26, 1982 9:14 am	Levin
	Make things SAFE.