-- FileIOCommonImpl.mesa
-- Last Edited by
--   MBrown on May 16, 1983 3:32 pm
--   Teitelman on June 9, 1982 9:14 pm

  DIRECTORY
    Rope USING [Fetch, Length, Match, ROPE],
    FileIO USING [AccessOptions, CreateOptions, CloseOptions, OpenFailed, OpenFailure,
      RawOption, StreamBufferParms, Trans],
    FileIOPrivate USING [AlpineOpen, CIFSOpen, ComSoftOpen, Data],
    IO USING [CreateRefStreamProcs, Error, GetChar, GetIndex, GetLength, SetIndex,
      STREAM, StreamProcs, UnsafeBlock],
    Transaction USING [Handle, nullHandle];

FileIOCommonImpl: CEDAR PROGRAM
  IMPORTS
    FileIO,
    FileIOPrivate,
    IO,
    Rope,
    Transaction
  EXPORTS
    FileIO,
    FileIOPrivate =
  BEGIN
  ROPE: TYPE = Rope.ROPE;
  STREAM: TYPE = IO.STREAM;

  DataHandle: TYPE = REF Data;
  Data: TYPE = FileIOPrivate.Data;

  OpenFailed: PUBLIC SIGNAL [why: FileIO.OpenFailure, fileName: ROPE]
    RETURNS [retryFileName: ROPE] = CODE;

  Open: PUBLIC PROC [
    fileName: ROPE,
    accessOptions: FileIO.AccessOptions,
    createOptions: FileIO.CreateOptions,
    closeOptions: FileIO.CloseOptions,
    transaction: FileIO.Trans,
    raw: FileIO.RawOption,
    createLength: INT,
    streamBufferParms: FileIO.StreamBufferParms]
    RETURNS [STREAM] = TRUSTED {
    -- FileIO.Open
    -- This proc just decides which more specific Open proc to call, and handles a few errors.
    openFailure: FileIO.OpenFailure;
    { -- block for failure exit
    DirSystem: TYPE = { CIFS, ComSoft, Alpine };
    SelectDirSystem: SAFE PROC [fileName: ROPE] RETURNS [dir: DirSystem] = CHECKED {
      RETURN [
        SELECT fileName.Fetch[0] FROM
          '/ => CIFS,
          '[ => IF Rope.Match[pattern: "[*.alpine]*", object: fileName, case: FALSE] THEN
                Alpine ELSE CIFS,
          ENDCASE => ComSoft ];
      };
    dirSystem: DirSystem;
    IF fileName.Length[] = 0 THEN {
      openFailure ← fileNotFound;  GOTO failure };
    IF accessOptions = read THEN createOptions ← oldOnly;
    dirSystem ← SelectDirSystem[fileName];
    SELECT dirSystem FROM
      CIFS, ComSoft => {
        trans: Transaction.Handle ← Transaction.nullHandle;
        WITH t: transaction SELECT FROM
          pilot => trans ← t.trans;
          juniper => IF t.trans # NIL THEN {
            openFailure ← wrongTransactionType;  GOTO failure };
          ENDCASE => ERROR;
        IF dirSystem = CIFS THEN RETURN [FileIOPrivate.CIFSOpen[
          fileName, accessOptions, createOptions, closeOptions, trans,
          raw, createLength, streamBufferParms]]
        ELSE RETURN [FileIOPrivate.ComSoftOpen[
          fileName, accessOptions, createOptions, closeOptions, trans,
          raw, createLength, streamBufferParms]];
        }; -- CIFS, ComSoft
      Alpine => RETURN [FileIOPrivate.AlpineOpen[
        fileName, accessOptions, createOptions, closeOptions, NIL,
          raw, createLength, streamBufferParms]];
      ENDCASE => ERROR;
  EXITS
    failure => {
      retryFileName: ROPE =
        SIGNAL FileIO.OpenFailed[why: openFailure, fileName: fileName];
      RETURN [Open[retryFileName, accessOptions, createOptions, closeOptions,
        transaction, raw, createLength, streamBufferParms]]
      }
  }};--Open

  -- Procs exported to FileIOPrivate

IsThisThingATiogaFile: PUBLIC PROC [h: STREAM]
  RETURNS [BOOLEAN, INT] = {
    -- FileIOPrivate.IsThisThingATiogaFile
    pos, len, length: INT;
    { -- block so EXITS code can use pos, len, and length.
    controlHeaderId: ARRAY [0..fileIdSize) OF CHAR = [235C,312C];
    controlTrailerId: ARRAY [0..fileIdSize) OF CHAR = [205C,227C];
    commentHeaderId: ARRAY [0..fileIdSize) OF CHAR = [0C,0C];
    fileIdSize: NAT = 2;
    numTrailerLengths: NAT = 3; -- <file-props-length> <data-length> <file-length>
    endSize: NAT = fileIdSize+numTrailerLengths*4; -- trailer plus three lengths
    ReadLen: PROC [h: STREAM] RETURNS [INT] = {
	    start: PACKED ARRAY [0..3] OF CHARACTER;
	    start[0] ← h.GetChar[]; start[1] ← h.GetChar[];
	    start[2] ← h.GetChar[]; start[3] ← h.GetChar[];
	    RETURN [LOOPHOLE[start]] };
	commentStart, commentLen, propsLen, controlLen, controlEnd: INT;
	pos ← h.GetIndex[]; -- save position to restore later
	length ← h.GetLength[]; -- length including any trailer stuff
	controlEnd ← length-endSize; -- where the trailer info starts
	IF controlEnd <= 0 THEN GOTO fail; -- too small
	h.SetIndex[controlEnd]; -- set up to read the trailer
	FOR i:NAT IN [0..fileIdSize) DO -- read the controlTrailerId
		IF h.GetChar[] # controlTrailerId[i] THEN GOTO fail;
		ENDLOOP;
	IF (propsLen ← ReadLen[h]) NOT IN [0..controlEnd) THEN GOTO fail;
	IF (commentStart ← ReadLen[h]) NOT IN [0..controlEnd) THEN GOTO fail;
	IF ReadLen[h] # length THEN GOTO fail;
	IF commentStart > 0 THEN { -- may have padded text with a null
		h.SetIndex[commentStart-1];
		len ← IF h.GetChar[]=0C THEN commentStart-1 ELSE commentStart }
	ELSE h.SetIndex[len ← commentStart];
	FOR i:NAT IN [0..fileIdSize) DO -- read the commentHeaderId
		IF h.GetChar[] # commentHeaderId[i] THEN GOTO fail;
		ENDLOOP;
	commentLen ← ReadLen[h]; -- the length of the comment section
	IF commentStart+commentLen NOT IN [0..controlEnd) THEN GOTO fail;
	h.SetIndex[commentStart+commentLen]; -- go to start of control info
	FOR i:NAT IN [0..fileIdSize) DO -- check the controlHeaderId
		IF h.GetChar[] # controlHeaderId[i] THEN GOTO fail;
		ENDLOOP;
	controlLen ← ReadLen[h]; -- the length of the control section
	IF commentStart+commentLen+controlLen # length THEN GOTO fail;
	GOTO succeed;
	EXITS
	  fail => { h.SetIndex[pos];  RETURN [FALSE, length] };
	  succeed => { h.SetIndex[pos];  RETURN [TRUE, len] };
	}};


  GetCharDisallowed: PUBLIC PROC [self: STREAM] RETURNS [CHAR] = {
    RaiseStreamError[self]; ERROR };
  PutCharDisallowed: PUBLIC PROC [self: STREAM, char: CHAR] = {
    RaiseStreamError[self] };
  GetBlockDisallowed: PUBLIC PROC [self: STREAM, block: REF TEXT,
    startIndex: NAT, stopIndexPlusOne: NAT] RETURNS [nBytesRead: NAT] = {
    RaiseStreamError[self]; ERROR };
  PutBlockDisallowed: PUBLIC PROC [self: STREAM, block: REF READONLY TEXT, startIndex: NAT,
    stopIndexPlusOne: NAT] = { RaiseStreamError[self] };
  UnsafeGetBlockDisallowed: PUBLIC PROC [self: STREAM, block: IO.UnsafeBlock]
    RETURNS [nBytesRead: INT] = { RaiseStreamError[self]; ERROR };
  UnsafePutBlockDisallowed: PUBLIC PROC [self: STREAM, block: IO.UnsafeBlock] = { 
    RaiseStreamError[self] };
  SetIndexDisallowed: PUBLIC PROC [self: STREAM, index: INT] = {
    RaiseStreamError[self] };

  RaiseStreamError: PROC [self: STREAM] = {
    selfData: DataHandle = NARROW[self.streamData];
    ERROR IO.Error[
      IF selfData.streamIsClosed THEN StreamClosed ELSE NotImplementedForThisStream, self]
    };

  Invalidate: PUBLIC PROC [self: STREAM] = {
    -- FileIOPrivate.Invalidate
    self.streamProcs ← commonFileIOInvalidProcs;
    -- Procs on self.propList must fend for themselves
    };

  -- Procs called via commonFileIOInvalidProcs.
  GetIndexDisallowed: PROC [self: STREAM] RETURNS [i: INT] = {
    RaiseStreamError[self] };
  DisallowedOpReturnsBoolean: PROC [self: STREAM] RETURNS[BOOL] = {
    RaiseStreamError[self]; ERROR };
  StreamOpIsNoop: PROC [self: STREAM] = { };
  CloseIsNoop: PROC [self: STREAM, abort: BOOL] = { };

  commonFileIOInvalidProcs: REF IO.StreamProcs = IO.CreateRefStreamProcs[
    getChar:  GetCharDisallowed,
    endOf: DisallowedOpReturnsBoolean,
    charsAvail: DisallowedOpReturnsBoolean,
    getBlock: GetBlockDisallowed,
    unsafeGetBlock: UnsafeGetBlockDisallowed,
    
    putChar: PutCharDisallowed,
    putBlock:  PutBlockDisallowed,
    unsafePutBlock: UnsafePutBlockDisallowed,
    flush: StreamOpIsNoop,
    
    reset: StreamOpIsNoop,
    close: CloseIsNoop,
    getIndex: GetIndexDisallowed,
    setIndex: SetIndexDisallowed,
    name: "Closed File"
    ];

END.


CHANGE LOG

Created by MBrown on December 12, 1980  12:56 PM

Changed by MBrown on January 6, 1981  8:22 PM
-- HandleFromFileName moved to FileByteStreamPilotImpl, since it now admits to being
--Pilot-specific. Added Invalidate, and private disallowed procs (GetLength, etc).

Changed by MBrown on 21-Jan-81 23:54:12
-- Added IndexTooLarge.

Changed by MBrown on January 22, 1981  3:59 PM
-- Make various procs raise streamClosed or operationDisallowed depending on value of
--streamIsClosed.

Changed by MBrown on 27-Jan-81 15:25:20
-- Moved Open here (juniper stuff being added).

Changed by MBrown on 29-Jan-81  8:58:37
-- Bug in Open: passed fileName (including server) to JOpen.  Juniper gave a "protection violation".

Changed by MBrown on 29-Jan-81 20:38:29
-- Moved POpen and JOpen out.

Changed by MBrown on 1-Mar-81 13:05:50
-- Minor changes due to change in CedarString interface (Rest procedure gone, SubString -> Substr).
Changed by MBrown on 31-Mar-81 17:06:56
-- In Open, IF accessOptions = read THEN createOptions ← oldOnly.

Change by Russ Atkinson, 26-May-81 14:00:13
-- CedarString -> Rope, LONG CARDINAL -> LONG INTEGER

Changed by MBrown on  7-Dec-81 10:33:20
-- Changed name to FileIO, made compatible with IO, use INT, ROPE, etc.

Changed by MBrown on March 25, 1982 9:07 am
-- In Open, check for fileName.Length[] = 0 before doing fileName.Fetch[0].  Change
--Rope.Size -> Rope.Length, Rope.Index -> Rope.Find.

Changed by MBrown on March 26, 1982 5:14 pm
-- Added IsThisThingATiogaFile, raw parm to Open.

Changed by MBrown on September 1, 1982 9:37 pm
-- FileIO.OpenFailed, CIFS access, initial file size in Open, buffer parms.

Changed by MBrown on February 8, 1983 4:45 pm
-- Flushed Juniper support.

Changed by MBrown on May 16, 1983 2:30 pm
-- Changed Open to call FileIO.AlpineOpen when appropriate.