TapeOpsImpl.mesa
Copyright © 1984, 1986 by Xerox Corp. All rights reserved.
Last edited by Tim Diebert: May 19, 1986 8:45:07 am PDT
DIRECTORY
IO USING [Close, Flush, GetBlock, GetChar, PutBlock, PutChar, PutFR, ROS, RopeFromROS, rope, STREAM],
Process USING [Pause, SecondsToTicks],
Pup USING [Address, Socket],
PupName USING [Error, NameLookup],
PupStream USING [Create, CloseReason, StreamClosing, Timeout],
Rope USING [InlineFetch, Length, ROPE],
TapeOps;
TapeOpsImpl: CEDAR PROGRAM
IMPORTS IO, Process, PupName, 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: BOOLFALSE] RETURNS [tapeHandle: TapeHandle] = BEGIN
ENABLE PupStream.StreamClosing => MakePupStreamError [tapeHandle, why, text];
socket: Pup.Socket ← [a: 0, b: 0, c: 0, d: 44B]; -- tape socket number;
ecForAbort: ROPENIL;
codeForAbort: ErrorCode;
commPortAddress: Pup.Address;
tapeHandle ← NEW[TapeHandleRep];
tapeHandle.serverName ← serverName;
commPortAddress ← PupName.NameLookup[serverName, socket
! PupName.Error => {
codeRope: ROPEIO.PutFR[(SELECT code FROM
noRoute => "No route to %g from here",
noResponse => "No response from name lookup server for %g",
errorFromServer => "Error from name lookup server for %g",
ENDCASE => "Name %g not found"), IO.rope[serverName]];
ERROR TapeOpsError[ec: codeRope, code: NameLookUpError];}];
tapeHandle.commStream ← PupStream.Create[commPortAddress, 180000, 180000];
Wait for 3 min.
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: BOOLFALSE] 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 TEXTNIL]
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: BOOLTRUE] = 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.ROPENIL, text: REF READONLY TEXTNIL] = 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.STREAMIO.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: BOOLSIGNAL 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: ROPEIO.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.ROPENIL;
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.