-- Files.Mesa  Edited by Geschke on September 13, 1978  2:55 PM

DIRECTORY
  AltoDefs: FROM "altodefs" USING [BytesPerPage, MaxFilePage],
  AltoFileDefs: FROM "altofiledefs" USING [
    CFA, eofDA, FA, FP, LD, SN, TIME, vDA],
  BFSDefs: FROM "bfsdefs" USING [CreatePages, DeletePages],
  BootDefs: FROM "bootdefs" USING [
    AllocateObject, EnumerateObjects, LiberateObject, ValidateObject],
  DirectoryDefs: FROM "directorydefs" USING [
    DirectoryLookup, DirectoryPurgeFP],
  DiskKDDefs: FROM "diskkddefs" USING [UpdateDiskKD],
  InlineDefs: FROM "inlinedefs" USING [BITAND, BITOR],
  MiscDefs: FROM "miscdefs",
  NucleusDefs: FROM "nucleusdefs",
  SegmentDefs: FROM "segmentdefs" USING [
    AccessOptions, Append, DataSegmentAddress, DataSegmentHandle,
    DefaultAccess, DefaultBase, DeleteDataSegment, DeleteFileSegment,
    FileHandle, FileObject, FileSegmentAddress, FileSegmentHandle, JumpToPage,
    LengthHandle, LengthObject, MaxLocks, NewDataSegment, NewFileOnly,
    NewFileSegment, Object, OldFileOnly, PageNumber, Read, SwapIn, Unlock,
    VersionOptions, Write];
 
DEFINITIONS FROM AltoFileDefs, SegmentDefs;

Files: PROGRAM
  IMPORTS BFSDefs, BootDefs, DirectoryDefs, DiskKDDefs, SegmentDefs
  EXPORTS BootDefs, MiscDefs, NucleusDefs, SegmentDefs SHARES SegmentDefs =
  BEGIN

  FileError: PUBLIC SIGNAL [file:FileHandle] = CODE;
  FileNameError: PUBLIC SIGNAL [name:STRING] = CODE;
  FileAccessError: PUBLIC SIGNAL [file:FileHandle] = CODE;

  NullFileObject: FileObject = Object [FALSE,
    file [FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,0,FALSE,0,0,0,
    FP[SN[1,0,1,17777B,177777B],eofDA]]];

  NewFile: PUBLIC PROCEDURE [
    name:STRING, access:AccessOptions, version:VersionOptions]
    RETURNS [file:FileHandle] =
    BEGIN OPEN InlineDefs;
    fp: FP;  old, create: BOOLEAN;
    [access,version] ← ValidateOptions[access,version];
    create ← BITAND[version,OldFileOnly]=0;
    old ← DirectoryDefs.DirectoryLookup[@fp,name,create];
    IF (old AND BITAND[version,NewFileOnly]#0)
    OR (~old AND ~create) THEN ERROR FileNameError[name];
    IF (file ← FindFile[@fp]) = NIL THEN
      BEGIN
      file ← AllocateFile[];
      file↑ ← NullFileObject;
      file.fp ← fp;
      END;
    SetFileAccess[file,access];
    RETURN
    END;

  InsertFile: PUBLIC PROCEDURE [fp:POINTER TO FP, access:AccessOptions]
    RETURNS [file:FileHandle] =
    BEGIN
    [access,] ← ValidateOptions[access,0];
    IF (file ← FindFile[fp]) = NIL THEN
      BEGIN
      file ← AllocateFile[];
      file↑ ← NullFileObject;
      file.fp ← fp↑;
      END;
    SetFileAccess[file,access];
    RETURN
    END;

  BootFile: PUBLIC PROCEDURE [access:AccessOptions] RETURNS [file: FileHandle] =
    BEGIN
    [access,] ← ValidateOptions[access,0];
    file ← AllocateFile[];
    file↑ ← NullFileObject;
    SetFileAccess[file,access];
    RETURN
    END;

  InsertFileLength: PUBLIC PROCEDURE [file: FileHandle, fa: POINTER TO FA] =
    BEGIN
    lh: LengthHandle ← AllocateLength[file];
    [da: lh.da, page: lh.page, byte: lh.byte] ← fa↑;
    RETURN
    END;

  ValidateOptions: PROCEDURE [access:AccessOptions, version:VersionOptions]
    RETURNS [AccessOptions, VersionOptions] =
    BEGIN OPEN InlineDefs;
    IF access = DefaultAccess THEN access ← Read;
    -- IF version = DefaultVersion THEN version ← 0;
    IF BITAND[version,NewFileOnly+OldFileOnly] = NewFileOnly+OldFileOnly
    OR (BITAND[version,NewFileOnly]#0 AND BITAND[access,Append]=0)
      THEN ERROR FileAccessError[NIL];
    IF BITAND[access,Append]=0 THEN
      version ← BITOR[version,OldFileOnly];
    RETURN[access,version]
    END;

  GetFileAccess: PUBLIC PROCEDURE [file:FileHandle] RETURNS [AccessOptions] =
    BEGIN
    access: AccessOptions ← 0;
    ValidateFile[file];
    IF file.read THEN access ← access+Read;
    IF file.write THEN access ← access+Write;
    IF file.append THEN access ← access+Append;
    RETURN[access]
    END;

  SetFileAccess: PUBLIC PROCEDURE [file:FileHandle, access:AccessOptions] =
    BEGIN OPEN InlineDefs;
    ValidateFile[file];
    IF access = DefaultAccess THEN access ← Read;
    file.read ← file.read OR BITAND[access,Read]#0;
    file.write ← file.write OR BITAND[access,Write]#0;
    file.append ← file.append OR BITAND[access,Append]#0;
    RETURN
    END;

  LockFile: PUBLIC PROCEDURE [file:FileHandle] =
    BEGIN OPEN file;
    ValidateFile[file];
    IF lock = MaxLocks THEN ERROR FileError[file];
    lock ← lock+1;
    RETURN
    END;

  UnlockFile: PUBLIC PROCEDURE [file:FileHandle] =
    BEGIN OPEN file;
    ValidateFile[file];
    IF lock = 0 THEN ERROR FileError[file];
    lock ← lock-1;
    RETURN
    END;

  ReleaseFile: PUBLIC PROCEDURE [file:FileHandle] =
    BEGIN
    [] ← PurgeFile[file];
    DiskKDDefs.UpdateDiskKD[];
    RETURN
    END;

  DestroyFile: PUBLIC PROCEDURE [file:FileHandle] =
    BEGIN
    seg: DataSegmentHandle;
    fp: FP ← file.fp;
    IF ~PurgeFile[file] OR ~DirectoryDefs.DirectoryPurgeFP[@fp]
      THEN ERROR FileError[file];
    seg ← NewDataSegment[DefaultBase,1];
    BFSDefs.DeletePages[DataSegmentAddress[seg],@fp,fp.leaderDA,0
      ! UNWIND => DeleteDataSegment[seg]];
    DeleteDataSegment[seg];
    DiskKDDefs.UpdateDiskKD[];
    RETURN
    END;

  PurgeFile: PROCEDURE [file:FileHandle] RETURNS [BOOLEAN] =
    BEGIN OPEN file;
    ValidateFile[file];
    IF segcount # 0 THEN ERROR FileError[file];
    IF lock # 0 THEN RETURN[FALSE];
    CloseFile[file];
    IF file.length THEN LiberateLength[FindLength[file]];
    LiberateFile[file];
    RETURN[TRUE]
    END;


  -- File length stuff

  NormalizeFileIndex: PUBLIC PROCEDURE [page:PageNumber, byte:CARDINAL]
    RETURNS [PageNumber, CARDINAL] =
    BEGIN
    page ← page + byte/AltoDefs.BytesPerPage;
    byte ← byte MOD AltoDefs.BytesPerPage;
    RETURN[page,byte]
    END;

  RoundFileIndex: PUBLIC PROCEDURE [page:PageNumber, byte:CARDINAL]
    RETURNS [PageNumber, CARDINAL] =
    BEGIN
    [page, byte] ← NormalizeFileIndex[page, byte];
    IF byte = AltoDefs.BytesPerPage THEN
      BEGIN  byte ← 0; page ← page+1; END;
    RETURN[page,byte]
    END;

  TruncateFileIndex: PUBLIC PROCEDURE [page:PageNumber, byte:CARDINAL]
    RETURNS [PageNumber, CARDINAL] =
    BEGIN
    [page, byte] ← NormalizeFileIndex[page, byte];
    IF page > 0 AND byte = 0 THEN
      BEGIN  page ← page-1; byte ← AltoDefs.BytesPerPage END;
    RETURN[page,byte]
    END;

  GetEndOfFile: PUBLIC PROCEDURE [file:FileHandle]
    RETURNS [page: PageNumber, byte: CARDINAL] =
    BEGIN OPEN file;
    lh: LengthHandle;
    cfa: CFA;  seg: DataSegmentHandle;
    ValidateFile[file];
    lh ← AllocateLength[file];
    IF ~lengthvalid THEN
      BEGIN
      next: vDA;
      IF ~open THEN OpenFile[file];
      IF lh.da = eofDA THEN GetLengthHint[lh];
      seg ← NewDataSegment[DefaultBase,1];
      cfa ← CFA[fp,FA[lh.da, lh.page, lh.byte]];
      [prev: , next: next] ← JumpToPage [@cfa,lh.page,DataSegmentAddress[seg]
        ! UNWIND => DeleteDataSegment[seg]];
      IF next # eofDA THEN
	[] ← JumpToPage [@cfa,AltoDefs.MaxFilePage,DataSegmentAddress[seg]
	  ! UNWIND => DeleteDataSegment[seg]];
      DeleteDataSegment[seg];
      ChangeFileLength[@cfa.fa,lh];
      END;
    [page, byte] ← TruncateFileIndex[lh.page,lh.byte];
    RETURN
    END;

  SetEndOfFile: PUBLIC PROCEDURE [
    file:FileHandle, page:PageNumber, byte:CARDINAL] =
    BEGIN  da: vDA;
    lh: LengthHandle;
    cfa: CFA;
    seg: DataSegmentHandle = NewDataSegment[DefaultBase,1];
    buf: POINTER = DataSegmentAddress[seg];
    BEGIN ENABLE UNWIND => DeleteDataSegment[seg];
      ValidateFile[file];
      lh ← AllocateLength[file];
      IF ~file.open THEN OpenFile[file];
      IF lh.da = eofDA THEN GetLengthHint[lh];
      cfa ← CFA[fp: file.fp, fa: [da: lh.da, page: lh.page, byte: lh.byte]];
      [page,byte] ← RoundFileIndex[page,byte];
      IF page=0 THEN ERROR FileError[file];
      [,da] ← JumpToPage[@cfa,page,buf];
      SELECT cfa.fa.page FROM
	= page =>
	  SELECT cfa.fa.byte FROM
	    > byte => IF ~file.write
	      THEN ERROR FileAccessError[file];
	    < byte => IF ~file.append
	      THEN ERROR FileAccessError[file];
	    ENDCASE =>
	      IF da=eofDA THEN GO TO done
	      ELSE ERROR FileError[file];
	< page =>
	  BEGIN  da ← eofDA;
	  IF ~file.append THEN
	    ERROR FileAccessError[file];
	  END;
	ENDCASE => ERROR FileError[file];
      BFSDefs.CreatePages[buf,@cfa,page,byte];
      IF da # eofDA THEN BFSDefs.DeletePages[buf,@cfa.fp,da,page+1];
      EXITS
	done => NULL;
      END;
    DeleteDataSegment[seg];
    ChangeFileLength[@cfa.fa,lh];
    RETURN
    END;

  ChangeFileLength: PROCEDURE [fa:POINTER TO FA, lh: LengthHandle] =
    BEGIN
    currentlength: FA ← [da: lh.da, page: lh.page, byte: lh.byte];
    IF currentlength # fa↑ THEN
      BEGIN
      lh.file.lengthchanged ← TRUE;
      [da: lh.da, page: lh.page, byte: lh.byte] ← fa↑;
      END;
    lh.file.lengthvalid ← TRUE;
    RETURN
    END;

  SetFileLength, UpdateFileLength: PUBLIC PROCEDURE [
    file:FileHandle, fa:POINTER TO FA] =
    BEGIN OPEN file;
    lh: LengthHandle;
    ValidateFile[file];
    lh ← AllocateLength[file];
    ChangeFileLength[fa, lh];
    RETURN
    END;

  GetFileLength: PUBLIC PROCEDURE [file:FileHandle, fa:POINTER TO FA] =
    BEGIN
    lh: LengthHandle;
    ValidateFile[file];
    IF ~file.length THEN
      BEGIN
      fa↑ ← [da: eofDA, page: 0, byte: 0];
      RETURN
      END;
    lh ← FindLength[file];
    fa↑ ← [da: lh.da, page: lh.page, byte: lh.byte];
    RETURN
    END;


  -- Open and Close (leader page stuff)

  MakePageZeroSeg: PROCEDURE [file:FileHandle, access: AccessOptions]
   RETURNS [seg:FileSegmentHandle] =
    BEGIN
    temp: FileHandle = BootFile[access];
    temp.fp ← file.fp;  temp.open ← TRUE;
    seg ← NewFileSegment[temp, 0, 1, access
      ! UNWIND => ReleaseFile[temp]];
    SwapIn[seg ! UNWIND => DeletePageZeroSeg[seg]];
    RETURN
    END;

  DeletePageZeroSeg: PROCEDURE [seg:FileSegmentHandle] =
    BEGIN
    IF seg.swappedin THEN Unlock[seg];
    DeleteFileSegment[seg];
    RETURN
    END;

  SecondsClock: POINTER TO TIME = LOOPHOLE[572B];

  DAYTIME: PUBLIC PROCEDURE RETURNS [TIME] =
    BEGIN
    RETURN[SecondsClock↑]
    END;

  OpenFile: PUBLIC PROCEDURE [file:FileHandle] =
    BEGIN OPEN file;
    lh: LengthHandle;
    ld: POINTER TO LD;
    seg: FileSegmentHandle;
    ValidateFile[file];
    IF ~open THEN
      BEGIN
      seg ← MakePageZeroSeg[file, Read+Write];
      ld ← FileSegmentAddress[seg];
      IF length THEN
	BEGIN
	lh ← FindLength[file];
	[page: lh.page, byte: lh.byte, da: lh.da] ← ld.eofFA;
	-- PATCH for OS versions 5 & up
	IF lh.da = 0 THEN lh.da ← eofDA;
	END;
      IF read THEN ld.read ← DAYTIME[];
      IF write OR append THEN ld.written ← DAYTIME[];
      DeletePageZeroSeg[seg];
      open ← TRUE;
      END;
    RETURN
    END;

  CloseFile: PUBLIC PROCEDURE [file:FileHandle] =
    BEGIN OPEN file;
    ld: POINTER TO LD;
    lh: LengthHandle;
    seg: FileSegmentHandle;
    ValidateFile[file];
    IF swapcount # 0 THEN
      SIGNAL FileError[file];
    IF open AND length AND lengthchanged THEN 
      BEGIN
      seg ← MakePageZeroSeg[file, Read+Write];
      ld ← FileSegmentAddress[seg];
      lh ← FindLength[file];
      ld.eofFA ← FA[byte: lh.byte, page: lh.page, da: lh.da];
      DeletePageZeroSeg[seg];
      lengthchanged ← FALSE;
      END;
    open ← FALSE;
    RETURN
    END;

  GetLengthHint: PROCEDURE [lh: LengthHandle] =
    BEGIN
    ld: POINTER TO LD;
    seg: FileSegmentHandle;
    seg ← MakePageZeroSeg[lh.file, Read];
    ld ← FileSegmentAddress[seg];
    [page: lh.page, byte: lh.byte, da: lh.da] ← ld.eofFA;
    -- PATCH for OS versions 5 & up
    IF lh.da = 0 THEN lh.da ← eofDA;
    DeletePageZeroSeg[seg];
    RETURN
    END;
 


  -- Managing File Objects

  -- Procedures are bound before this initialization code is run
  ValidateFile: PROCEDURE [FileHandle] = LOOPHOLE[BootDefs.ValidateObject];
  LiberateFile: PROCEDURE [FileHandle] = LOOPHOLE[BootDefs.LiberateObject];
  AllocateFile: PROCEDURE RETURNS [FileHandle] =
    BEGIN
    RETURN[LOOPHOLE[BootDefs.AllocateObject[SIZE[file Object]]]]
    END;

  EnumerateFiles: PUBLIC PROCEDURE [
    proc: PROCEDURE [FileHandle] RETURNS [BOOLEAN]] RETURNS [FileHandle] =
    BEGIN
    RETURN[LOOPHOLE[BootDefs.EnumerateObjects[file, LOOPHOLE[proc]]]]
    END;

  FindFile: PUBLIC PROCEDURE [fp:POINTER TO FP] RETURNS [FileHandle] =
    BEGIN
    MatchFP: PROCEDURE [file:FileHandle] RETURNS [BOOLEAN] =
      BEGIN
      RETURN [file.fp.leaderDA = fp.leaderDA AND
        file.fp.serial = fp.serial]
      END;
    RETURN[EnumerateFiles[MatchFP]]
    END;

  GetFileFP: PUBLIC PROCEDURE [file:FileHandle, fp:POINTER TO FP] =
    BEGIN
    ValidateFile[file];
    fp↑ ← file.fp;
    RETURN
    END;

  
  -- Managing Length Objects

  -- Procedures are bound before this initialization code is run
  ValidateLength: PROCEDURE [LengthHandle] =
    LOOPHOLE[BootDefs.ValidateObject];
  LiberateLength: PROCEDURE [LengthHandle] =
    LOOPHOLE[BootDefs.LiberateObject];

  FindLength: PROCEDURE [file: FileHandle] RETURNS [LengthHandle] =
    BEGIN
    FindLength: PROCEDURE [lh: LengthHandle] RETURNS [BOOLEAN] =
      BEGIN RETURN[lh.file = file] END;
    RETURN[LOOPHOLE[BootDefs.EnumerateObjects[length, LOOPHOLE[FindLength]]]]
    END;

  AllocateLength: PROCEDURE [file:FileHandle] RETURNS [lh:LengthHandle] =
    BEGIN
    IF file.length THEN RETURN[FindLength[file]];
    lh ← LOOPHOLE[BootDefs.AllocateObject[SIZE[LengthObject]]];
    lh↑ ← [FALSE, length[0,0,0,file,eofDA]];
    file.length ← TRUE;
    file.lengthvalid ← FALSE;
    RETURN
    END;

  END.