<> <> <> DIRECTORY IO USING [Close, Flush, GetBlock, GetChar, PutBlock, PutChar, PutFR, ROS, RopeFromROS, rope, STREAM], Process USING [Pause, SecondsToTicks], PupStream USING [CloseReason, GetPupAddress, PupByteStreamCreate, PupNameTrouble, SecondsToTocks, StreamClosing, TimeOut, Tocks], PupTypes USING [PupAddress, PupSocketID], Rope USING [InlineFetch, Length, ROPE], TapeOps; TapeOpsImpl: CEDAR PROGRAM IMPORTS IO, Process, PupStream, Rope EXPORTS TapeOps = BEGIN OPEN TapeOps; ROPE: TYPE ~ Rope.ROPE; errRead: TapeStatus ~ [errHDW: TRUE, errCMD: TRUE, errHE: TRUE, errDL: TRUE, errRDP: TRUE]; -- +errNRZI errWrite: TapeStatus ~ [errHDW: TRUE, errCMD: TRUE, errHE: TRUE, errSE: TRUE, errDL: TRUE, errRDP: TRUE, FPT: TRUE, errWFP: TRUE, errICL: TRUE]; TapeOpsWarning: PUBLIC SIGNAL [ec: ROPE, code: ErrorCode] RETURNS [procced: BOOL]= CODE; TapeOpsError: PUBLIC ERROR [ec: ROPE, code: ErrorCode] = CODE; -- Public Procs OpenDrive: PUBLIC PROC [serverName: ROPE, driveNumber: NAT _ 0, density: Density _ PE1600, justOpen: BOOL _ FALSE] RETURNS [tapeHandle: TapeHandle] = BEGIN ENABLE PupStream.StreamClosing => MakePupStreamError [tapeHandle, why, text]; socket: PupTypes.PupSocketID _ [a: 0, b: 44B]; -- tape socket number; ticks: PupStream.Tocks _ PupStream.SecondsToTocks[3*60]; ecForAbort: ROPE _ NIL; codeForAbort: ErrorCode; commPortAddress: PupTypes.PupAddress; tapeHandle _ NEW[TapeHandleRep]; tapeHandle.serverName _ serverName; commPortAddress _ PupStream.GetPupAddress[socket, serverName ! PupStream.PupNameTrouble => { codeRope: ROPE _ IO.PutFR[(SELECT code FROM noRoute => "No route to %g from here", noResponse => "No response from name lookup server for %g", ENDCASE => "Name %g not found"), IO.rope[serverName]]; ERROR TapeOpsError[ec: codeRope, code: NameLookUpError];}]; tapeHandle.commStream _ PupStream.PupByteStreamCreate[commPortAddress, ticks]; BEGIN ENABLE TapeOpsError => { ecForAbort _ ec; codeForAbort _ code; GOTO CloseAndAbortCreate; }; SendCommand[tapeHandle: tapeHandle, command: cmdVersion, params: 1, param1: 0, -- our interface version string: "Tape server protocol version V0.3"]; tapeHandle.driveNumber _ driveNumber; SendCommand[tapeHandle: tapeHandle, command: cmdOpenDrive, params: 1, param1: tapeHandle.driveNumber, string: "Tape" ! TapeOpsError => BEGIN tapeHandle.commStream.Close[]; ERROR TapeOpsError [ec: "Drive busy", code: TapeUserError]; END]; SendCommand[tapeHandle: tapeHandle, command: cmdGetStatus]; tapeHandle.density _ density; IF NOT justOpen THEN BEGIN IF NOT tapeHandle.status[RDY] THEN ERROR TapeOpsError [ec: "Drive not ready", code: TapeOperationError]; IF LOOPHOLE [tapeHandle.status, CARDINAL] = 0 THEN ERROR TapeOpsError [ec: "Status from drive is 0.", code: TapeOperationError]; IF (NOT tapeHandle.status[BOT]) AND tapeHandle.status[RDY] AND tapeHandle.status[ONL] THEN BEGIN rewindWait: INT _ 0; SendCommand[tapeHandle, cmdRewind]; DO Process.Pause[Process.SecondsToTicks[1]]; rewindWait _ rewindWait + 1; SendCommand[tapeHandle, cmdGetStatus]; IF tapeHandle.status[BOT] THEN EXIT; IF rewindWait > 2000 THEN ERROR TapeOpsError [ec: "Timeout out on rewind.", code: TapeOperationError]; ENDLOOP; END; SendCommand[tapeHandle: tapeHandle, command: cmdSetStatus, params: 2, param1: subcmdSetDensity, param2: IF tapeHandle.density = NRZI800 THEN 800 ELSE 1600]; tapeHandle.densityIsSet _ TRUE; END; SendCommand[tapeHandle: tapeHandle, command: cmdGetStatus]; EXITS CloseAndAbortCreate => BEGIN SendCommand[tapeHandle: tapeHandle, command: cmdCloseDrive ! TapeOpsError => CONTINUE]; tapeHandle.commStream.Close[ ! PupStream.StreamClosing, PupStream.TimeOut => CONTINUE]; ERROR TapeOpsError[ecForAbort, codeForAbort]; END; END; -- for ENABLE tapeHandle.open _ TRUE; RETURN [tapeHandle]; END; CloseDrive: PUBLIC PROC [tapeHandle: TapeHandle] = BEGIN IF tapeHandle = NIL THEN RETURN; IF NOT tapeHandle.open THEN RETURN; SendCommand[tapeHandle: tapeHandle, command: cmdCloseDrive ! TapeOpsError => CONTINUE]; tapeHandle.commStream.Close[ ! PupStream.StreamClosing => CONTINUE]; tapeHandle.buffer _ NIL; tapeHandle.open _ FALSE; RETURN; END; GetStatus: PUBLIC PROC [tapeHandle: TapeHandle] RETURNS [status: TapeStatus] = BEGIN IF tapeHandle = NIL THEN TapeOpsError [ec: "Drive closed", code: TapeUserError]; IF NOT tapeHandle.open THEN {tapeHandle.commStream.Close[]; TapeOpsError [ec: "Drive closed", code: TapeUserError];}; SendCommand[tapeHandle, cmdGetStatus]; RETURN [tapeHandle.status]; END; SetRetry: PUBLIC PROC [tapeHandle: TapeHandle, retryCount: RetryCount _ 4] = BEGIN OpenCheck [tapeHandle, FALSE]; SendCommand[tapeHandle, cmdSetStatus, 2, subcmdSetRetries, retryCount]; END; SetDensity: PUBLIC PROC [tapeHandle: TapeHandle, density: Density] = BEGIN OpenCheck [tapeHandle]; SendCommand[tapeHandle, cmdGetStatus]; IF NOT tapeHandle.status[BOT] THEN TapeOpsError [ec: "Can't set the density except at the beginning of the tape", code: TapeUserError]; tapeHandle.density _ density; SendCommand[tapeHandle, cmdSetStatus, 2, subcmdSetDensity, IF density = PE1600 THEN 1600 ELSE 800]; tapeHandle.densityIsSet _ TRUE; END; Rewind: PUBLIC PROC [tapeHandle: TapeHandle, waitForCompletion: BOOL _ FALSE] RETURNS [status: TapeStatus] = BEGIN OpenCheck [tapeHandle]; IF NOT tapeHandle.status[BOT] THEN SendCommand[tapeHandle, cmdRewind]; IF waitForCompletion AND NOT tapeHandle.status[BOT] THEN DO Process.Pause[Process.SecondsToTicks[1]]; SendCommand[tapeHandle, cmdGetStatus]; IF tapeHandle.status[RDY] OR tapeHandle.status[BOT] THEN EXIT; ENDLOOP; RETURN [tapeHandle.status]; END; Unload: PUBLIC PROC [tapeHandle: TapeHandle] RETURNS [status: TapeStatus]= BEGIN OpenCheck [tapeHandle]; SendCommand[tapeHandle, cmdUnload]; RETURN [tapeHandle.status]; END; ReadRecord: PUBLIC PROC [tapeHandle: TapeHandle, buffer: REF TEXT _ NIL] RETURNS [tapeMark: BOOL, record: REF TEXT, status: TapeStatus] = BEGIN OpenCheck [tapeHandle]; IF NOT tapeHandle.densityIsSet THEN ERROR TapeOpsError[ec: "Tape dinsity not set", code: TapeUserError]; IF buffer = NIL THEN BEGIN IF tapeHandle.buffer = NIL THEN tapeHandle.buffer _ NEW[TEXT[maxBufferLength]]; END ELSE tapeHandle.buffer _ buffer; tapeHandle.buffer.length _ 0; SendCommand[tapeHandle, cmdReadRecord]; IF tapeHandle.status[FMK] THEN BEGIN tapeHandle.buffer.length _ 0; RETURN [TRUE, tapeHandle.buffer, tapeHandle.status]; END ELSE RETURN [FALSE, tapeHandle.buffer, tapeHandle.status]; END; WriteRecord: PUBLIC PROC [tapeHandle: TapeHandle, writeData: REF READONLY TEXT] RETURNS [status: TapeStatus] = BEGIN OpenCheck [tapeHandle]; IF NOT tapeHandle.densityIsSet THEN ERROR TapeOpsError[ec: "Tape dinsity not set", code: TapeUserError]; SendCommand[tapeHandle: tapeHandle, command: cmdWriteRecord, params: 1, param1: writeData.length, text: writeData]; RETURN [tapeHandle.status]; END; WriteFileMark: PUBLIC PROC [tapeHandle: TapeHandle] RETURNS [status: TapeStatus]= BEGIN OpenCheck [tapeHandle]; SendCommand[tapeHandle, cmdWriteEOF]; RETURN [tapeHandle.status]; END; ForwardSpaceFile: PUBLIC PROC [tapeHandle: TapeHandle] RETURNS [status: TapeStatus] = BEGIN OpenCheck [tapeHandle]; SendCommand[tapeHandle, cmdFwdSpaceFile]; RETURN [tapeHandle.status]; END; ForwardSpaceRecord: PUBLIC PROC [tapeHandle: TapeHandle] RETURNS [status: TapeStatus] = BEGIN OpenCheck [tapeHandle]; SendCommand[tapeHandle, cmdFwdSpaceRecord]; RETURN [tapeHandle.status]; END; BackSpaceFile: PUBLIC PROC [tapeHandle: TapeHandle] RETURNS [status: TapeStatus] = BEGIN OpenCheck [tapeHandle]; IF tapeHandle.status[BOT] THEN RETURN [tapeHandle.status]; SendCommand[tapeHandle, cmdBackSpaceFile]; RETURN [tapeHandle.status]; END; BackSpaceRecord: PUBLIC PROC [tapeHandle: TapeHandle] RETURNS [status: TapeStatus] = BEGIN OpenCheck [tapeHandle]; IF tapeHandle.status[BOT] THEN RETURN [tapeHandle.status]; SendCommand[tapeHandle, cmdBackSpaceRecord]; RETURN [tapeHandle.status]; END; -- Private Procs OpenCheck: PROC [tapeHandle: TapeHandle, checkNotRdy: BOOL _ TRUE] = BEGIN IF tapeHandle = NIL THEN TapeOpsError [ec: "Drive closed", code: TapeUserError]; IF NOT tapeHandle.open THEN {tapeHandle.commStream.Close[]; TapeOpsError [ec: "Drive closed", code: TapeUserError];}; SendCommand[tapeHandle, cmdGetStatus]; IF checkNotRdy THEN IF NOT tapeHandle.status[RDY] THEN TapeOpsError [ec: "Drive not ready", code: TapeOperationError]; END; SendCommand: PUBLIC PROC [tapeHandle: TapeHandle, command: TapeCommand, params, param1, param2: INT _ 0, string: Rope.ROPE _ NIL, text: REF READONLY TEXT _ NIL] = BEGIN ENABLE BEGIN PupStream.StreamClosing => MakePupStreamError [tapeHandle, why, text]; PupStream.TimeOut => CONTINUE; UNWIND => NULL END; SendWord: PROC[n: WORD] = BEGIN high: [0..256) = n/256; low: [0..256) = n MOD 256; tapeHandle.commStream.PutChar[LOOPHOLE[high, CHAR]]; tapeHandle.commStream.PutChar[LOOPHOLE[low, CHAR]]; END; -- of SendWord GetWord: PROC[] RETURNS[WORD] = BEGIN n1: [0..256) = LOOPHOLE[tapeHandle.commStream.GetChar[]]; n0: [0..256) = LOOPHOLE[tapeHandle.commStream.GetChar[]]; replyBytes _ replyBytes - 2; RETURN[256 * n1 + n0]; END; -- of GetWord GetString: PROC[] RETURNS[r: Rope.ROPE] = BEGIN IF replyBytes>0 THEN BEGIN s: IO.STREAM _ IO.ROS[]; charCount: [0..256) = LOOPHOLE[IO.GetChar[tapeHandle.commStream]]; FOR i: INT IN [0..charCount) DO s.PutChar[tapeHandle.commStream.GetChar[]]; ENDLOOP; r _ IO.RopeFromROS[s]; replyBytes _ replyBytes-(1+r.Length[]); END ELSE r _ NIL; END; -- of GetString FinishReply: PROC[] = BEGIN WHILE replyBytes>0 DO i: [0..256) _ LOOPHOLE[tapeHandle.commStream.GetChar[]]; replyBytes _ replyBytes - 1; ENDLOOP; END; -- of FinishReply -- main body of SendCommand... replyBytes: NAT _ 2; reply: TapeCommand; stringLen: NAT _ Rope.Length[NIL]; statusWord: CARDINAL; -- Send the command to the remote tape server, with whatever parameters -- go with the command. SendWord[2 + params + (IF string = NIL THEN 0 ELSE (stringLen + 2)/2) + (IF command = cmdWriteRecord THEN (param1 + 1)/2 ELSE 0)]; -- 16-bit word count SendWord[command]; IF params > 0 THEN SendWord[param1]; IF params > 1 THEN SendWord[param2]; IF string # NIL THEN BEGIN tapeHandle.commStream.PutChar[LOOPHOLE[stringLen]]; FOR i: INT IN [0..stringLen) DO tapeHandle.commStream.PutChar[string.InlineFetch[i]]; ENDLOOP; IF (stringLen MOD 2) # 1 THEN BEGIN -- pad to a word tapeHandle.commStream.PutChar[0C]; -- trailing NUL END; END; IF command = cmdWriteRecord THEN BEGIN tapeHandle.commStream.PutBlock[text]; IF param1 MOD 2 # 0 THEN BEGIN -- pad to a word tapeHandle.commStream.PutChar[0C]; -- trailing NUL END; END; tapeHandle.commStream.Flush[]; -- force the Pup package to ship it -- Now get a reply appropriate to the command we sent, and alter the tape -- tapeHandle to reflect the reply. replyBytes _ 2 * GetWord[] - 2; reply _ LOOPHOLE[GetWord[]]; SELECT command FROM cmdVersion => BEGIN IF reply # cmdVersion THEN BEGIN tapeHandle.commStream.Close[]; tapeHandle.open _ FALSE; ERROR TapeOpsError[ec: "Bad version return", code: ServerProtocolError]; END; [] _ GetWord[]; tapeHandle.versionString _ GetString[]; END; cmdGetStatus => BEGIN drive, retryCount, density: INT; IF reply # cmdHereIsStatus THEN BEGIN FinishReply[]; tapeHandle.commStream.Close[]; tapeHandle.open _ FALSE; ERROR TapeOpsError[ec: "Not a status return", code: ServerProtocolError]; END; drive _ LOOPHOLE[GetWord[], INTEGER]; tapeHandle.status _ LOOPHOLE[GetWord[]]; retryCount _ GetWord[]; density _ GetWord[]; IF drive < 0 THEN { tapeHandle.commStream.Close[]; tapeHandle.open _ FALSE; ERROR TapeOpsError[ec: "Drive closed", code: ServerProtocolError]; }; END; cmdReadRecord => BEGIN dataBytes: NAT; IF reply # cmdHereIsRecord THEN { tapeHandle.commStream.Close[]; tapeHandle.open _ FALSE; ERROR TapeOpsError [ec: "Not a read record return", code: ServerProtocolError]; }; tapeHandle.status _ LOOPHOLE[GetWord[]]; dataBytes _ GetWord[]; statusWord _ LOOPHOLE [tapeHandle.status]; IF statusWord = 0 THEN BEGIN FinishReply[]; ERROR TapeOpsError[ec: "Status from drive is 0.", code: TapeOperationError]; END; IF tapeHandle.status[BOT] AND tapeHandle.status[EOT] THEN BEGIN FinishReply[]; ERROR TapeOpsError[ec: "Tape not mounted", code: TapeUserError]; END; FOR reason: TapeStatusIndex IN TapeStatusIndex DO IF tapeHandle.status[reason] AND errRead[reason] THEN BEGIN FinishReply[]; ERROR TapeOpsError[ec: IO.PutFR["Tape data error, %g", IO.rope[errExplanation[reason]]], code: DataError]; END; ENDLOOP; IF dataBytes > tapeHandle.buffer.maxLength THEN tapeHandle.buffer _ NEW[TEXT[dataBytes + 1]]; tapeHandle.buffer.length _ tapeHandle.commStream.GetBlock[tapeHandle.buffer, 0, dataBytes]; replyBytes _ replyBytes - dataBytes; IF tapeHandle.status[errSE] THEN BEGIN proceed: BOOL _ SIGNAL TapeOpsWarning[ec: IO.PutFR["Tape data error, %g", IO.rope[errExplanation[errSE]]], code: DataError]; IF NOT proceed THEN RETURN[]; END; END; ENDCASE => SELECT reply FROM cmdYes => BEGIN [] _ GetWord[]; tapeHandle.status _ LOOPHOLE[GetWord[]]; FinishReply[]; statusWord _ LOOPHOLE [tapeHandle.status]; END; cmdNo => BEGIN cause: TapeCause _ LOOPHOLE[GetWord[]]; tapeHandle.status _ LOOPHOLE[GetWord[]]; FinishReply[]; statusWord _ LOOPHOLE [tapeHandle.status]; IF statusWord = 0 THEN ERROR TapeOpsError[ec: "Status from drive is 0.", code: TapeOperationError]; IF tapeHandle.status[BOT] AND tapeHandle.status[EOT] THEN ERROR TapeOpsError[ec: "Tape not mounted", code: TapeUserError]; IF NOT tapeHandle.status[RDY] THEN ERROR TapeOpsError[ec: "Drive not ready", code: TapeOperationError]; IF command = cmdWriteRecord THEN FOR reason: TapeStatusIndex IN TapeStatusIndex DO IF tapeHandle.status[reason] AND errWrite[reason] THEN BEGIN FinishReply[]; ERROR TapeOpsError[ec: IO.PutFR["Tape data error, %g", IO.rope[errExplanation[reason]]], code: DataError]; END; ENDLOOP; IF tapeHandle.status[errHDW] OR tapeHandle.status[errCMD] THEN ERROR TapeOpsError[ec: IO.PutFR["Tape data error, %g", IO.rope[errExplanation[(IF tapeHandle.status[errHDW] THEN errHDW ELSE errCMD)]]], code: TapeOperationError]; ERROR TapeOpsError[ec: tapeCauseExplanation[cause], code: TapeUserError]; END; ENDCASE => { tapeHandle.commStream.Close[]; tapeHandle.open _ FALSE; ERROR TapeOpsError[ec: "Unknown response", code: ServerProtocolError]; }; FinishReply[]; SELECT command FROM cmdGetStatus, cmdVersion, cmdRewind, cmdUnload, cmdCloseDrive, cmdOpenDrive, cmdSetStatus => NULL; ENDCASE => BEGIN -- ending status must be "drive ready" IF NOT tapeHandle.status[RDY] OR NOT tapeHandle.status[ONL] THEN ERROR TapeOpsError[ec: "Bad drive ending status", code: TapeOperationError]; END; END; -- of SendCommand MakePupStreamError: PROC [ tapeHandle: TapeHandle, why: PupStream.CloseReason, text: ROPE] = BEGIN codeRope: ROPE _ IO.PutFR["Communication with %g broken%s\n", IO.rope[tapeHandle.serverName], IO.rope[SELECT why FROM localClose => " from this end", remoteClose => " from remote end", noRouteToNetwork => ", no route from here to remote server", transmissionTimeout => " because of time-out", remoteReject => ", connection rejected by remote server", ENDCASE => ", reason unknown"]]; tapeHandle.open _ FALSE; ERROR TapeOpsError[ec: codeRope, code: ServerControlStreamAbort]; END; -- Commands to tape server TapeCommand: TYPE = [0..26]; cmdYes: TapeCommand = 01B; cmdNo: TapeCommand = 02B; cmdVersion: TapeCommand = 03B; cmdSendTEXT: TapeCommand = 04B; cmdGetTEXT: TapeCommand = 05B; cmdOpenDrive: TapeCommand = 06B; cmdCloseDrive: TapeCommand = 07B; cmdReadRecord: TapeCommand = 010B; cmdWriteRecord: TapeCommand = 011B; cmdFwdSpaceRecord: TapeCommand = 012B; cmdBackSpaceRecord: TapeCommand = 013B; cmdFwdSpaceFile: TapeCommand = 014B; cmdBackSpaceFile: TapeCommand = 015B; cmdWriteEOF: TapeCommand = 016B; cmdWriteBlankTape: TapeCommand = 017B; cmdRewind: TapeCommand = 020B; cmdUnload: TapeCommand = 021B; cmdGetStatus: TapeCommand = 022B; cmdSetStatus: TapeCommand = 023B; cmdHereIsTEXT: TapeCommand = 024B; cmdHereIsRecord: TapeCommand = 025B; cmdHereIsStatus: TapeCommand = 026B; -- subcommands (setstatus) subcmdSetRetries: TapeCommand = 00B; subcmdSetDensity: TapeCommand = 01B; -- Message selectors for CauseNo TapeCause: TYPE ~ MACHINE DEPENDENT { doneOperation(0), noGoodMessage(1), openAlready(2), driveInUse(3), badDrive(4), badDriveNo(5), driveNotOpened(6), noVersion(7), badStatusSelector(8), badRetrySetting(9), writeProtected(10), badDensitySetting(11), densitySetNotatBOT(12) }; NilRope: TYPE ~ Rope.ROPE _ NIL; tapeCauseExplanation: ARRAY TapeCause OF NilRope ~ [ doneOperation: "Good command", noGoodMessage: "Illegal command", openAlready: "This socket already has a tape drive open", driveInUse: "Drive busy", badDrive: "Inoperable tape drive", badDriveNo: "Bad tape drive number", driveNotOpened: "Drive not open", noVersion: "Interface version wasn't checked", badStatusSelector: "Cannot set that tape drive status", badRetrySetting: "Improper retry count", writeProtected: "Can't write because tape is write protected", badDensitySetting: "Tape drive doesn't support this density", densitySetNotatBOT: "Can't set the density except at the beginning of the tape" ]; errExplanation: ARRAY TapeStatusIndex OF NilRope ~ [ RDY: "Drive ready", ONL: "Drive online", RWD: "Drive rewinding", FPT: "Tape has no write ring", BOT: "Tape at BOT", EOT: "Tape at EOT", FMK: "File mark encountered", NRZI: "Tape at 800 bpi (NRZI)", errHE: "Hard tape error", errSE: "Soft tape error", errDL: "Data late error", errRDP: "Read data parity error", errICL: "Incorrect byte length written", errHDW: "Hardware detected error", errWFP: "Write attempted on file protected reel", errCMD: "Unknown command type"]; END.