-- Files.Mesa  Edited by Sandman on July 1, 1980  7:58 AM
-- Copyright  Xerox Corporation 1979, 1980

DIRECTORY
  AltoDefs USING [BytesPerPage, MaxFilePage],
  AltoFileDefs USING [CFA, eofDA, FA, FP, LD, NullFP, TIME, vDA],
  BFSDefs USING [CreatePages, DeletePages],
  DirectoryDefs USING [DirectoryLookup, DirectoryPurgeFP],
  DiskKDDefs USING [UpdateDiskKD],
  InlineDefs USING [BITAND, BITOR],
  MiscDefs USING [],
  Mopcodes USING [zEXCH],
  NucleusOps USING [],
  SegmentDefs USING [
    AccessOptions, Append, DataSegmentAddress, DataSegmentHandle, DefaultAccess,
    DefaultBase, DeleteDataSegment, DeleteFileSegment, FileHandle, FileObject,
    FileSegmentAddress, FileSegmentHandle, JumpToPage, LengthHandle,
    MakeDataSegment, MakeSwappedIn, MaxFileLocks, NewDataSegment, NewFileOnly,
    NewFileSegment, Object, OldFileOnly, PageNumber, Read, SwapIn, Unlock,
    VersionOptions, Write],
  StreamDefs USING [],
  SwapperOps USING [
    AllocateObject, EnumerateObjects, LiberateObject, ValidateObject],
  TimeDefs USING [DefaultTime, PackedTime];

Files: PROGRAM
  IMPORTS BFSDefs, SwapperOps, DirectoryDefs, DiskKDDefs, InlineDefs, SegmentDefs
  EXPORTS SwapperOps, MiscDefs, NucleusOps, SegmentDefs, StreamDefs
  SHARES SegmentDefs =
  BEGIN OPEN AltoFileDefs, SegmentDefs, SwapperOps;

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

  NullFileObject: FileObject =
    [busy: FALSE,
      body: file[
      open: FALSE, length: FALSE, lengthvalid: FALSE, read: FALSE, write: FALSE,
      append: FALSE, lengthchanged: FALSE, lock: 0, swapcount: 0, segcount: 0,
      fp: NullFP]];

  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 ← LOOPHOLE[SwapperOps.AllocateObject[SIZE[file Object]]];
      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 ← LOOPHOLE[SwapperOps.AllocateObject[SIZE[file Object]]];
      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 ← LOOPHOLE[SwapperOps.AllocateObject[SIZE[file Object]]];
    file↑ ← NullFileObject;
    SetFileAccess[file, access];
    RETURN
    END;

  InsertFileLength: PUBLIC PROCEDURE [file: FileHandle, fa: POINTER TO FA] =
    BEGIN
    lh: LengthHandle ← GetLength[file];
    IF fa.da # eofDA THEN [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;
    ValidateObject[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;
    ValidateObject[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;
    ValidateObject[file];
    IF lock = MaxFileLocks THEN ERROR FileError[file];
    lock ← lock + 1;
    RETURN
    END;

  UnlockFile: PUBLIC PROCEDURE [file: FileHandle] =
    BEGIN OPEN file;
    ValidateObject[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, SwapperOps;
    ValidateObject[file];
    IF segcount # 0 THEN ERROR FileError[file];
    IF lock # 0 THEN RETURN[FALSE];
    CloseFile[file];
    IF file.length THEN LiberateObject[FindLength[file]];
    LiberateObject[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;
    ValidateObject[file];
    lh ← GetLength[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];
    ValidateObject[file];
    lh ← GetLength[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;
    ValidateObject[file];
    lh ← GetLength[file];
    ChangeFileLength[fa, lh];
    RETURN
    END;

  GetFileLength: PUBLIC PROCEDURE [file: FileHandle, fa: POINTER TO FA] =
    BEGIN
    lh: LengthHandle;
    ValidateObject[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;
    ValidateObject[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.created ← 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;
    ValidateObject[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;

  MesaToBCPLTime: PROCEDURE [LONG CARDINAL] RETURNS [TIME] = MACHINE CODE
    BEGIN Mopcodes.zEXCH END;

  BCPLToMesaTime: PROCEDURE [TIME] RETURNS [LONG CARDINAL] = MACHINE CODE
    BEGIN Mopcodes.zEXCH END;

  GetFileTimes: PUBLIC PROCEDURE [file: FileHandle]
    RETURNS [read, write, create: TimeDefs.PackedTime] =
    BEGIN
    ld: POINTER TO LD;
    seg: FileSegmentHandle;
    ValidateObject[file];
    seg ← MakePageZeroSeg[file, Read];
    ld ← FileSegmentAddress[seg];
    read ← BCPLToMesaTime[ld.read];
    write ← BCPLToMesaTime[ld.written];
    create ← BCPLToMesaTime[ld.created];
    DeletePageZeroSeg[seg];
    RETURN
    END;

  SetFileTimes: PUBLIC PROCEDURE [
    file: FileHandle,
    read, write, create: TimeDefs.PackedTime ← TimeDefs.DefaultTime] =
    BEGIN
    now: TIME = DAYTIME[];
    ld: POINTER TO LD;
    seg: FileSegmentHandle;
    ValidateObject[file];
    IF ~file.open THEN OpenFile[file];
    seg ← MakePageZeroSeg[file, Read + Write];
    ld ← FileSegmentAddress[seg];
    ld.read ← IF read = TimeDefs.DefaultTime THEN now ELSE MesaToBCPLTime[read];
    ld.written ←
      IF write = TimeDefs.DefaultTime THEN now ELSE MesaToBCPLTime[write];
    ld.created ←
      IF create = TimeDefs.DefaultTime THEN now ELSE MesaToBCPLTime[create];
    DeletePageZeroSeg[seg];
    RETURN
    END;

  -- Managing File Objects


  EnumerateFiles: PUBLIC PROCEDURE [
    proc: PROCEDURE [FileHandle] RETURNS [BOOLEAN]] RETURNS [FileHandle] =
    BEGIN RETURN[LOOPHOLE[SwapperOps.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 ValidateObject[file]; fp↑ ← file.fp; RETURN END;

  -- Managing Length Objects


  FindLength: PROCEDURE [file: FileHandle] RETURNS [LengthHandle] =
    BEGIN

    FindLength: PROCEDURE [lh: LengthHandle] RETURNS [BOOLEAN] =
      BEGIN RETURN[lh.file = file] END;

    RETURN[LOOPHOLE[SwapperOps.EnumerateObjects[length, LOOPHOLE[FindLength]]]]
    END;

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


  END.