-- FileMgr>FileImpl.mesa  (December 9, 1982 11:37 am by Levin)

-- Note on monitors in this module:  All procedures in this module that must read or change entries in the FileMgr's caches do their actual work through LogicalVolume.VolumeAccess, and are thus protected by VolumeImpl's monitor lock from inconsistencies that might otherwise result if cache accesses were interleaved.  Simply (I hope) stated: VolumeImpl's monitor is used to insure the consistency of individual cache entries, FileCacheImpl's monitor insures consistency of the global state of the caches, and FileImpl's monitor is used to serialize access to files at a higher level (i.e., above the level of the caches).  There is at least one procedure, GetFileDescriptor, which must be outside of FileImpl's monitor (because it is used by the FileHelper), but which must access the FileMgr caches. 

DIRECTORY
  Boot USING [Location, LVBootFiles],
  Device USING [Type],
  DiskChannel USING [Address, Handle, Drive, GetAttributes, GetDriveAttributes,
    GetDriveTag],
  Environment USING [PageCount, PageNumber, wordsPerPage],
  File USING [Capability, delete, ErrorType, grow, ID, lastPageNumber, nullCapability,
    nullID, PageCount, PageNumber, Permissions, read, shrink, Type, write],
  FileCache USING [FlushFile, GetFilePtrs, GetPageGroup, SetFile, SetPageGroup,
    ReturnFilePtrs],
  FileInternal USING [Descriptor, FilePtr, LocalFilePtr, maxPermissions, PageGroup],
  FilePageTransfer USING [Initiate, Request],
  FilerException USING [Await],
  FMPrograms USING [MarkerPageImpl, PhysicalVolumeImpl, VolAllocMapImpl,
    VolFileMapImpl],
  Inline USING [BITAND, LowHalf],
  KernelFile USING [defaultPageCount, GetNextFile, GetRootFile],
  LabelTransfer USING [ReadLabel, VerifyLabels, WriteLabelAndData],
  LogicalVolume USING [CloseLogicalVolume, FileVolumeStatus, FreeVolumePages,
    Handle, nullVolumePage, OpenLogicalVolume, PageNumber, PutRootFile,
    VolumeAccess, VolumeAccessProc, VolumeAccessStatus],
  PilotDisk USING [Address, GetLabelFilePage, Label, LabelCheckSum, SetLabelFilePage],
  PilotFileTypes USING [PilotRootFileType, PilotVFileType, tTempFileList],
  PilotSwitches USING [switches --.f--],
  Process USING [GetPriority, Priority, SetPriority],
  ProcessPriorities USING [priorityPageFaultHigh],
  Runtime USING [CallDebugger],
  SimpleSpace USING [Create, ForceOut, Handle, Map, Page, Unmap],
  Space USING [defaultWindow],
  SpecialFile USING [Link],
  SpecialVolume USING [GetLogicalVolumeBootFiles, SetLogicalVolumeBootFiles],
  StoragePrograms,
  SubVolume USING [Find, GetPageAddress, Handle],
  System USING [GetUniversalID, VolumeID],
  SystemInternal USING [ --altoFPSeries,--Unimplemented],
  Transaction USING [Handle, InvalidHandle, nullHandle],
  TransactionState USING [FileOps, Log, LogEntry],
  Utilities USING [LongPointerFromPage],
  VMMapLog USING [Entry],
  VolAllocMap USING [AllocPageGroup, FreePageGroup],
  VolFileMap USING [DeletePageGroup, GetPageGroup, InsertPageGroup],
  Volume USING [GetNext, ID, InsufficientSpace, NeedsScavenging, NotOpen, nullID,
    systemID, TypeSet, Unknown];

FileImpl: MONITOR
  IMPORTS
    DiskChannel, FileCache, FilePageTransfer, FilerException, FMPrograms, Inline,
    KernelFile, LabelTransfer, LogicalVolume, PilotDisk, PilotSwitches,
    Process, Runtime, SimpleSpace, SpecialVolume, SubVolume, System,
    SystemInternal, Transaction, TransactionState, Utilities, VolAllocMap,
    VolFileMap, Volume
  EXPORTS File, LogicalVolume, KernelFile, SpecialFile, StoragePrograms
  SHARES DiskChannel, File, System, SystemInternal =
BEGIN OPEN File, FileInternal;
VolProc: TYPE = LogicalVolume.VolumeAccessProc;

-- One-page buffer used by MakePermanent, MakeImmutable, MakeBootable:
pageBuffer: SimpleSpace.Handle = SimpleSpace.Create[1, hyperspace];
bufferPage: Environment.PageNumber = SimpleSpace.Page[pageBuffer];
bufferPointer: LONG POINTER = Utilities.LongPointerFromPage[bufferPage];

-- Temporary File list used by DeleteTemps and EnterInTempFileList
--        (these values assume list entries do not overlap pages)
tmpsBuffer: SimpleSpace.Handle ← SimpleSpace.Create[1, hyperspace];
tmpsVolume: Volume.ID ← Volume.nullID;
tmpsFile: File.Capability;
tmpsFileSize: File.PageCount;
tmpStart: LONG POINTER TO File.ID =
  Utilities.LongPointerFromPage[SimpleSpace.Page[tmpsBuffer]];
tmpsLast: LONG POINTER TO File.ID =
  tmpStart+(Environment.wordsPerPage/SIZE[File.ID])*SIZE[File.ID];
tmps: LONG POINTER TO File.ID;

nullTransaction: Transaction.Handle = Transaction.nullHandle;

AttributeAction: TYPE = {immutable, permanent, mutable, temporary};

Error: PUBLIC ERROR [type: ErrorType] = CODE;
InvalidParameters: PUBLIC ERROR = CODE;
LabelError: SIGNAL = CODE;
Unknown: PUBLIC ERROR [file: Capability] = CODE;
ExistingFile: PUBLIC ERROR = CODE;
FileImplError: ERROR [{disappearedOrRemoteTempFile, fileWithHole, illegalAttributeAction,
  impossibleFileType, makeBootablePageGroupNotFound, makeBootableImpossibleError,
  missingPage0, missingPageGroupSetSize, missingPinnedPageGroup, remoteTmpsFile,
  remoteTxLog, SubvolumeNotFoundInGetFilePoint, tmpsFileProblem, tmpsFileWentAway,
  tmpsVolumeWentAway, volumeWentAwayDuringDeleteTemps, setSizeImpossibleError}] =
  CODE;

-- MONITOR EXTERNAL PROCEDURES

Create: PUBLIC SAFE PROCEDURE [volume: System.VolumeID, initialSize: PageCount, type: Type,
  transaction: Transaction.Handle] RETURNS [file: Capability] = TRUSTED
  BEGIN
  IF type IN PilotFileTypes.PilotVFileType THEN ERROR Error[reservedType];
  file ← [[System.GetUniversalID[]], FileInternal.maxPermissions];
  CreateWithIDExternal[volume, initialSize, type, file.fID, transaction];
  END;

CreateWithID: PUBLIC PROCEDURE [volume: System.VolumeID, initialSize: PageCount,
  type: Type, id: ID] = -- Create file given the file identifier
  {CreateWithIDExternal[volume, initialSize, type, id]};

Delete: PUBLIC PROCEDURE [file: Capability, transaction: Transaction.Handle] =
  {DeleteCommon[@file, NIL, transaction]};

DeleteImmutable: PUBLIC PROCEDURE [file: Capability, volume: System.VolumeID,
  transaction: Transaction.Handle] =
  {DeleteCommon[@file, @volume, transaction]};

FileHelperProcess: PROCEDURE =
  BEGIN
  helpCount: CARDINAL ← 0;  -- for perfomance monitoring.
  fileD: FileInternal.Descriptor;
  req: FilePageTransfer.Request;
  DO --FOREVER--
    req ← FilerException.Await[];
    GetFileDescriptorSignals[@req.file, @fileD];  -- file cache updated as side effect
    WITH fileD SELECT FROM
      local =>
        BEGIN

        Helper1: VolProc =
          BEGIN
          fPtr: FileInternal.FilePtr;
          success: BOOLEAN;
          group: FileInternal.PageGroup;
          updateMarkers ← FALSE;
          [success, fPtr] ← FileCache.GetFilePtrs[1, fileD.fileID];
          IF ~success THEN RETURN;  -- fell out of cache, let exception happen again
          [success, group] ←
             VolFileMap.GetPageGroup[volume, @fileD, req.filePage];
          IF (success ← (success AND req.filePage<group.nextFilePage)) THEN
            FileCache.SetPageGroup[req.file.fID, group, FALSE];  -- page is within file?
          FileCache.ReturnFilePtrs[1, fPtr];
          IF ~success THEN Runtime.CallDebugger["Mapped off File (Helper)"L];
          END;

        SELECT LogicalVolume.VolumeAccess[@fileD.volumeID, Helper1] FROM
          ok => NULL;
          ENDCASE => Runtime.CallDebugger[
            "Volume vanished between Descriptor and PageGroup (Helper)"];
        END; --Helper1--

      remote => NULL;
      ENDCASE;
    FilePageTransfer.Initiate[req];
    helpCount ← helpCount+1;
    ENDLOOP
  END;

GetAttributes: PUBLIC SAFE PROCEDURE [file: Capability, transaction: Transaction.Handle]
  RETURNS [type: Type, immutable, temporary: BOOLEAN, volume: System.VolumeID] = TRUSTED
  BEGIN
  f: FileInternal.Descriptor;
  GetFileDescriptorSignals[@file, @f];
  WITH f SELECT FROM
    local => RETURN[type, immutable, temporary, f.volumeID];
    ENDCASE => --RETURN[File.Type[0], FALSE, FALSE, VolumeInternal.altoVolume]
      ERROR SystemInternal.Unimplemented
  END;

GetBootLocation: PUBLIC PROCEDURE [
  file: File.Capability, filePage: File.PageNumber]
  RETURNS [deviceType: Device.Type, deviceOrdinal: CARDINAL, link: SpecialFile.Link] =
  BEGIN
  v: Volume.ID;
  grp: FileInternal.PageGroup;
  channel: DiskChannel.Handle;
  GetVIDAndGroup[@v, @grp, @file, filePage]; -- set vID, group
  [channel, LOOPHOLE[link, DiskChannel.Address]] ←
    SubVolume.GetPageAddress[v, grp.volumePage+(filePage-grp.filePage)];
  [deviceType: deviceType, deviceOrdinal: deviceOrdinal] ←
    DiskChannel.GetDriveAttributes[DiskChannel.GetAttributes[channel].drive];
  END;

GetFileDescriptor: PROCEDURE [
  file: POINTER TO READONLY File.Capability, fileD: FileInternal.FilePtr,
  vP: POINTER TO READONLY Volume.ID ← NIL] RETURNS [found: BOOLEAN] =
  -- Look up file in cache and then in given volume or all volumes (vP=NIL),
  -- and set the descriptor.  Return success
  BEGIN
  fPtr: FileInternal.FilePtr;
  success: BOOLEAN;
  v: Volume.ID;
  getAll: Volume.TypeSet = [normal: TRUE, debugger: TRUE, debuggerDebugger: TRUE];
  GetFileDescriptor1: VolProc =
    BEGIN
    group: FileInternal.PageGroup;
    label: PilotDisk.Label;
    lf: local FileInternal.Descriptor ← [file.fID, v, local[, , , ]];
    updateMarkers ← FALSE;
    [success, group] ← VolFileMap.GetPageGroup[volume, @lf, 0];
    IF ~success THEN RETURN;
    label ← LabelTransfer.ReadLabel[lf, 0, group.volumePage].label;
    IF label.fileID#lf.fileID OR PilotDisk.GetLabelFilePage[@label]#0 THEN
      SIGNAL LabelError;
    lf.body ← local[
      immutable: label.immutable, temporary: label.temporary,
      size: VolFileMap.GetPageGroup[
                         volume, LONG[@lf], File.lastPageNumber].group.filePage,
      type: label.type];
    -- success??
    fileD↑ ← lf;
    DO
      FileCache.SetFile[lf, FALSE];
      [success, fPtr] ← FileCache.GetFilePtrs[1, lf.fileID];
      IF success THEN
        BEGIN
        FileCache.SetPageGroup[lf.fileID, group, FALSE];
        FileCache.ReturnFilePtrs[1, fPtr];
        RETURN;
        END;
      ENDLOOP;
    END;

    IF file.fID = File.nullID THEN RETURN[FALSE];
  --IF LOOPHOLE[file.fID, SystemInternal.UniversalID].series=SystemInternal.altoFPSeries THEN
  --IF GetAltoSize[file]=0 THEN ERROR Unknown[file↑] ELSE RETURN[[file.fID, VolumeInternal.altoVolume, remote[]]];
  [success, fPtr] ← FileCache.GetFilePtrs[1, file.fID];
  IF success THEN
    BEGIN
    fileD↑ ← fPtr↑;
    FileCache.ReturnFilePtrs[1, fPtr];
    IF vP=NIL OR fileD.volumeID=vP↑ THEN RETURN[TRUE];
    END;
  v ← IF vP=NIL THEN Volume.GetNext[Volume.nullID, getAll] ELSE vP↑;
  WHILE v#Volume.nullID DO
    IF LogicalVolume.VolumeAccess[@v, GetFileDescriptor1]=ok AND success THEN
      RETURN[TRUE];
    IF vP#NIL THEN EXIT ELSE v ← Volume.GetNext[v, getAll];
    ENDLOOP;
  RETURN[FALSE];
  END;

GetFileDescriptorSignals: PROCEDURE [
  file: POINTER TO READONLY File.Capability, fileD: FileInternal.FilePtr,
  vP: POINTER TO READONLY Volume.ID ← NIL] =
  BEGIN
  IF ~GetFileDescriptor[file, fileD, vP].found THEN ERROR Unknown[file↑];
  END;

GetFilePoint: PUBLIC PROCEDURE [
  pEntry: LONG POINTER TO VMMapLog.Entry, pFile: POINTER TO File.Capability,
  filePage: File.PageNumber] =
  BEGIN
  countMax: CARDINAL = 4096;
  vID: Volume.ID;
  group: FileInternal.PageGroup;
  lvPage: LogicalVolume.PageNumber;
  success: BOOLEAN;
  svH: SubVolume.Handle;
  label: PilotDisk.Label;
  GetVIDAndGroup[@vID, @group, pFile, filePage];
  lvPage ← filePage-group.filePage+group.volumePage;
  -- file => logical vol page
  [success, svH] ← SubVolume.Find[vID, lvPage];
  IF ~success THEN ERROR FileImplError[SubvolumeNotFoundInGetFilePoint];
  label.fileID ← pFile.fID;
  PilotDisk.SetLabelFilePage[@label, filePage];
  pEntry↑ ←
    [page:, writeProtected:, fill:,
    count: MIN[countMax, Inline.LowHalf[group.nextFilePage-filePage]],
    filePoint: disk[
      driveTag: DiskChannel.GetDriveTag[DiskChannel.GetAttributes[svH.channel].drive],
      diskPage: lvPage-svH.lvPage+svH.pvPage,
      -- logical vol => physical vol page,
      labelCheck: PilotDisk.LabelCheckSum[@label, 0]]];
  END;

GetSize: PUBLIC SAFE PROCEDURE [file: Capability, transaction: Transaction.Handle]
  RETURNS [size: PageCount] = TRUSTED
  BEGIN
  fileD: FileInternal.Descriptor;
  GetFileDescriptorSignals[@file, @fileD];
  RETURN[
    WITH fileD SELECT FROM
      local => size,
      ENDCASE => --GetAltoSize[@file]--ERROR SystemInternal.Unimplemented]
  END;

IsOnVolume: PUBLIC SAFE PROCEDURE [file: Capability, volume: System.VolumeID] = TRUSTED
  BEGIN
  fileD: FileInternal.Descriptor;
  IF ~GetFileDescriptor[@file, @fileD] THEN
    FileCache.SetFile[[file.fID, volume, remote[]], FALSE];
  END;

MakeBootable: PUBLIC PROCEDURE [
  file: File.Capability, firstPage: File.PageNumber, count: File.PageCount,
  lastLink: SpecialFile.Link] RETURNS [firstLink: SpecialFile.Link] =
  BEGIN
  RETURN[LOOPHOLE[MakeBootableOrUnbootable[
    bootable, @file, firstPage, count, LOOPHOLE[lastLink]]]];
  END;

MakeImmutable: PUBLIC PROCEDURE [file: Capability, transaction: Transaction.Handle] =
  {ChangeAttributes[@file, immutable, transaction]};

MakeMutable: PUBLIC PROCEDURE [file: Capability] =
  {ChangeAttributes[@file, mutable]};

MakePermanent: PUBLIC SAFE PROCEDURE [file: Capability, transaction: Transaction.Handle] =
  TRUSTED {ChangeAttributes[@file, permanent, transaction]};

MakeTemporary: PUBLIC PROCEDURE [file: Capability] =
  BEGIN
  fileD: FileInternal.Descriptor;
  IF ~GetFileDescriptor[@file, @fileD] THEN ERROR Unknown[file];
  TmpsEnter[@file, @fileD.volumeID];
  ChangeAttributes[@file, temporary];
  END;

MakeUnbootable: PUBLIC PROCEDURE [
  file: File.Capability, firstPage: File.PageNumber, count: File.PageCount] =
  {[] ← MakeBootableOrUnbootable[unbootable, @file, firstPage, count, ]};

Move: PUBLIC PROCEDURE [
  file: Capability, volume: System.VolumeID, transaction: Transaction.Handle] =
  {SIGNAL SystemInternal.Unimplemented};

ReplicateImmutable: PUBLIC SAFE PROCEDURE [
  file: Capability, volume: System.VolumeID, transaction: Transaction.Handle] = TRUSTED
  {SIGNAL SystemInternal.Unimplemented};

SetDebuggerFiles: PUBLIC PROCEDURE [debugger, debuggee: File.Capability] =
  BEGIN
  bootFiles: Boot.LVBootFiles;
  fileD: FileInternal.Descriptor;
  dTEr, dTEe: Device.Type;
  dOEr, dOEe: CARDINAL;
  linkEr, linkEe: SpecialFile.Link;
  id: Volume.ID = Volume.systemID;
  [deviceType: dTEr, deviceOrdinal: dOEr, link: linkEr] ← GetBootLocation[debugger, 0];
  [deviceType: dTEe, deviceOrdinal: dOEe, link: linkEe] ← GetBootLocation[debuggee, 0];
  GetFileDescriptorSignals[@debugger, @fileD];
  IF dTEr#dTEe OR dOEr#dOEe OR fileD.volumeID#id THEN ERROR InvalidParameters;
  SpecialVolume.GetLogicalVolumeBootFiles[id, @bootFiles];
  bootFiles[debugger] ← [debugger.fID, 0, LOOPHOLE[linkEr]];
  bootFiles[debuggee] ← [debuggee.fID, 0, LOOPHOLE[linkEe]];
  SpecialVolume.SetLogicalVolumeBootFiles[id, @bootFiles];
  END;

SufficientPermissions: PROCEDURE [given, needed: Permissions] RETURNS [BOOLEAN] =
  INLINE {RETURN[Inline.BITAND[given, needed]=needed]};

-- ENTRY PROCEDURES

ChangeAttributes: ENTRY PROCEDURE [
  file: POINTER TO READONLY File.Capability, action: AttributeAction,
  transaction: Transaction.Handle ← nullTransaction] =
  BEGIN
  fileD: FileInternal.Descriptor;
  IF ~GetFileDescriptor[file, @fileD] THEN RETURN WITH ERROR Unknown[file↑];
  WITH f: fileD SELECT FROM
    local =>
      BEGIN
      IF f.immutable AND action#mutable THEN RETURN WITH ERROR Error[immutable];
      IF action=permanent AND ~f.temporary THEN RETURN;
      IF transaction#nullTransaction THEN
        BEGIN OPEN TransactionState;
        entry: LogEntry ← SELECT action FROM
          immutable => [makeMutable[id: file.fID]],
          permanent => [makeTemporary[id: file.fID]],
          ENDCASE => ERROR FileImplError[illegalAttributeAction];
        Log[transaction, @entry, txFileProcs !
          Volume.InsufficientSpace => GOTO InsufficientSpace;
          Transaction.InvalidHandle => GOTO InvalidTxHandle];
        EXITS
          InsufficientSpace => RETURN WITH ERROR Volume.InsufficientSpace;
          InvalidTxHandle => RETURN WITH ERROR Transaction.InvalidHandle;
        END;
      SELECT ChangeAttributesInternal[@f, action] FROM
        ok => NULL;
        volumeUnknown => RETURN WITH ERROR Volume.Unknown[f.volumeID];
        volumeNotOpen => RETURN WITH ERROR Volume.NotOpen[f.volumeID];
        ENDCASE;
      END
    ENDCASE => RETURN WITH ERROR SystemInternal.Unimplemented;
  END;

CloseVolumeAndFlushFiles: PUBLIC ENTRY PROCEDURE [
  v: POINTER TO READONLY Volume.ID] =
  BEGIN -- Someday, flush file caches & make sure nobody is mapped to the bugger
  IF tmpsVolume=v↑ THEN TmpsUnmap[];
  LogicalVolume.CloseLogicalVolume[v ! Volume.Unknown => GOTO unknown];
  EXITS unknown => RETURN WITH ERROR Volume.Unknown[v↑];
  END;

CreateWithIDExternal: PROCEDURE [
  volume: System.VolumeID, initialSize: PageCount, type: Type, id: ID,
  transaction: Transaction.Handle ← nullTransaction] =
  BEGIN
  file: Capability ← [id, FileInternal.maxPermissions];
  fileD: FileInternal.Descriptor;
  CreateWithIDEntry: ENTRY PROCEDURE = INLINE
    BEGIN
    IF type IN PilotFileTypes.PilotVFileType THEN ERROR FileImplError[impossibleFileType];
    IF GetFileDescriptor[@file, @fileD, @volume] THEN RETURN WITH ERROR ExistingFile;
    IF transaction#nullTransaction THEN
      BEGIN OPEN TransactionState;
      entry: LogEntry ← [delete[id: file.fID]];
      Log[transaction, @entry, txFileProcs !
        Volume.InsufficientSpace => GOTO InsufficientSpace;
        Transaction.InvalidHandle => GOTO InvalidTxHandle];
      EXITS
        InsufficientSpace => RETURN WITH ERROR Volume.InsufficientSpace;
        InvalidTxHandle => RETURN WITH ERROR Transaction.InvalidHandle;
      END;
    SELECT CreateWithIDInternal[@volume, @initialSize, type, @id] FROM
      ok => NULL;
      insufficientSpace => RETURN WITH ERROR Volume.InsufficientSpace;
      ENDCASE;
    END;
  TmpsEnter[@file, @volume];
  CreateWithIDEntry[];
  END;

DeleteCommon: ENTRY PROCEDURE [
  file: POINTER TO READONLY File.Capability,
  volume: POINTER TO READONLY Volume.ID,
  -- NIL if Delete, volume if DeleteImmutable--
  transaction: Transaction.Handle] =
  BEGIN
  fileD: FileInternal.Descriptor;
  IF ~GetFileDescriptor[file, @fileD, volume] THEN RETURN WITH ERROR Unknown[file↑];
  IF ~SufficientPermissions[file.permissions, File.delete] THEN
    RETURN WITH ERROR Error[insufficientPermissions];
  WITH f: fileD SELECT FROM
    local =>
      BEGIN
      IF volume = NIL THEN {IF f.immutable THEN RETURN WITH ERROR Error[immutable]}
      ELSE IF ~f.immutable THEN RETURN WITH ERROR Error[notImmutable];
      IF transaction#nullTransaction THEN
        BEGIN OPEN TransactionState;
        ENABLE {
          Volume.InsufficientSpace => GOTO InsufficientSpace;
          Transaction.InvalidHandle => GOTO InvalidTxHandle};
        entry: LogEntry ←
          [setContents[
            id: f.fileID, base: 0, size: f.size,
            makePermanent: ~f.temporary, makeImmutable: f.immutable]];
        Log[transaction, @entry, txFileProcs];
        entry ← [create[id: f.fileID, volume: f.volumeID, size: f.size, type: f.type]];
        Log[transaction, @entry, txFileProcs];
        EXITS
          InsufficientSpace => RETURN WITH ERROR Volume.InsufficientSpace;
          InvalidTxHandle => RETURN WITH ERROR Transaction.InvalidHandle;
        END;
      SELECT DeleteFileOnVolumeInternal[@f] FROM
        ok => NULL;
        volumeUnknown => RETURN WITH ERROR Volume.Unknown[f.volumeID];
        volumeNotOpen => RETURN WITH ERROR Volume.NotOpen[f.volumeID];
        ENDCASE;
      IF f.temporary THEN TmpsRemove[file, @f.volumeID];
      END;
    ENDCASE => RETURN WITH ERROR SystemInternal.Unimplemented;
  END;

GetFileAttributes: PUBLIC ENTRY PROCEDURE [file: Capability]
  RETURNS [size: PageCount, immutable: BOOLEAN, readOnly: BOOLEAN] =
  BEGIN
  f: FileInternal.Descriptor;
  IF ~GetFileDescriptor[@file, @f] THEN RETURN WITH ERROR Unknown[file];
  WITH f SELECT FROM
    local => RETURN[size, immutable, ~SufficientPermissions[file.permissions, write]];
    ENDCASE => RETURN WITH ERROR SystemInternal.Unimplemented
  END;

--DeleteTemps: PUBLIC PROCEDURE [volume: Volume.ID] =
--BEGIN

--DeleteTempsEntry: ENTRY PROCEDURE =
--BEGIN
--SELECT DeleteTempsInternal[
--@volume] FROM
--ok => NULL;
--volumeUnknown => RETURN WITH ERROR Volume.Unknown[volume];
--volumeNotOpen => RETURN WITH ERROR Volume.NotOpen[volume];
--ENDCASE;
--END;

--DeleteTempsEntry[];
--END;

-- Maintained by AltoSize
-- cacheA, cacheB: RECORD[file: File.ID, size: PageCount] ← [File.nullID, 0];
-- Access file attributes in label given fileID keeping 2 element MRU cache
--GetAltoSize: ENTRY PROCEDURE [file: POINTER TO READONLY File.Capability] RETURNS[size: PageCount] =
--BEGIN
--faP: LONG POINTER TO AltoFileDefs.FA = @LOOPHOLE[bufferPointer, LONG POINTER TO AltoFileDefs.LD].eofFA;
--IF file.fID=cacheA.file THEN RETURN[cacheA.size];
--IF file.fID=cacheB.file THEN size ← cacheB.size ELSE
--BEGIN
--SimpleSpace.Map[pageBuffer, Space.WindowOrigin[file↑, 0], FALSE];
--size ← faP.page+MIN[faP.byte, 1];
--SimpleSpace.Unmap[pageBuffer]
--END;
--cacheB ← cacheA; cacheA ← [file.fID, size]
--END;
-- Look up file in cache and then in all vfm's, set vID and page group descriptor or signal error.

GetVIDAndGroup: ENTRY PROCEDURE [
  pVID: POINTER TO Volume.ID, pGroup: POINTER TO FileInternal.PageGroup,
  pFile: POINTER TO READONLY File.Capability, filePage: File.PageNumber] =
  BEGIN
  fileD: FileInternal.Descriptor;
  IF ~GetFileDescriptor[pFile, @fileD] THEN RETURN WITH ERROR Unknown[pFile↑];
  WITH fileD SELECT FROM
    local =>
      BEGIN
      GetVIDAndGroup1: VolProc =
        BEGIN
        success: BOOLEAN;
        updateMarkers ← FALSE;
        [success, pGroup↑] ← FileCache.GetPageGroup[fileD.fileID, filePage];
        IF ~success THEN
          [success, pGroup↑] ← VolFileMap.GetPageGroup[volume, @fileD, filePage];
        IF ~(success AND filePage<pGroup.nextFilePage) THEN
          ERROR FileImplError[fileWithHole];
        END;

      pVID↑ ← fileD.volumeID;
      SELECT LogicalVolume.VolumeAccess[pVID, GetVIDAndGroup1] FROM
        ok => NULL;
        volumeUnknown => RETURN WITH ERROR Volume.Unknown[pVID↑];
        volumeNotOpen => RETURN WITH ERROR Volume.NotOpen[pVID↑];
        ENDCASE;
      END;
    remote => RETURN WITH ERROR SystemInternal.Unimplemented;
    ENDCASE;
  END;

LogContents: PUBLIC ENTRY PROCEDURE [
  transaction: Transaction.Handle, file: Capability, base: PageNumber,
  count: PageCount] =
  BEGIN OPEN TransactionState; ENABLE UNWIND => NULL;
  entry: LogEntry ← [setContents[id: file.fID, base: base, size: count]];
  Log[transaction, @entry, txFileProcs];
  END;

MakeBootableOrUnbootable: ENTRY PROCEDURE [
  op: {bootable, unbootable}, file: POINTER TO READONLY File.Capability,
  firstPage: File.PageNumber, count: File.PageCount,
  lastLink: DiskChannel.Address] RETURNS [firstLink: DiskChannel.Address] =
  BEGIN
  fileD: FileInternal.Descriptor;
  group: FileInternal.PageGroup;
  limitPage: File.PageNumber = firstPage+count;
  nextLink: DiskChannel.Address;
  page: File.PageNumber ← firstPage;
  success: BOOLEAN;
  GetStuff: VolProc =
    BEGIN
    updateMarkers ← FALSE;
    [success, group] ← VolFileMap.GetPageGroup[volume, @fileD, page];
    IF ~success THEN ERROR FileImplError[makeBootablePageGroupNotFound];
    IF firstPage=page THEN
      firstLink ← SubVolume.GetPageAddress[fileD.volumeID,
        group.volumePage+(firstPage-group.filePage)].address;
    SELECT TRUE FROM
      op=unbootable => nextLink ← [0, 0, 0];
      group.nextFilePage>=limitPage =>
        {group.nextFilePage ← limitPage; nextLink ← lastLink};
      ENDCASE => -- should check success of GetPageGroup in next statement
        nextLink ← SubVolume.GetPageAddress[fileD.volumeID,
          VolFileMap.GetPageGroup[volume, @fileD,
            group.nextFilePage].group.volumePage].address;
    IF group.filePage>=group.nextFilePage THEN
      ERROR FileImplError[makeBootableImpossibleError];
    group.volumePage ← group.volumePage+(group.nextFilePage-group.filePage)-1;
    group.filePage ← group.nextFilePage-1;
    END;

  TransferLabels: PROCEDURE = INLINE
    BEGIN
    SimpleSpace.Map[pageBuffer, [[fileD.fileID, File.read], group.filePage], TRUE, 1];
    [] ← LabelTransfer.WriteLabelAndData[
      fileD, group.filePage, group.volumePage, bufferPage, nextLink];
    SimpleSpace.Unmap[pageBuffer];
    END;

    IF ~GetFileDescriptor[file, @fileD] THEN RETURN WITH ERROR Unknown[file↑];
  IF (WITH fD: fileD SELECT FROM local => limitPage>fD.size, ENDCASE => TRUE)
    THEN RETURN WITH ERROR InvalidParameters;
  WHILE page<limitPage DO
    SELECT LogicalVolume.VolumeAccess[@fileD.volumeID, GetStuff] FROM
      ok => NULL;
      volumeUnknown => RETURN WITH ERROR Volume.Unknown[fileD.volumeID];
      volumeNotOpen => RETURN WITH ERROR Volume.NotOpen[fileD.volumeID];
      ENDCASE;
    IF success THEN TransferLabels[];
    page ← group.nextFilePage;
    ENDLOOP;
  END;

OpenVolumeAndDeleteTemps: PUBLIC ENTRY PROCEDURE [
  volume: POINTER TO READONLY Volume.ID] =
  BEGIN
  SELECT LogicalVolume.OpenLogicalVolume[volume] FROM
    wasOpen => RETURN;
    ok => NULL;
    Unknown => RETURN WITH ERROR Volume.Unknown[volume↑];
    VolumeNeedsScavenging => RETURN WITH ERROR Volume.NeedsScavenging;
    ENDCASE;
  IF PilotSwitches.switches.u=up THEN IF DeleteTempsInternal[volume]#ok THEN
    ERROR FileImplError[volumeWentAwayDuringDeleteTemps];
  END;

Pin: PUBLIC ENTRY PROCEDURE [
  file: File.Capability, page: File.PageNumber, count: File.PageCount] =
  BEGIN
  fileD: FileInternal.Descriptor;
  after: File.PageNumber;
  IF ~GetFileDescriptor[@file, @fileD] THEN RETURN WITH ERROR Unknown[file];
  WITH f: fileD SELECT FROM
    local =>
      BEGIN
      p: FileInternal.LocalFilePtr ← @f; -- FOR COMPILER BUG
      Pin1: VolProc =
        BEGIN
        success: BOOLEAN;
        group: FileInternal.PageGroup ← [, , page];
        FileCache.SetFile[fileD, TRUE];
        updateMarkers ← FALSE;
        WHILE group.nextFilePage<after DO
          [success, group] ← VolFileMap.GetPageGroup[volume, p, group.nextFilePage];
          IF ~success THEN ERROR FileImplError[missingPinnedPageGroup];
          FileCache.SetPageGroup[file.fID, group, TRUE]
          ENDLOOP;
        END;

      after ← IF count=KernelFile.defaultPageCount THEN p.size ELSE page+count;
      SELECT LogicalVolume.VolumeAccess[@f.volumeID, Pin1] FROM
        ok => NULL;
        volumeUnknown => RETURN WITH ERROR Volume.Unknown[f.volumeID];
        volumeNotOpen => RETURN WITH ERROR Volume.NotOpen[f.volumeID];
        ENDCASE;
      END;
    remote => RETURN WITH ERROR SystemInternal.Unimplemented;
    ENDCASE;
  END;

SetSize: PUBLIC ENTRY PROCEDURE [
  file: Capability, size: PageCount, transaction: Transaction.Handle] =
  BEGIN ENABLE UNWIND => NULL;
  fileD: FileInternal.Descriptor;
  IF ~GetFileDescriptor[@file, @fileD] THEN RETURN WITH ERROR Unknown[file];
  IF ~SufficientPermissions[file.permissions, File.write] THEN
    RETURN WITH ERROR Error[insufficientPermissions];
  WITH f: fileD SELECT FROM
    local =>
      BEGIN
      IF f.immutable THEN RETURN WITH ERROR Error[immutable];
      IF transaction#nullTransaction THEN IF size#f.size THEN
        BEGIN OPEN TransactionState;
        entry: LogEntry ← (SELECT size FROM
          >f.size => [shrink[id: file.fID, size: f.size]],
          ENDCASE => [setContents[
            id: file.fID, base: size, size: f.size-size]]);
        Log[transaction, @entry, txFileProcs !
          Volume.InsufficientSpace => GOTO InsufficientSpace;
          Transaction.InvalidHandle => GOTO InvalidTxHandle];
        EXITS
          InsufficientSpace => RETURN WITH ERROR Volume.InsufficientSpace;
          InvalidTxHandle => RETURN WITH ERROR Transaction.InvalidHandle;
        END;
      SELECT SetSizeInternal[@f, @size, file.permissions] FROM
        ok => RETURN;
        insufficientSpace => RETURN WITH ERROR Volume.InsufficientSpace;
        insufficientPermissions => RETURN WITH ERROR Error[insufficientPermissions];
        ENDCASE;
      END;
    remote => RETURN WITH ERROR SystemInternal.Unimplemented;
    ENDCASE;
  END;

-- this is its own entry procedure since it can get many nasty errors

TmpsEnter: ENTRY PROCEDURE [
  f: POINTER TO READONLY File.Capability, v: POINTER TO READONLY Volume.ID] =
  BEGIN

  FindIt: INTERNAL PROCEDURE RETURNS [BOOLEAN] = INLINE
    BEGIN
    tOriginal: LONG POINTER TO File.ID ← tmps;
    DO
      IF tmps↑=File.nullID THEN RETURN[TRUE];
      IF (tmps ← tmps+SIZE[File.ID])=tmpsLast THEN tmps ← tmpStart;
      IF tmps=tOriginal THEN EXIT;
      ENDLOOP;
    RETURN[FALSE];
    END;

  SELECT TmpsGet[v, last] FROM
    ok => NULL;
    volumeUnknown => RETURN WITH ERROR Volume.Unknown[v↑];
    notOpen => RETURN WITH ERROR Volume.NotOpen[v↑];
    noFile => RETURN; -- next delete tmps will be hard way

    ENDCASE;
  IF ~FindIt[] THEN IF ~TmpsGrow[] THEN RETURN WITH ERROR Volume.InsufficientSpace;
  tmps↑ ← f.fID;
  SimpleSpace.ForceOut[tmpsBuffer];
  END;

-- INTERNAL PROCEDURES

ChangeAttributesInternal: INTERNAL PROCEDURE [
  fileD: LocalFilePtr, action: AttributeAction]
  RETURNS [s: LogicalVolume.VolumeAccessStatus] =
  BEGIN
  file: Capability ← [fileD.fileID, maxPermissions];
  link: DiskChannel.Address ← LOOPHOLE[LONG[0]];
  volumePage: LogicalVolume.PageNumber;
  volID: Volume.ID ← fileD.volumeID; -- FOR COMPILER BUG
  GetGroup0: VolProc =
    BEGIN
    group: FileInternal.PageGroup;
    success: BOOLEAN;
    updateMarkers ← FALSE;
    [success, group] ← VolFileMap.GetPageGroup[volume, fileD, 0];
    IF ~success THEN ERROR FileImplError[missingPage0];
    volumePage ← group.volumePage;
    END;

  SELECT s ← LogicalVolume.VolumeAccess[@volID, GetGroup0] FROM
    ok => NULL;
    volumeUnknown, volumeNotOpen => RETURN;
    ENDCASE;
  IF LabelTransfer.VerifyLabels[
        fileD↑, [0, volumePage, 1], FALSE, FALSE].status ~= goodCompletion
    THEN SIGNAL LabelError;

  -- smash time, folks.  Watch out for zero size file

  IF action=permanent OR action=temporary THEN
    BEGIN
    IF fileD.type IN PilotFileTypes.PilotRootFileType THEN
      LogicalVolume.PutRootFile[@volID, fileD.type, @file !
        Volume.Unknown => GOTO VUnknown;
        Volume.NotOpen => GOTO VNotOpen];
    fileD.temporary ← action=temporary;
    EXITS
      VUnknown => RETURN[volumeUnknown];
      VNotOpen => RETURN[volumeNotOpen];
    END
  ELSE fileD.immutable ← action=immutable;

  SimpleSpace.Map[
    pageBuffer, IF fileD.size=0 THEN Space.defaultWindow ELSE [file, 0], TRUE];
  -- There's a small race here, since the label on the disk is now inconsistent with
  -- the information in the FileCache.  If someone tries to access page 0 before we
  -- get the file cache updated, they will get a label check.  The only fix for this
  -- is to do the label transfer inside the FileCache monitor (ugh) or arrange for the
  -- a recomputation of the label info and a retry of the i/o.  Neither is easy, so
  -- we'll leave the race in until Klamath.  Note that calling FileCache.FlushFile is
  -- bad too (that's what used to be done), because it flushes pinned page groups, causing
  -- disaster elsewhere.  That problem is more severe than the remaining one.
  LOOPHOLE[link, PilotDisk.Address] ←
    LabelTransfer.ReadLabel[fileD↑, 0, volumePage].label.bootChainLink;
  [] ← LabelTransfer.WriteLabelAndData[
    fileD↑, 0, volumePage, SimpleSpace.Page[pageBuffer], link];
  FileCache.SetFile[fileD↑, FALSE];
  SimpleSpace.Unmap[pageBuffer];
  IF action=permanent THEN TmpsRemove[@file, @volID];
  END;

CreateWithIDInternal: INTERNAL PROCEDURE [
  volume: POINTER TO READONLY System.VolumeID,
  initialSize: POINTER TO READONLY PageCount, type: File.Type,
  file: POINTER TO READONLY File.ID]
  RETURNS [s: LogicalVolume .FileVolumeStatus[ok..insufficientSpace]] =
  -- Create file given the file identifier (called by CreateWithIDExternal,
  -- OpenVolumeAndDeleteTemps). Expect to see Volume.Unknown, 
  -- Volume.NotOpen, Volume.InsufficientSpace
  BEGIN
  fileD: local FileInternal.Descriptor ← [file↑, volume↑, local[FALSE, TRUE, 0, type]];
  CreateInner: VolProc =
    BEGIN
    group: FileInternal.PageGroup ← [, 0, 0];
    updateMarkers ← FALSE;
    IF LogicalVolume.FreeVolumePages[volume] <= initialSize↑ THEN
      s ← insufficientSpace
    ELSE
      BEGIN
      DO
        group ←
          [group.nextFilePage,
          group.volumePage+group.nextFilePage-group.filePage,
          initialSize↑];
        VolAllocMap.AllocPageGroup[volume, LONG[@fileD], @group, TRUE];
        -- results in modified group and FilePtr.size
        VolFileMap.InsertPageGroup[volume, LONG[@fileD], @group];
        IF fileD.size=initialSize↑ THEN EXIT;
        ENDLOOP;
      FileCache.SetFile[fileD, FALSE];
      END;
    END;

  s ← ok;
  SELECT LogicalVolume.VolumeAccess[@fileD.volumeID, CreateInner, TRUE] FROM
    ok => NULL;
    volumeUnknown => s ← volumeUnknown;
    volumeNotOpen => s ← volumeNotOpen;
    ENDCASE;
  END;

DeleteFileOnVolumeInternal: INTERNAL PROCEDURE [
  fileD: FileInternal.LocalFilePtr]
  RETURNS [s: LogicalVolume.VolumeAccessStatus] =
  BEGIN
  vID: Volume.ID ← fileD.volumeID;
  DeleteIt: VolProc =
    BEGIN
    g: FileInternal.PageGroup;
    success: BOOLEAN;
    FileCache.FlushFile[fileD.fileID];
    updateMarkers ← FALSE;
    DO
      [success, g] ← VolFileMap.GetPageGroup[volume, fileD, File.lastPageNumber];
      -- group.nextFile page is last page in file
      IF ~success THEN EXIT;
      g ← [0, LogicalVolume.nullVolumePage, g.nextFilePage];
      VolFileMap.DeletePageGroup[volume, fileD, @g]; -- results in modified group
      VolAllocMap.FreePageGroup[volume, fileD, @g, TRUE]; -- results in modified fileD
      ENDLOOP;
    END;

  RETURN[LogicalVolume.VolumeAccess[@vID, DeleteIt, TRUE]];
  END;

DeleteTempsInternal: INTERNAL PROCEDURE [v: POINTER TO READONLY Volume.ID]
  RETURNS [s: LogicalVolume.VolumeAccessStatus] =
  BEGIN
  one: File.PageCount ← 1;
  SELECT TmpsGet[v, first] FROM
    noFile => s ← DeleteTmpsInternalOld[v];
    ok => s ← DeleteTmpsInternalNew[v]; -- also deletes tempFile

    volumeUnknown => RETURN[volumeUnknown];
    notOpen => RETURN[volumeNotOpen];
    ENDCASE;
  IF s#ok THEN RETURN; -- Create a tmps file.
  tmpsFile ← [[System.GetUniversalID[]], FileInternal.maxPermissions];
  SELECT CreateWithIDInternal[v, @one, PilotFileTypes.tTempFileList, @tmpsFile.fID] FROM
    insufficientSpace => RETURN; -- there will be no temp file; that's Ok
    volumeUnknown, volumeNotOpen => FileImplError[tmpsVolumeWentAway];
    ENDCASE;
  LogicalVolume.PutRootFile[v, PilotFileTypes.tTempFileList, @tmpsFile !
    Volume.Unknown, Volume.NotOpen => FileImplError[tmpsVolumeWentAway]];
  END;

DeleteTmpsInternalNew: INTERNAL PROCEDURE [v: POINTER TO READONLY Volume.ID]
  RETURNS [s: LogicalVolume.VolumeAccessStatus] =
  BEGIN
  cap: File.Capability ← [File.nullID, File.delete];
  fileD: FileInternal.Descriptor;
  p: File.PageNumber;
  t: LONG POINTER TO File.ID;
  FOR p ← 0, p+1 WHILE p<tmpsFileSize DO
    TmpsMap[p];
    FOR t ← tmpStart, t+SIZE[File.ID] WHILE t#tmpsLast DO
      IF t↑=File.nullID THEN LOOP;
      cap.fID ← t↑;
      IF GetFileDescriptor[@cap, @fileD, v] THEN
        WITH f: fileD SELECT FROM
          local =>
            IF f.temporary THEN IF (s ← DeleteFileOnVolumeInternal[@f])#ok THEN RETURN
              ELSE LOOP;
          ENDCASE; -- ingore remote files (can't happen), and delete errors

      ENDLOOP;
    ENDLOOP;
  TmpsUnmap[]; -- finally, delete the file itself.
  IF GetFileDescriptor[@tmpsFile, @fileD, v] THEN
    WITH f: fileD SELECT FROM
      local => RETURN[DeleteFileOnVolumeInternal[@f]];
      ENDCASE;
  ERROR FileImplError[disappearedOrRemoteTempFile];
  END;

DeleteTmpsInternalOld: INTERNAL PROCEDURE [v: POINTER TO READONLY Volume.ID]
  RETURNS [s: LogicalVolume.VolumeAccessStatus] =
  BEGIN
  lastCap: File.Capability ← File.nullCapability;
  thisCap: File.Capability;
  fileD: FileInternal.Descriptor;
  DO
    thisCap ← KernelFile.GetNextFile[v↑, lastCap !
      Volume.Unknown => GOTO unknown;
      Volume.NotOpen => GOTO notOpen];
    IF thisCap=File.nullCapability THEN EXIT;
    IF GetFileDescriptor[@thisCap, @fileD, v] THEN
      WITH f: fileD SELECT FROM
        local =>
          IF f.temporary THEN
            IF (s ← DeleteFileOnVolumeInternal[@f])#ok THEN RETURN ELSE LOOP;
        ENDCASE; -- ignore remote files (can't happen)
    lastCap ← thisCap;
    ENDLOOP;
  RETURN[ok];
  EXITS unknown => RETURN[volumeUnknown]; notOpen => RETURN[volumeNotOpen];
  END;

SetSizeInternal: INTERNAL PROCEDURE [
  fileD: FileInternal.LocalFilePtr, size: POINTER TO READONLY File.PageCount,
  permissions: File.Permissions] RETURNS [s: LogicalVolume.FileVolumeStatus] =
  BEGIN
  vID: Volume.ID ← fileD.volumeID;
  vs: LogicalVolume.VolumeAccessStatus;
  SizeIt: VolProc =
    BEGIN
    success: BOOLEAN;
    group: FileInternal.PageGroup;
    -- if the file has pinned cache entries, the following is bad, but we don't have
    -- any other good way to deal with it unless the FileCache interface is changed.  Better
    -- to wait for Klamath.
    FileCache.FlushFile[fileD.fileID];
    updateMarkers ← FALSE;
    SELECT fileD.size FROM
      <size↑ =>
        BEGIN
        IF ~SufficientPermissions[permissions, File.write+File.grow] THEN
          s ← insufficientPermissions
        ELSE
          IF LogicalVolume.FreeVolumePages[volume]<size↑-fileD.size THEN
            s ← insufficientSpace
          ELSE
            BEGIN
            [success, group] ←
              VolFileMap.GetPageGroup[volume, fileD, fileD.size-MIN[fileD.size, 1]];
            IF ~success THEN ERROR FileImplError[missingPageGroupSetSize];
            WHILE fileD.size<size↑ DO
              group ←
                [group.nextFilePage,
                group.volumePage+(group.nextFilePage-group.filePage),
                size↑];
              VolAllocMap.AllocPageGroup[volume, fileD, @group, FALSE];
              -- change group+fileD
              VolFileMap.InsertPageGroup[volume, fileD, @group];
              ENDLOOP
            END
        END;
      >size↑ =>
        BEGIN
        IF ~SufficientPermissions[permissions, File.write+File.shrink] THEN
          s ← insufficientPermissions
        ELSE
          BEGIN
          WHILE fileD.size>size↑ DO
            group ← [size↑, LogicalVolume.nullVolumePage, fileD.size];
            VolFileMap.DeletePageGroup[volume, fileD, @group]; -- changes group
            VolAllocMap.FreePageGroup[volume, fileD, @group, FALSE];  -- changes FilePtr
            ENDLOOP;
          END;
        END;
      ENDCASE;
    IF s = ok THEN FileCache.SetFile[fileD↑, FALSE];
    END;
  s ← ok;
  IF (vs ← LogicalVolume.VolumeAccess[@vID, SizeIt, TRUE]) ~= ok THEN RETURN [vs];
  RETURN[s]
  END;

TmpsGet: INTERNAL PROCEDURE [
  v: POINTER TO READONLY Volume.ID, pg: {first, last}]
  RETURNS [{ok, volumeUnknown, notOpen, noFile}] =
  BEGIN
  IF v↑=Volume.nullID THEN RETURN[volumeUnknown];
  IF tmpsVolume#v↑ THEN
    BEGIN
    fileD: FileInternal.Descriptor;
    TmpsUnmap[];
    tmpsFile ← KernelFile.GetRootFile[PilotFileTypes.tTempFileList, v↑ !
      Volume.Unknown => GOTO unknown;
      Volume.NotOpen => GOTO notOpen];
    IF ~GetFileDescriptor[@tmpsFile, @fileD, v] THEN RETURN[noFile];
    WITH fileD SELECT FROM
      local => tmpsFileSize ← size;
      ENDCASE => ERROR FileImplError[remoteTmpsFile];
    TmpsMap[IF pg=first THEN 0 ELSE tmpsFileSize-1];
    tmpsVolume ← v↑;
    EXITS unknown => RETURN[volumeUnknown]; notOpen => RETURN[notOpen];
    END;
  RETURN[ok];
  END;

TmpsGrow: INTERNAL PROCEDURE RETURNS [BOOLEAN] =
  BEGIN
  fileD: FileInternal.Descriptor;
  newSize: File.PageCount ← tmpsFileSize+1;
  IF ~GetFileDescriptor[@tmpsFile, @fileD, @tmpsVolume] THEN
    ERROR FileImplError[tmpsFileWentAway];
  WITH f: fileD SELECT FROM
    local =>
      SELECT SetSizeInternal[@f, @newSize, File.grow+File.write] FROM
        insufficientSpace => RETURN[FALSE];
        ok => NULL;
        ENDCASE => FileImplError[tmpsFileProblem];
    ENDCASE => ERROR FileImplError[remoteTmpsFile];
  TmpsMap[tmpsFileSize];
  tmpsFileSize ← tmpsFileSize+1;
  RETURN[TRUE];
  END;

TmpsMap: INTERNAL PROCEDURE [page: File.PageNumber] = INLINE
  BEGIN
  IF tmpsVolume#nullID THEN SimpleSpace.Unmap[tmpsBuffer];
  SimpleSpace.Map[tmpsBuffer, [tmpsFile, page], FALSE];
  tmps ← tmpStart;
  END;

TmpsRemove: INTERNAL PROCEDURE [
  f: POINTER TO READONLY File.Capability, v: POINTER TO READONLY Volume.ID] =
  BEGIN
  t: LONG POINTER TO File.ID ← tmps;
  IF v↑#Volume.nullID -- impossible, he says-- AND tmpsVolume=v↑ THEN
    DO
      -- only cheap wins
      IF t↑=f.fID THEN BEGIN t↑ ← File.nullID; RETURN; END;
      IF t=tmpStart THEN t ← tmpsLast;
      IF (t ← t-SIZE[File.ID])=tmps THEN EXIT;
      ENDLOOP;
  END;

TmpsUnmap: INTERNAL PROCEDURE = INLINE
  BEGIN
  IF tmpsVolume#Volume.nullID THEN
    BEGIN SimpleSpace.Unmap[tmpsBuffer]; tmpsVolume ← Volume.nullID; END;
  END;

-- Here follows a gross kludge to allow the transaction machinery to sneak
-- past our monitor lock.

txFileProcs: TransactionState.FileOps = [TxCreate, TxMakePerm, TxSetSize];

TxCreate: INTERNAL PROCEDURE [size: PageCount, type: Type] RETURNS [file: ID] =
  BEGIN
  volID: Volume.ID ← Volume.systemID;
  file ← [System.GetUniversalID[]];
  SELECT CreateWithIDInternal[@volID, @size, type, @file] FROM
    ok => NULL;
    insufficientSpace => ERROR Volume.InsufficientSpace;
    ENDCASE;
  END;

TxMakePerm: INTERNAL PROCEDURE [file: ID] =
  BEGIN
  fileC: Capability ← [file, maxPermissions];
  fileD: FileInternal.Descriptor;
  IF ~GetFileDescriptor[@fileC, @fileD] THEN ERROR Unknown[fileC];
  WITH f: fileD SELECT FROM
    local =>
      BEGIN
      SELECT ChangeAttributesInternal[@f, permanent] FROM
        volumeUnknown => ERROR Volume.Unknown[f.volumeID];
        volumeNotOpen => ERROR Volume.NotOpen[f.volumeID];
        ENDCASE;
      END;
    ENDCASE => ERROR FileImplError[remoteTxLog];
  END;

TxSetSize: INTERNAL PROCEDURE [file: ID, size: PageCount] =
  BEGIN
  fileC: Capability ← [file, maxPermissions];
  fileD: FileInternal.Descriptor;
  IF ~GetFileDescriptor[@fileC, @fileD] THEN ERROR Unknown[fileC];
  WITH f: fileD SELECT FROM
    local =>
      BEGIN
      SELECT SetSizeInternal[@f, @size, maxPermissions] FROM
        insufficientSpace => ERROR Volume.InsufficientSpace;
        volumeUnknown => ERROR Volume.Unknown[f.volumeID];
        volumeNotOpen => ERROR Volume.NotOpen[f.volumeID];
        ENDCASE;
      END;
    ENDCASE => ERROR FileImplError[remoteTxLog];
  END;

-- Initialization

InitializeFileMgr: PUBLIC PROCEDURE [
    bootFile: LONG POINTER TO disk Boot.Location,
    pLVBootFiles: POINTER TO Boot.LVBootFiles]
  RETURNS [debuggerDeviceType: Device.Type, debuggerDeviceOrdinal: CARDINAL] =
  BEGIN
  priorityPrev: Process.Priority;
  throwAway: PROCESS;
  IF PilotSwitches.switches.f=down THEN Runtime.CallDebugger["Key Stop F"L];
  priorityPrev ← Process.GetPriority[];
  Process.SetPriority[ProcessPriorities.priorityPageFaultHigh];
  throwAway ← FORK FileHelperProcess[];  -- (no profit in Detaching)
  Process.SetPriority[priorityPrev];
  START FMPrograms.VolAllocMapImpl;
  START FMPrograms.VolFileMapImpl;
  START FMPrograms.MarkerPageImpl;
  [debuggerDeviceType, debuggerDeviceOrdinal] ←
      START FMPrograms.PhysicalVolumeImpl[bootFile, pLVBootFiles];
  -- ..also starts VolumeImpl and ScavengeImpl.
  END;


END.

(For earlier log entries, see Pilot 4.0 archive version.)

April 1, 1980  2:47 PM        Gobbel
        Added transaction handles

April 15, 1980  4:37 PM        Gobbel
        Added nullTransactionHandle

April 17, 1980  10:08 PM        Luniewski
        Added TransactionHandle argument to GetSize and GetAttributes

April 18, 1980  2:59 PM        Luniewski
        Temporary exportation of transaction.nullHandle added

May 15, 1980  6:11 PM        McJones
        Add page and count parameters to Pin

May 30, 1980  3:07 PM        Luniewski
        PhysicalVolume removed from DIRECTORY.  Made compatible with
        FileInternal.FileDescriptors being LONG POINTERs.  STARTed
        PhysicalVolumeImpl.  Removed START of VolumeImpl as it is now started
        by PhysicalVolumeImpl.  LogicalVolume.{Open Close}Volume =>
        LogicalVolume.{Open Close}LogicalVolume.

June 10, 1980  1:20 AM        Gobbel
        Added MakeTemporary and MakeMutable

June 10, 1980  3:10 PM        Gobbel
        Added transaction logging code

July 19, 1980  11:15 AM        McJones
        Deleted nullTransactionHandle and (Transaction.)nullHandle; adapted
        for new PilotDisk.Label; removed dependency in initialization of
        tmpsLast on SIZE[File.ID]

July 25, 1980  3:32 PM        Luniewski
        SpecialVolume.(NotOpen VolumeNeedsScavenging) => Volume.(NotOpen
        NeedsScavenging)

August 11, 1980  5:24 PM        Gobbel/Knutsen
        Restructure Create to allow export of CreateWithID to KernelFile for
        transaction crash recovery

August 14, 1980  11:15 AM        McJones
        Delete dependencies on tBootFile; FilePageLabel=>PilotDisk; require
        File.write in SetSizeInternal

August 21, 1980  2:10 PM        Gobbel
        Restructure transactional operations to use new interface for
        managing log files

September 12, 1980  12:07 PM        Gobbel
        Make GetFileDescriptor look at all volume types

September 17, 1980  3:07 PM        Luniewski
          Mods for new priocedure type passed to LogicalVolume.VolumeAccess.

September 26, 1980  4:29 PM        McJones
        GetBootLocation forgot to add offset to volume page

December 31, 1980  12:18 PM        Gobbel
        Changed tx log entries to have ids instead of capabilities.

January 12, 1981  12:18 PM        Luniewski
        New LabelTransfer interface.

January 12, 1981  12:18 PM        Knutsen
        Changed process priorities.

January 26, 1981        Knutsen
        Renamed nullTransactionHandle to nullTransaction (export name conflict).

February 11, 1981  5:28 PM        Knutsen
        FilePageTransfer.Initiate now MStore.Promises itself.

February 24, 1981  6:15 PM        Gobbel
        Changed all procs that take transaction handles to be sure that monitor lock will
        not be held in client catch phrase.

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

November 11, 1982 3:01 pm		Levin
	Move all relevant FileCache operations inside volume access lock to ensure proper atomicity of file cache.

December 8, 1982 5:11 pm		Levin
	Correct access to FileCache in ChangeAttributesInternal.

December 9, 1982 11:37 am		Levin
	Replace lost "s ← ok" in SetSizeInternal.