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. ŽTapeOpsImpl.mesa Copyright c 1984, 1986 by Xerox Corp. All rights reserved. Last edited by Tim Diebert: March 20, 1986 10:41:36 am PST Κ ˜codešœ™Kšœ Οmœ0™;Kšœ:™:—K˜šΟk ˜ šœžœ"˜*Kšœ˜Kšœžœ˜ —Kšœžœ˜&šœ žœ2˜AKšœ?˜?—Kšœ žœ˜)Kšœžœžœ˜'Kšœ˜—K˜šΟb œžœž˜Kšžœ˜$Kšžœ ž˜Kšžœ ˜ K˜Kšžœžœžœ˜K˜šœžœ žœ˜3Kšœžœ žœ žœΟc ˜4—šœ žœ žœ˜4Kš œžœ žœ žœ žœ˜4Kšžœžœ žœ žœ˜'—K˜K˜KšΠbnœžœžœžœžœ žœžœ˜YKš ‘ œžœžœžœžœ˜>K˜K˜KšΠbc˜K˜š‘ œžœžœžœžœ-žœžœžœž˜ŸšžœG˜MK˜—Kšœ/ ˜EKšœ8˜8Kšœ žœžœ˜Kšœ˜Kšœ%˜%K˜Kšœ žœ˜ Kšœ#˜#šœ<˜<šœ˜šœ žœžœžœž˜+Kšœ&˜&Kšœ;˜;Kšžœžœ˜6—Kšžœ6˜;——K˜šœ˜Kšœ6˜6—K˜šžœžœ˜Kšœ(žœ˜D—˜šœ8˜8Kšœ ˜ Kšœ  ˜#Kšœ-˜-—Kšœ%˜%šœ:˜:Kšœ ˜ Kšœ˜šœ˜šœž˜Kšœ˜Kšžœ6˜;Kšžœ˜———Kšœ<˜Kšžœ˜—Kšžœ˜Kšžœ˜—K˜šŸœž œžœž˜PKšœ˜Kšœ#˜#Kšžœ˜Kšžœ˜—K˜š Ÿ œž œ"žœžœžœ˜HKšžœ žœ ž œž˜FKšœ˜šžœžœž˜#Kšžœ?˜D—šžœ ž˜šžœž˜ Kš žœžœžœžœžœ˜OKšž˜—Kšžœ˜ —Kšœ˜Kšœ'˜'šžœžœ˜šž ˜ Kšœ˜Kšžœžœ(˜4Kšž˜—Kšž œžœ(˜:—Kšžœ˜—K˜šŸ œž ˜Kšœ$žœž œžœž˜[Kšœ˜šžœžœž˜#Kšžœ?˜D—šœG˜GKšœ+˜+—Kšžœ˜Kšžœ˜—K˜šŸ œž œžœž˜XKšœ˜Kšœ%˜%Kšžœ˜Kšžœ˜K˜—šŸœž œžœž˜[Kšœ˜Kšœ)˜)Kšžœ˜Kšžœ˜—K˜šŸœž œžœž˜^Kšœ˜Kšœ+˜+Kšžœ˜Kšžœ˜—K˜šŸ œž œžœž˜XKšœ˜Kšžœžœžœžœ˜:Kšœ*˜*Kšžœ˜Kšžœ˜—K˜šŸœž œžœž˜[Kšœ˜Kšžœžœžœžœ˜:Kšœ,˜,Kšžœ˜Kšžœ˜—K˜K˜Kš’˜K˜š ‘ œžœ'žœžœž˜JKšžœžœžœ8˜Pšžœž˜KšœY˜Y—Kšœ&˜&šžœ ž˜šžœžœžœž˜"Kšœ?˜?——Kšžœ˜—K˜šΟn œž œ.˜GKšœžœ˜ Kšœ žœžœžœžœžœžœž˜?šž˜Kšž˜KšœF˜FKšœž œ˜Kšžœž˜Kšžœ˜—šœ žœžœž˜Kšœ˜Kšœžœ˜Kšœžœžœ˜4Kšœžœžœ˜3Kšžœ ˜—šœ žœžœžœž˜%Kšœžœ"˜9Kšœžœ"˜9Kšœ˜Kšžœ˜Kšžœ ˜—šœ žœžœ žœž˜/šžœ ˜šž˜šž˜Kšœžœžœžœ˜Kšœž œ$˜Bšžœžœžœž˜Kšœ+˜+Kšžœ˜—Kšœžœ˜—Kšœ'˜'Kšž˜—Kšžœžœ˜ —Kšžœ ˜—šœ žœž˜šžœž˜Kšœžœ"˜8Kšœ˜Kšžœ˜—Kšžœ ˜——K˜Kš ˜˜Kšœ žœ˜Kšœ˜Kšœ žœžœ˜"Kšœ žœ˜K˜Kš G˜GKš ˜Kšœ˜Kš œžœ žœžœžœ˜1Kšœžœžœžœ ˜OK˜Kšœ˜Kšžœ žœ˜$Kšžœ žœ˜$šžœ žœž˜Kšž˜Kšœžœ ˜3šžœžœžœž˜Kšœ5˜5Kšžœ˜—šžœ žœž˜Kšžœ ˜Kšœ# ˜2Kšžœ˜—Kšžœ˜—šžœž ˜&Kšœ%˜%šžœžœž˜Kšžœ ˜Kšœ# ˜2Kšžœ˜—Kšžœ˜—Kšœ $˜CKš I˜IKš #˜#K˜Kšœ˜Kšœžœ ˜šžœ ž˜šœ ž˜šžœž ˜ Kšœ˜Kšœžœ˜KšžœC˜HKšžœ˜—Kšœ˜Kšœ'˜'Kšžœ˜—šœž˜Kšœžœ˜ šžœž ˜%Kšœ˜Kšœ˜Kšœžœ˜KšžœD˜IKšžœ˜—Kšœžœ žœ˜%Kšœžœ ˜(Kšœ˜Kšœ˜šžœ žœ˜Kšœ˜Kšœžœ˜Kšžœ@˜E—Kšžœ˜—šœž˜Kšœ žœ˜šžœ˜šžœ˜Kšœ˜Kšœžœ˜KšžœM˜R——Kšœžœ ˜(Kšœ˜Kšœ žœ˜*šžœž ˜Kšœ˜KšžœG˜LKšžœ˜—š žœžœžœžœžœž˜?Kšœ˜Kšžœ;˜@Kšžœ˜—šžœžœž˜1šžœžœž ˜;Kšœ˜Kšžœžœžœ1˜jKšžœ˜—Kšžœ˜—šžœ)ž˜/Kšœžœžœ˜-—šœ˜Kšœ@˜@—Kšœ$˜$šžœžœž˜&Kš œ žœžœžœžœ0˜|Kšžœžœ žœžœ˜Kšžœ˜—Kšžœ˜—šžœžœž˜šœ ž˜Kšœ˜Kšœžœ ˜(Kšœ˜Kšœ žœ˜*Kšžœ˜—šœž˜Kšœžœ ˜'Kšœžœ ˜(Kšœ˜Kšœ žœ˜*šžœž˜KšžœG˜L—š žœžœžœžœž˜9Kšžœ;˜@—šžœžœžœž˜"Kšžœ?˜D—šžœž˜ šžœžœž˜1šžœžœž ˜šžœžœ˜6Kšžœžœžœžœ'˜l——šž˜KšœC˜C—Kšžœ˜—šžœ˜ Kšœ˜Kšœžœ˜KšžœD˜IK˜———Kšœ˜K˜šžœ ž˜šœ/˜/Kšœ-žœ˜2—šžœž '˜7š žœžœžœžœžœžœž˜@KšžœG˜L—Kšžœ˜——K˜šžœ œ˜K˜—šŸœž˜Kšœ<žœž˜Išœ žœ/˜=Kšœ˜šœžœž˜Kšœ˜Kšœ"˜"Kšœ<˜˜>Kšœ=˜=KšœO˜OKšœ˜—K˜šœžœžœ ˜2K˜Kšžœ˜Kšžœ˜Kšžœ˜Kšžœ˜Kšžœ˜Kšžœ˜Kšžœ˜Kšžœ˜Kšœ˜Kšœ˜Kšœ˜Kšœ!˜!Kšœ(˜(Kšœ"˜"Kšœ1˜1Kšœ ˜ —K˜Kšžœ˜———…—DΠZh