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, errSE: 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];
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;
tapeHandle ← NEW[TapeHandleRec];
tapeHandle.serverName ← serverName;
tapeHandle.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[tapeHandle.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;
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;
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;
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.