(* <<>> *) MODULE Connect232 ; (*) * A communications routine via the RS232 line to another host. * Parameters are: * * EscChar The "escape" character, when this character is read * from the keyboard return to caller. * HalfDuplex The state of the host's connection, if HalfDuplex is * true echo the keyboard characters locally. * TabletOk If true, the yellow button on the puck causes an * exit too. 5-Oct-83. Change cursor shape and allow ANY puck button to cause an exit. * RETURN: ConCharExit if caused exit, * ConButtonExit for puck button. (*) EXPORTS (*-------------*) IMPORTS IO_Unit FROM IO_Unit; IMPORTS IOErrors FROM IOErrors; TYPE (* What caused "Connect" to exit *) ConExitFlag = (ConCharExit, ConButtonExit) ; FUNCTION Connect( EscChar: Char; HalfDuplex, TabletOk: Boolean ) : ConExitFlag; PRIVATE (*---------------*) IMPORTS Screen FROM Screen ; IMPORTS System FROM System ; IMPORTS IO_Others FROM IO_Others; FUNCTION Connect( EscChar: Char; HalfDuplex, TabletOk: Boolean ) : ConExitFlag; CONST NUL = Chr(#000) ; BS = Chr(#010) ; TAB = Chr(#011) ; LF = Chr(#012) ; CR = Chr(#015) ; CtrlQ = Chr(#021) ; CtrlS = Chr(#023) ; VAR hpos: Integer ; (* current position in the line (for tabs) *) oldX, oldY: Integer ; (* Old cursor offsets *) quit: Boolean ; (* loop control *) LineChr, KeyChr: Char; (* current RS232 and keyboard characters *) OldCurs, NewCurs: CurPatPtr ; (* Old and New cursors (if TabletOk) *) return: ConExitFlag ; (* the exit flag *) PROCEDURE WriteChr( c: Char ) ; BEGIN SPutChr( c ) ; Hpos := Hpos + 1 END ; HANDLER CtlC ; BEGIN END ; BEGIN (*-Connect-*) (* Allocate cursor space *) New( 0, 4, NewCurs) ; New( 0, 4, OldCurs) ; (* Clear the cursor area *) RasterOp(RXor, 64, 64, 0, 0, 4, RECAST(NewCurs, RasterPtr), 0, 0, 4, RECAST(NewCurs, RasterPtr) ) ; (* Cursor values from file: Connect3.Cursor *) NewCurs^[ 0,0] := #40 ; NewCurs^[ 1,0] := #120 ; NewCurs^[ 1,1] := #1642 ; NewCurs^[ 1,2] := #167000 ; NewCurs^[ 2,0] := #210 ; NewCurs^[ 2,1] := #1024 ; NewCurs^[ 2,2] := #42000 ; NewCurs^[ 3,0] := #404 ; NewCurs^[ 3,1] := #1610 ; NewCurs^[ 3,2] := #42000 ; NewCurs^[ 4,0] := #1002 ; NewCurs^[ 4,1] := #1024 ; NewCurs^[ 4,2] := #42000 ; NewCurs^[ 5,0] := #404 ; NewCurs^[ 5,1] := #1642 ; NewCurs^[ 5,2] := #162000 ; NewCurs^[ 6,0] := #2211 ; NewCurs^[ 7,0] := #5122 ; NewCurs^[ 7,1] := #100000 ; NewCurs^[ 8,0] := #10444 ; NewCurs^[ 8,1] := #40000 ; NewCurs^[ 9,0] := #20210 ; NewCurs^[ 9,1] := #20000 ; NewCurs^[10,0] := #40120 ; NewCurs^[10,1] := #10000 ; NewCurs^[11,0] := #20210 ; NewCurs^[11,1] := #20000 ; NewCurs^[12,0] := #10444 ; NewCurs^[12,1] := #40000 ; NewCurs^[13,0] := #5122 ; NewCurs^[13,1] := #100000 ; NewCurs^[14,0] := #2211 ; NewCurs^[15,0] := #404 ; NewCurs^[16,0] := #1002 ; NewCurs^[17,0] := #404 ; NewCurs^[18,0] := #210 ; NewCurs^[19,0] := #120 ; NewCurs^[20,0] := #40 ; (* Debug :- %) Writeln('TabletOk = ', TabletOk) ; (% Debug *) SCurOn ; (* ? *) (* Set up our cursor, or turn the cursor off if we can't use a cursor *) IF TabletOk THEN BEGIN IOReadCursPicture( OldCurs, oldX, oldY ) ; IOLoadCursor( NewCurs, 0, 0) ; IOSetModeTablet( relTablet ) ; IOCursorMode( TrackCursor ) END ELSE IOCursorMode( OffCursor ) ; (* Turn it off *) return := ConCharExit ; (* Assume the exit by escape char *) quit := False ; WHILE NOT quit DO BEGIN (*---------- RS232 Input ----------*) IF (IOCRead(RS232In, LineChr)=IOEIOC) THEN BEGIN LineChr := Chr( Land( Ord(LineChr), #177) ) ; IF (LineChr = TAB) THEN BEGIN WriteChr( ' ' ) ; WHILE (Hpos MOD 8) <> 0 DO WriteChr( ' ' ) END ELSE IF (LineChr = BS) THEN BEGIN IF Hpos > 0 THEN BEGIN (* Delete the character *) SBackSpace( ' ' ); SPutChr( ' ' ) ; SBackSpace( ' ' ) ; Hpos := Hpos - 1 END END ELSE IF (LineChr IN [NUL, CtrlS, CtrlQ]) THEN (* NOTHING *) ELSE WriteChr( LineChr ) ; (* write it *) IF (LineChr IN [CR, LF]) THEN Hpos := 0 ; (* a new line *) END ; (* RS232 input *) (*---------- Keyboard Input ----------*) IF (IOCRead(TransKey, KeyChr)=IOEIOC) THEN BEGIN IF (KeyChr = EscChar) THEN BEGIN quit := True END ELSE BEGIN IF IOCWrite(RS232Out, KeyChr)<>IOEIOC THEN KeyChr := Chr(#277) ; IF HalfDuplex THEN WriteChr( KeyChr ) END END ; (* Keyboard input *) (*---------- Tablet Input ----------*) IF TabletOk AND TabSwitch THEN BEGIN return := ConButtonExit ; quit := True END END ; (* while *) (* Restore cursor *) IF TabletOk THEN IOLoadCursor( OldCurs, oldX, oldY ) ELSE IOCursorMode( TrackCursor ) ; (* I assume it was originally on *) Dispose( NewCurs ) ; Connect := return END . (*-Connect-*) (* <<>> *) PROGRAM Kermit(Input,Output); (*) * 29-Nov-83 Allow eight bit file transfer with SET EIGHT-BIT ON/OFF * add global flag and extra SET command [pgt001] * For byte value 0..255 the end of (data) string value is now -1, * and end of file value -2. * 1-Dec-83 Place all globals into module KermitGlobals. (*) IMPORTS Stdio FROM Stdio ; IMPORTS KermitGlobals FROM KermitGlobals ; (**********) IMPORTS KermitUtils FROM KermitUtils ; IMPORTS KermitParms FROM KermitParms ; IMPORTS KermitHelp FROM KermitHelp ; IMPORTS KermitError FROM KermitError ; IMPORTS KermitSend FROM KermitSend ; IMPORTS KermitRecv FROM KermitRecv ; IMPORTS Connect232 FROM Connect232 ; IMPORTS PMatch FROM PMatch ; IMPORTS PopCmdParse FROM PopCmdParse ; IMPORTS Perq_String FROM Perq_String ; IMPORTS Screen FROM Screen ; IMPORTS IO_Unit FROM IO_Unit ; IMPORTS IOErrors FROM IOErrors; IMPORTS IO_Others FROM IO_Others; IMPORTS System FROM System; IMPORTS Sleep FROM Sleep; (* Handle ^C's from the console -pt*) HANDLER CtlC ; BEGIN (*-CtlC-*) IOKeyClear ; (* Remove ^C from input stream *) CtrlCPending := False ; (* Clear to prevent next ^C from aborting job *) FromConsole := AbortNow (* Set our flag *) END ; (*-CtlC-*) HANDLER HelpKey(VAR str: Sys9s) ; (* Make the HELP key generate the correct command (i.e. not a switch) -pt*) BEGIN (*-HelpKey-*) str := 'HELP ' ; str[5] := Chr( CR ) END ; (*-HelpKey-*) PROCEDURE OverHd( p,f: Stats; VAR o:Integer); (* Calculate OverHead as % *) (* OverHead := (p-f)*100/f *) BEGIN IF (f = 0.0) THEN o := 0 ELSE o := Round( (p-f)*100/f ) END; PROCEDURE CalRat(f: Stats; t:Integer; VAR r:Integer); (* Calculate Effective Baud Rate *) (* Rate = f*10/t *) BEGIN IF (t = 0) THEN r := 0 ELSE r := Round( f*10/t ) END; PROCEDURE Statistics ; VAR overhead, effrate : Integer; BEGIN (*-Statistics-*) (* print info on number of packets etc *) (* All output here was originally to STDERR -pt*) Writeln ; Writeln('Packets sent: ',NumSendPacks:1); Writeln('Packets received: ',NumRecvPacks:1); (* Calculate overhead *) OverHd(ChInPack,ChInFile,overhead); IF (Overhead <> 0) THEN BEGIN Writeln('Overhead (%): ' ,overhead:1); END; IF (RunTime <> 0) THEN BEGIN (* calculate effective rate *) CalRat(ChInFile,RunTime,effrate); Writeln('Effective Rate: ',effrate:1); END; (* Transmit stats *) Inverse( TRUE ) ; Writeln(' Send :-') ; Inverse( FALSE ) ; Writeln('Number of ACK: ',NumACKrecv:1); Writeln('Number of NAK: ',NumNAKrecv:1); Writeln('Number of BAD: ',NumBADrecv:1); (* Transmit stats *) Inverse( TRUE ) ; Writeln(' Receive :-') ; Inverse( FALSE ) ; Writeln('Number of ACK: ',NumACK:1); Writeln('Number of NAK: ',NumNAK:1); Writeln END ; (*-Statistics-*) PROCEDURE FinishUp; (* do any End of Program clean up *) BEGIN Sclose(DiskFile); SYSfinish; (* do System dependent *) END; PROCEDURE DoConnect ; (* Connect to the other host -pt*) VAR whyExit: ConExitFlag ; (* Why "connect" exited *) ch: Char ; (* the character after the "escape" char *) BEGIN (*-DoConnect-*) Writeln('[Connecting to host. Type Control-', EscPrint, ' C or any button on the puck]') ; REPEAT whyExit := Connect( EscapeChar, HalfDuplex, TRUE) ; (* Get the command *) IF (whyExit = ConButtonExit) THEN (* the button was pressed *) BEGIN Nap( 10 ) ; ch := 'C' (* Close the connection *) END ELSE WHILE (IOCRead(TransKey, ch) <> IOEIOC) DO ; IF (ch = EscapeChar) THEN XmtChar( EscapeChar ) ELSE IF (ch = '?') THEN BEGIN Writeln ; Writeln('When CONNECT''ed to another host, type Control-', EscPrint) ; Writeln('followed by :-') ; Writeln(' C to close the connection') ; Writeln(' ^', EscPrint, ' to send that character') ; Writeln(' ? for this information') ; Writeln('[Back to host]') END (* help *) UNTIL (Uppercase(ch) = 'C') ; Writeln ; Writeln('[Connection closed. Returning to PERQ]') END ; (*-DoConnect-*) BEGIN StdIOInit; SYSinit; (* system dependent *) done:=False; Writeln ; REPEAT KermitInit; (* initialize *) WHILE NOT (RunType IN [transmit, receive, setparm]) AND (NOT done) DO BEGIN CmdIndex := GetCmdLine(NullIdleProc, 'Kermit-PQ', CmdLine, CmdSpelling, Inf, RECAST(MainMenu, pNameDesc), firstPress, OK_to_pop) ; ConvUpper( CmdSpelling ) ; (* Make it upper case *) (* see what the command was *) CASE CmdIndex OF 1: DoConnect ; (* CONNECT *) 2: done := True ; (* EXIT *) 3: DoHelp ; (* HELP *) 4: done := True ; (* QUIT *) 5: RunType := Receive ; (* RECEIVE *) 6: RunType := Transmit; (* SEND *) 7: RunType := SetParm ; (* SET *) 8: DoShow ; (* SHOW *) 9: Statistics ; (* STATISTICS *) 10: Writeln('%Not a KERMIT command: ', CmdSpelling) ; 11: Writeln('%Ambiguous command: ', CmdSpelling) ; 12: (* empty line *) ; 13: Writeln('%KERMIT does not take switches, type HELP.'); 14: Writeln('?Illegal character after command') ; (* ?? *) OTHERWISE: Writeln('?Unknown command: ', CmdSpelling) END (* case *) END; CASE RunType OF Receive: BEGIN (* filename is optional here *) (* Remove blanks from the cmd line *) IF (CmdLine <> '') THEN RemDelimiters( CmdLine, ' ', dumStr) ; IF GetArgument(aline) THEN BEGIN IF Exists(aline) AND FileWarning THEN BEGIN ErrorMsg('Overwriting: '); ErrorStr(aline); END; IF EightBitFile THEN (* [pgt001] *) DiskFile := Sopen(aline,StdIO8Write) ELSE DiskFile := Sopen(aline,StdIOWrite); IF (DiskFile <= StdIOError) THEN ErrorPack('Cannot Open File'); END; RecvSwitch; END; Transmit: BEGIN (* New version -pt*) (* must give file name, so ask if one was not given -pt*) IF (CmdLine = '') THEN BEGIN Write('File to transmit ', PromptChar) ; Readln( CmdLine ) (* get the response *) END ; (* What shall we do with the line ? *) (* First remove blanks *) RemDelimiters( CmdLine, ' ', dumStr) ; IF (CmdLine = '') THEN (* another empty line, do nothing *) ELSE IF IsPattern(CmdLine) THEN Writeln('%SEND does not take wild file names') ELSE SendSwitch (* SendFile checks parameters - file exists *) END; Invalid: (* nothing *); SetParm: SetParameters ; END; (* case *) UNTIL done; FinishUp; (* End of Program *) ScreenReset (* Clear up screen data *) END. (* <<>> *) MODULE KermitError ; EXPORTS IMPORTS KermitGlobals FROM KermitGlobals ; PROCEDURE ErrorMsg(msg:MsgString ) ; PROCEDURE ErrorInt( msg:MsgString; n: Integer ) ; PROCEDURE ErrorStr( str: istring ) ; PROCEDURE DebugPacket(mes : MsgString; VAR p : Ppack); PROCEDURE Verbose(c:MsgString); PRIVATE IMPORTS Screen FROM Screen ; PROCEDURE ErrorMsg(msg:MsgString ) ; (* output literal preceeded by NEWLINE *) (* to the PERQ error window -pt*) BEGIN (*-ErrorMsg-*) ChangeWindow( ErrorWindow ) ; Writeln ; Write( msg ) ; ChangeWindow( KermitWindow ) END; (*-ErrorMsg-*) PROCEDURE ErrorInt( msg:MsgString; n: Integer ) ; (* Output a number preceeded by a message *) (* to the PERQ error window -pt*) BEGIN (*-ErrorInt-*) ChangeWindow( ErrorWindow ) ; Writeln ; Write( msg, n:1 ) ; ChangeWindow( KermitWindow ) END; (*-ErrorInt-*) PROCEDURE ErrorStr( str: istring ) ; (* Output a "istring" to the error window *) VAR i: Integer ; BEGIN (*-ErrorStr-*) ChangeWindow( ErrorWindow ) ; i := 1 ; WHILE str[i] <> ENDSTR DO BEGIN IF (str[i] = LF) THEN Writeln ELSE Write( Chr(str[i]) ) ; i := i + 1 END ; ChangeWindow( KermitWindow ) END ; (*-ErrorStr-*) PROCEDURE DebugPacket(mes : MsgString; VAR p : Ppack); (* Print Debugging Info, into the error window -pt*) VAR i: Integer ; (* index into data field -pt*) BEGIN (*-DebugPacket-*) ChangeWindow( ErrorWindow ) ; (* Print all this in error window -pt*) Writeln ; Write(mes); WITH Buf[p] DO BEGIN Write( '(count:', count-#40:1 ) ; (* local "UnChar" *) Write( ') (seq:', seq-#40:1 ) ; Writeln( ') (type:', Chr(ptype), ')' ); (* Write out the data field, straight to the screen -pt*) i := 1 ; WHILE (data[i] <> ENDSTR) DO BEGIN Write( Chr(data[i]) ) ; i := i + 1 END ; Writeln ; (* done -pt*) END; ChangeWindow( KermitWindow ) (* back to kermit -pt*) END; (*-DebugPacket-*) PROCEDURE Verbose(c:MsgString); (* Print writeln if verbosity *) BEGIN IF Verbosity THEN ErrorMsg(c); END. (* <<>> *) MODULE KermitGlobals; (*) * 1-Dec-83. * Split the Kermit program file into: KermitGlobals which contains all * global information, and Kermit.Pas which is the main program file. * this allow all the kermit modules to be used by any other program. (*) EXPORTS IMPORTS CmdParse FROM CmdParse ; IMPORTS SystemDefs FROM SystemDefs ; CONST (*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*) KermitWindow = 1 ; (* Window numbers - See SysInit for their creation -pt*) ErrorWindow = 2 ; (* An error window for all messages and errors -pt*) FF = Chr(#014) ; (* A form feed to clear the windows -pt*) PromptChar = Chr(#032) ; (* PERQ character set: grey arrow head -pt*) OK_to_Pop = True ; (* Allow pop-up menus -pt*) MaxPopCmds = 10 ; (* Maximum pop-up commands -pt*) SetCount = 7 ; (* Number of SET commands [pgt001]*) SetNot = SetCount+1 ; (* Non-SET command index *) SetAmbig = SetCount+2; (* Ambiguous SET command *) ShowCount = SetCount+1;(* SET commands plus 'ALL' *) ShowNot = ShowCount+1 ; ShowAmbig = ShowCount+2 ; MainCount = 9 ; MainNot = MainCount+1 ; MainAmbig = MainCount+2 ; (*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*) return = #015 ; formfeed = #014 ; controlbar = 28; { universal manifest constants } ENDSTR = -1; (* End-of-string value [pgt001] *) MAXSTR = 100; { longest possible string } MsgLength = 20; { length of message string -pt} { ascii character set in decimal } BACKSPACE = 8; TAB = 9; lf = #012 ; (* Line feed/new line *) BLANK = 32; EXCLAM = 33; { ! } DQUOTE = 34; { " } SHARP = 35; { # } DOLLAR = 36; { $ } PERCENT = 37; { % } AMPER = 38; { & } SQUOTE = 39; { ' } ACUTE = SQUOTE; LPAREN = 40; { ( } RPAREN = 41; { ) } STAR = 42; { * } PLUS = 43; { + } COMMA = 44; { , } MINUS = 45; { - } DASH = MINUS; PERIOD = 46; { . } SLASH = 47; { / } COLON = 58; { : } SEMICOL = 59; { ; } LESS = 60; { < } EQUALS = 61; { = } GREATER = 62; { > } QUESTION = 63; { ? } ATSIGN = 64; { @ } LBRACK = 91; { [ } BACKSLASH = 92; { \ } ESCAPE = BACKSLASH; { changed - used to be @ } RBRACK = 93; { ] } CARET = 94; { ^ } UNDERLINE = 95; { _ } GRAVE = 96; { ` } LETA = 97; { lower case ... } LETB = 98; LETC = 99; LETD = 100; LETE = 101; LETF = 102; LETG = 103; LETH = 104; LETI = 105; LETJ = 106; LETK = 107; LETL = 108; LETM = 109; LETN = 110; LETO = 111; LETP = 112; LETQ = 113; LETR = 114; LETS = 115; LETT = 116; LETU = 117; LETV = 118; LETW = 119; LETX = 120; LETY = 121; LETZ = 122; LBRACE = 123; { left brace } BAR = 124; { | } RBRACE = 125; { right brace } TILDE = 126; { ~ } SOH = 1; (* ascii SOH character *) CR = 13; (* CR *) DEL = 127; (* rubout *) DEFEOL = CR ; (* default eoln *) DEFTRY = 10; (* default for number of retries *) DEFTIMEOUT = 12; (* default time out *) MAXPACK = 94; (* max is 94 ~ - ' ' *) DEFDELAY = 1; (* delay before sending first init *) NUMPARAM = 6; (* number of parameters in init packet *) DEFQUOTE = SHARP; (* default quote character *) DEFPAD = 0; (* default number OF padding chars *) DEFPADCHAR = 0; (* default padding character *) NumBuffers = 5; (* Number of packet buffers *) (* packet types *) TYPEB = 66; (* ord('B') *) TYPED = 68; (* ord('D') *) TYPEE = 69; (* ord('E') *) TYPEF = 70; (* ord('F') *) TYPEN = 78; (* ord('N') *) TYPES = 83; (* ord('S') *) TYPET = 84; (* ord('T') *) TYPEY = 89; (* ord('Y') *) TYPEZ = 90; (* ord('Z') *) TYPE CharBytes = -2..255; (* full 8-bits, with -1 == end-of-string [pgt001]*) istring = ARRAY [1..MAXSTR] OF CharBytes; MsgString = String[ MsgLength ]; (* String for various messages -pt*) (* Data Types for Kermit *) Packet = RECORD mark : CharBytes; (* SOH character *) count: CharBytes; (* # of bytes following this field *) seq : CharBytes; (* sequence number modulo 64 *) ptype: CharBytes; (* d,y,n,s,b,f,z,e,t packet type *) data : istring; (* the actual data *) (* chksum is last validchar in data array *) (* eol is added, not considered part of packet proper *) END; KermitCommand = (Transmit,Receive,SetParm,Invalid); KermitStates = (FileData,Init,Break,FileHeader,EOFile,Complete,Abort); Stats = Real ; (* Statistic counting -pt*) Ppack = 1..NumBuffers; CType = RECORD check: Integer; PacketPtr : Integer; i : Integer; fld : Integer; t : CharBytes; finished : Boolean; restart : Boolean; control : Boolean; good : Boolean; END; InType = (abortnow,nothing,CRin); (* Data types for pop-up menus *) MyCmds = ARRAY [1..MaxPopCmds] OF String[25] ; (* Menu strings *) MyMenu = RECORD Head: String[25] ;(* Heading *) numcmds: Integer ;(* Number of commands *) cmd: MyCmds (* The actual commands *) END ; MyMenuPtr = ^MyMenu ; VAR done:Boolean; bufferoverflow, finis, XOFFState:Boolean; ch:Char; XON, XOFF:Char; (* Variables for commands *) CmdSpelling, CmdLine: CString ; (* the command and rest of line *) CmdIndex: Integer ; (* Index from command parser *) Inf: pCmdList ; (* Command file pointer *) firstPress: Boolean ;(* Inital call to command parser *) (* Variables for pop-up menus *) MainMenu, (* Main Kermit menu *) SetMenu: MyMenuPtr ; (* SET commands *) OnOff: CmdArray ; (* For the SET feature ON/OFF *) (* SET variables *) EscapeChr: Char ; (* CONNECT 'escape' character -pt*) EscPrint : Char ; (* Printable verion of this character -pt*) BaudRate : String ; FileWarning: Boolean ; HalfDuplex:Boolean; Verbosity: Boolean; (* true to print verbose messages *) Debug : Boolean; EightBitFile: Boolean ; (* 8-bit flag [pgt001]*) (* Varibles for Kermit *) dumStr : String ;(* Dummy string -pt*) dumCh: Char ; (* A dummy character -pt*) aline : istring; DiskFile : Integer;(* Should be "filedesc" -pt*) SaveState: kermitstates; MaxTry : Integer; n,J : Integer; (* packet number *) NumTry : Integer; (* times this packet retried *) OldTry : Integer; NumPad : Integer; (* padding to send *) MyPad : Integer; (* number of padding characters I need *) PadChar : CharBytes; MyPadChar: CharBytes; RunType : KermitCommand; State : kermitstates; (* current state of the automaton *) MyTimeOut: Integer; (* when i want to be timed out *) TheirTimeOut : Integer; Delay : Integer; SizeRecv, SizeSend : Integer; SendEOL, SendQuote : CharBytes; myEOL,myQuote: CharBytes; NumSendPacks : Integer; NumRecvPacks : Integer; NumACK : Integer; NumNAK : Integer; NumACKrecv : Integer; NumNAKrecv : Integer; NumBADrecv : Integer; RunTime: Integer; ChInFile, ChInPack : Stats; Buf : ARRAY [1..NumBuffers] OF packet; ThisPacket : Ppack; (* current packet being sent *) LastPacket : Ppack; (* last packet sent *) CurrentPacket : Ppack; (* current packet received *) NextPacket : Ppack; (* next packet being received *) InputPacket : Ppack; (* save input to do debug *) TOPacket : packet; (* Time_Out Packet *) OldTime : Double ; (* Clock time -pt*) TimeLeft : Integer; (* until Time_Out *) FromConsole : InType;(* Input from Console during receive *) PackControl : CType; (* variables for receive packet routine *) PROCEDURE SYSinit; (* special initialization *) PROCEDURE SYSfinish; (* System dependent *) PROCEDURE KermitInit;(* initialize various parameters & defaults *) PROCEDURE ErrorPack(c:MsgString); (* Send the other host the an error packet with mesage -pt*) EXCEPTION GotErrorPacket(VAR ErrorMsg: istring) ; (*) * This is used when procedure "BuildPacket" receives an error packet * from the other Host. Handlers in procedures "RecvSwitch" and * "SendSwitch" are used to abort the current RECEIVE/SEND command * and close any disk files open. (*) PRIVATE IMPORTS Screen FROM Screen ; IMPORTS PopCmdParse FROM PopCmdParse ; IMPORTS IO_Others FROM IO_Others ; IMPORTS RS232Baud FROM RS232Baud ; IMPORTS Stdio FROM Stdio ; IMPORTS KermitUtils FROM KermitUtils ; IMPORTS KermitSend FROM KermitSend ; PROCEDURE SYSinit; (* special initialization *) BEGIN Writeln( FF ) ; (* Clear the entire screen *) (*---------- PERQ ----------*) (* Create the windows *) CreateWindow(KermitWindow, 0, 0, 767, 700, 'PERQ Kermit, Version 2.0') ; (* A cursor for the Kermit window *) SCurChr( Chr(#177) ) ; (* A black rectangle *) SCurOn ; (* Turn it on *) CreateWindow(ErrorWindow, 0, 701, 767, 322, 'Error and Message Window') ; ChangeWindow( KermitWindow ) ; (* Create pop-up menus *) New(MainMenu) ; WITH MainMenu^ DO BEGIN Head := 'Kermit' ; numcmds := MainCount ; cmd[1] := 'CONNECT' ; cmd[2] := 'EXIT' ; cmd[3] := 'HELP' ; cmd[4] := 'QUIT' ; cmd[5] := 'RECEIVE' ; cmd[6] := 'SEND' ; cmd[7] := 'SET' ; cmd[8] := 'SHOW' ; cmd[9] := 'STATISTICS' ; END ; (* with main menu *) (* ON or OFF *) OnOff[1] := 'ON' ; OnOff[2] := 'OFF' ; New(SetMenu) ; WITH SetMenu^ DO BEGIN Head := 'SET commands' ; numcmds := SetCount ; (* 7 if we include "ALL" for SHOW cmd *) cmd[1] := 'SPEED' ; cmd[2] := 'DEBUG' ; cmd[3] := 'ESCAPE' ; cmd[4] := 'WARNING' ; cmd[5] := 'LOCAL' ; cmd[6] := 'VERBOSE' ; cmd[7] := 'EIGHT-BIT' ; (* [pgt001] *) cmd[8] := 'ALL' ; (* <<<< *) END ; (* with SET menu *) (* other initialisation *) InitCmdFile(Inf, 0) ; InitPopUp ; IOCursorMode( TrackCursor ) ; firstPress := True ; (*---------- KERMIT ----------*) finis:=False; XOFFState:=False; XON:=Chr(#021); XOFF:=Chr(#023); (* SET values -pt*) EscapeChr := Chr(#034) ; (* CONNECT escape character ^\ *) EscPrint := '\' ; (* Printable version *) BaudRate := '9600' ; SetBaud( '9600', True ) ; HalfDuplex:=False ; Verbosity := False; (* default to false / only valid if local *) Debug := False; EightBitFile := False ; (* [pgt001] *) FileWarning := False ; (* Statistic counters *) NumSendPacks := 0; NumRecvPacks := 0; NumACK := 0; NumNAK := 0; NumACKrecv := 0; NumNAKrecv := 0; NumBADrecv := 0; ChInFile := 0.0; (* Statsistics are now reals. -pt*) ChInPack := ChInFile; (* Other values *) NumPad := DEFPAD; (* set defaults *) MyPad := DEFPAD; PadChar := DEFPADCHAR; MyPadChar := DEFPADCHAR; TheirTimeOut := DEFTIMEOUT; MyTimeOut := DEFTIMEOUT; Delay := DEFDELAY; SizeRecv := MAXPACK; SizeSend := MAXPACK; SendEOL := DEFEOL; MyEOL := DEFEOL; SendQuote := DEFQUOTE; MyQuote := DEFQUOTE; MaxTry := DEFTRY; END; PROCEDURE SYSfinish; (* System dependent *) BEGIN Writeln( FF ) ; Dispose( MainMenu ) ; Dispose( SetMenu ) ; DstryCmdFile( Inf ) ; END; PROCEDURE KermitInit; (* initialize various parameters & defaults *) BEGIN n := 0; RunType := invalid; DiskFile := StdIOError; (* to indicate not open yet *) ThisPacket := 1; LastPacket := 2; CurrentPacket := 3; NextPacket := 4; InputPacket := 5; WITH TOPacket DO BEGIN count := 3; seq := 0; ptype := TYPEN; data[1] := ENDSTR; END; FROMCONSOLE:=NOTHING; END; PROCEDURE CtoS(x:MsgString; VAR s:istring); (* convert constant to STIP string *) VAR i : Integer; BEGIN FOR i:=1 TO Length(x) DO s[i] := Ord(x[i]); s[Length(x)+1] := ENDSTR; END; PROCEDURE ErrorPack(c:MsgString); (* output Error packet if necessary -- then exit *) BEGIN WITH Buf[ThisPacket] DO BEGIN seq := n; ptype := TYPEE; CtoS(c,data); count := ilength(data); END; SendPacket; Writeln('%Message to other Host: ', c) END. (* <<>> *) MODULE KermitHelp ; EXPORTS PROCEDURE DoHelp ; PRIVATE IMPORTS KermitUtils FROM KermitUtils ; PROCEDURE DoHelp ; (*) * Print out the Kermit help info. Use the utilities to write the * commands in inverse video. (*) BEGIN (*-DoHelp-*) Writeln( Chr(#014) ) ; (* Clear the screen *) Inverse( TRUE ) ; Writeln(' CONNECT'); Inverse( FALSE ) ; Writeln('Connect the PERQ to another host. This allows you to log into other'); Writeln('systems.'); Inverse( TRUE ) ; Writeln(' EXIT'); Inverse( FALSE ) ; Writeln('Exit from KERMIT back to the PERQ operating system.'); Inverse( TRUE ) ; Writeln(' HELP'); Inverse( FALSE ) ; Writeln('Print instructions on various commands available in KERMIT.'); Inverse( TRUE ) ; Writeln(' QUIT'); Inverse( FALSE ) ; Writeln('Same as EXIT.'); Inverse( TRUE ) ; Writeln(' RECEIVE '); Inverse( FALSE ) ; Writeln('Receive a file group from the remote host. If an incoming file name'); Writeln('is not legal, then attempt to transform it to a similar legal name,'); Writeln('e.g. by deleting illegal or excessive characters. If the file'); Writeln('already exists, it will be superceded unless WARNING is ON.'); Inverse( TRUE ) ; Writeln(' SEND '); Inverse( FALSE ) ; Writeln('Sends a file from the PERQ to the remote host. The name of the file'); Writeln('is passed to the remote host in a special control packet, so that the'); Writeln('remote host can store it with the same name. Wildcards are not yet'); Writeln('supported.'); Inverse( TRUE ) ; Writeln(' SET '); Inverse( FALSE ) ; Writeln('Change various system-dependent parameters. For a list of keywords,'); Writeln('type SET ?.'); Inverse( TRUE ) ; Writeln(' SHOW '); Inverse( FALSE ) ; Writeln('Display various system-dependent parameters established by the SET'); Writeln('command. For a list of available keywords type SHOW ?.'); Inverse( TRUE ) ; Writeln(' STATISTICS'); Inverse( FALSE ) ; Writeln('Display some statistics about Kermit''s operations.'); Writeln END (*-DoHelp-*) . (* <<>> *) MODULE KermitParms ; (* Deal with various Kermit Parameters: Set and Show *) (* 29-Nov-83 Allow eight bit file transfer [pgt001] *) EXPORTS PROCEDURE SetParameters ; PROCEDURE DoShow ; PRIVATE IMPORTS KermitGlobals FROM KermitGlobals ; IMPORTS RS232Baud FROM RS232Baud ; IMPORTS CmdParse FROM CmdParse ; IMPORTS PopCmdParse FROM PopCmdParse ; IMPORTS PopUp FROM PopUp ; IMPORTS Perq_String FROM Perq_String ; PROCEDURE SetParameters ; (* Set Kermit flags and other communications features -pt*) VAR id, parm: String ; (* SET identifier and (possible) parameter *) switch, parmsw: Boolean ; (* Switch flags for feature and parameter *) index: Integer ; (* Command index *) PROCEDURE DoBaudRate( NewRate: String ) ; (* Try to set a new baud rate for the RS232 port *) CONST InputEnable = True ; (* Enable RS232 input *) HANDLER BadBaudRate ; BEGIN (*-BadBaudRate-*) Writeln('?Bad baud rate given: ', NewRate) ; EXIT( DoBaudRate ) END ; (*-BadBaudRate-*) BEGIN (*-DoBaudRate-*) IF (NewRate = '') THEN Writeln('%No value for SET SPEED') ELSE BEGIN (* set the rate *) SetBaud( NewRate, InputEnabled) ; (* Here if that was successful, save the new rate *) BaudRate := NewRate END END ; (*-DoBaudRate-*) FUNCTION MkOctal( src: String ): Integer ; (* convert the octal number in the source string into a number *) VAR i, sum: Integer ; (* index and summation value *) ok: Boolean ; (* loop control *) BEGIN (*-MkOctal-*) ok := True ; i := 1 ; sum := 0 ; WHILE ok DO IF NOT (src[i] IN ['0'..'7']) THEN ok := False (* reached non-octal *) ELSE BEGIN sum := sum*8 + Ord(src[i]) - #60 ; i := i + 1 ; ok := (i <= Length(src)) (* exit test *) END ; MkOctal := sum END ; (*-MkOctal-*) PROCEDURE DoEscChr( OctalStr: String ) ; (* try to set a new CONNECT escape character *) (* OctalStr contains the string representation of the octal number *) VAR val: Integer ; (* The escape character's ordinal *) BEGIN (*-DoEscChr-*) IF (OctalStr = '') THEN Writeln('?SET ESCAPE requires an octal number') ELSE IF (OctalStr[1] IN ['0'..'7']) THEN BEGIN val := MkOctal( OctalStr ) ; (* Get the value *) IF (val = 0) OR (val > #037) THEN Writeln('%Illegal ESCAPE character value: ', val:1:8) ELSE BEGIN (* set the character and its printable version *) EscapeChr := Chr( val ) ; EscPrint := Chr( val + #100 ) END END (* octal digit *) ELSE Writeln('?Non-Octal digit in SET ESCAPE parameter') END ; (*DoEscChr-*) PROCEDURE DoOnOff(VAR flag: Boolean) ; (*) * For the set feature with menu index see if is * either ON or OFF. If so, set to True or False, resp. * Otherwise write error message and leave alone. (*) VAR val: Integer ; (* Value of table search ON/OFF *) BEGIN (*-DoOnOff-*) ConvUpper( parm ) ; (* MUST be upper case *) IF (parm = '') THEN val := 3 (* not ON/OFF *) ELSE val := UniqueCmdIndex(parm, OnOff, 2) ; CASE val OF 1: flag := True ; (* ON *) 2: flag := False ; (* OFF *) 3: Writeln('%SET ', SetMenu^.Cmd[index], ' requires ON or OFF') ; 4: Writeln('%Ambiguous ON or OFF in SET ', SetMenu^.Cmd[index] ) END ; (* case *) END ; (*-DoOnOff-*) PROCEDURE SetHelp ; (* Provide help information for the command SET ? *) BEGIN (*-SetHelp-*) Writeln ; Writeln('The following features are available with the SET command :') ; Writeln ; Writeln('SPEED Change the PERQ''s line speed') ; Writeln('DEBUG ON|OFF Print debug information') ; Writeln('ESCAPE Change the CONNECT escape character') ; Writeln('WARNING ON|OFF Give warning when overwriting existing files') ; Writeln('LOCAL ON|OFF Echo CONNECT typein locally') ; Writeln('VERBOSE ON|OFF Display Kermit''s actions') ; Writeln('EIGHT-BIT ON|OFF Allow eight bit file transfer');(*[pgt001]*) Writeln END ; (*-SetHelp-*) BEGIN (*-SetParameter-*) (* If the command line is empty, prompt user *) IF (CmdLine = '') THEN BEGIN Write('Kermit-SET', PromptChar) ; Readln( CmdLine ) END ; (* get the first identifier from the line *) dumCh := NextIDString( CmdLine, id, switch ) ; (* and a possible parameter *) dumCh := NextIDString( CmdLine, parm, parmsw ) ; IF (id = '') THEN (* nothing - return *) ELSE IF switch OR parmsw THEN Writeln('%SET does not take switches') ELSE IF (id[1] = '?') THEN SetHelp ELSE BEGIN index := PopUniqueCmdIndex(id, RECAST(SetMenu, pNameDesc) ) ; (* What was the command ? *) CASE index OF 1: DoBaudRate( parm ) ; (* SPEED *) 2: DoOnOff( debug ) ; (* DEBUG *) 3: DoEscChr( parm ) ; (* ESCAPE *) 4: DoOnOff( FileWarning ) ; (* WARNING *) 5: DoOnOff( HalfDuplex ) ; (* LOCAL *) 6: DoOnOff( Verbosity ) ; (* VERBOSE *) 7: DoOnOff( EightBitFile ) ; (* EIGHT-BIT [pgt001]*) 8: Writeln('%Not a SET feature: ', id) ; 9: Writeln('%Ambiguous SET feature: ', id) END ; (* case *) END (* else *) END ; (*-SetParameter-*) PROCEDURE DoShow ; (* Show the Kermit flags and parameters *) VAR flag: ARRAY [Boolean] OF String[3] ; (* OF or OFF *) id: String ; (* identifier *) switch: Boolean ; (* SHOW /xxx flag *) i: Integer ; (* Index *) PROCEDURE Feature( index: Integer ) ; (* write a single feature - Index into SetMenu *) BEGIN (*-Index-*) CASE index OF 1: Writeln('Baud rate ', BaudRate) ; 2: Writeln('Debug ', flag[debug]) ; 3: Writeln('Escape chr ^', EscPrint,' (Octal ', Ord(EscapeChr):1:8, ')') ; 4: Writeln('Warning ', flag[FileWarning]) ; 5: Writeln('Local ', flag[HalfDuplex]) ; 6: Writeln('Verbose ', flag[Verbosity]) ; 7: Writeln('Eight-Bit ', flag[EightBitFile]) (*[pgt001]*) END (* case *) END ; (*-Feature-*) BEGIN (*-DoShow-*) Writeln ; flag[True] := 'ON' ; flag[False]:= 'OFF' ; (* get the show feature *) dumCh := NextIDString(CmdLine, id, switch) ; IF (id = '') THEN id := 'ALL' ; (* Default *) IF switch THEN Writeln('%SHOW does not take switches') ELSE IF (id[1] = '?') THEN (* simple help *) BEGIN Writeln('One of the following :-') ; WITH SetMenu^ DO FOR i := 1 TO ShowCount DO (* include 'ALL' *) Writeln( Cmd[i] ) END ELSE (* find feature's index *) BEGIN (* add 'ALL' to the search *) SetMenu^.numcmds := ShowCount ; i := PopUniqueCmdIndex( id, RECAST(SetMenu, pNameDesc) ) ; SetMenu^.numcmds := SetCount ; IF (i <= SetCount) THEN Feature( i ) ELSE IF (i = ShowCount) THEN BEGIN FOR i := 1 TO SetCount DO Feature(i) END ELSE IF (i = ShowNot) THEN Writeln('?Not a SHOW parameter: ', id) ELSE IF (i = ShowAmbig) THEN Writeln('%Ambiguous SHOW parameter: ', id) END ; (* else *) Writeln END . (*-DoShow-*) (* <<>> *) MODULE KermitRecv ; (* 29-Nov-83 Allow eight bit file transfer (c.f. sopen call) [pgt001] *) (* 30-Nov-83 During a receive clear the screen and show characters *) (* and packets received. [pgt002] *) EXPORTS FUNCTION ReceiveACK : (* Returning *) Boolean; PROCEDURE RecvSwitch; (* this procedure is the main receive routine *) PRIVATE IMPORTS KermitGlobals FROM KermitGlobals ; IMPORTS KermitUtils FROM KermitUtils ; IMPORTS Stdio FROM Stdio ; IMPORTS KermitError FROM KermitError ; IMPORTS KermitSend FROM KermitSend ; (* for sending ACKs and NAKs, etc *) IMPORTS Screen FROM Screen ; (* screen control [pgt002] *) VAR OldChInFile: Stats ; (* Characters in file [pgt002]*) BadPackets: Integer ; (* Bad packet count for this recv [pgt002]*) {$RANGE-} (* Range checks off to see if it runs faster (16-Jan-84)*) PROCEDURE Field1; (* Count *) VAR test: Boolean; BEGIN WITH Buf[NextPacket] DO BEGIN WITH PackControl DO BEGIN Buf[InputPacket].count := t; count := UnChar(t); test := (count >= 3) OR (count <= SizeRecv-2); (* IF (NOT test) AND Debug THEN ErrorMsg('Bad count'); *) good := good AND test; END; END; END; PROCEDURE Field2; (* Packet Number *) VAR test : Boolean; BEGIN WITH Buf[NextPacket] DO BEGIN WITH PackControl DO BEGIN Buf[InputPacket].seq := t; seq := UnChar(t); test := (seq >= 0) OR (seq <= 63); (* IF (NOT test) AND Debug THEN ErrorMsg('Bad seq number'); *) good := test AND good; END; END; END; PROCEDURE Field3; (* Packet Type *) VAR test : Boolean; BEGIN WITH Buf[NextPacket] DO BEGIN WITH PackControl DO BEGIN ptype := t; Buf[InputPacket].ptype := t; test := IsValidPType(ptype); (* IF (NOT test) AND Debug THEN ErrorMsg('Bad Packet Type'); *) good := test AND good; END; END; END; PROCEDURE Field4; (* Data *) BEGIN WITH PackControl DO BEGIN PacketPtr := PacketPtr+1; Buf[InputPacket].data[PacketPtr] := t; WITH Buf[NextPacket] DO BEGIN IF (t = MyQuote) THEN (* character is quote *) BEGIN IF control THEN (* quote ,quote *) BEGIN data[i] := MyQuote; i := i+1; control := False; END ELSE (* set control on *) control := True END ELSE (* not quote *) IF control THEN (* convert to control *) BEGIN data[i] := ctl(t); i := i+1; control := False END ELSE (* regular data *) BEGIN data[i] := t; i := i+1; END; END; END; END; PROCEDURE Field5; (* Check Sum *) VAR test : Boolean; BEGIN WITH PackControl DO BEGIN PacketPtr := PacketPtr +1; Buf[InputPacket].data[PacketPtr] := t; Buf[InputPacket].data[PacketPtr + 1] := ENDSTR; check := CheckFunction(check); check := MakeChar(check); test := (t=check); IF (NOT test) AND Debug THEN ErrorMsg('Bad CheckSum'); good := test AND good; Buf[NextPacket].data[i] := ENDSTR; finished := True; (* set finished *) END; END; PROCEDURE BuildPacket; (* receive packet & validate checksum *) VAR temp : Ppack; BEGIN WITH PackControl DO BEGIN WITH Buf[NextPacket] DO BEGIN IF (t <> ENDSTR) THEN IF restart THEN BEGIN (* read until get SOH marker *) IF (t = SOH) THEN BEGIN finished := False; (* set varibles *) control := False; good := True; seq := -1; (* set return values to bad packet *) ptype := QUESTION; data[1] := ENDSTR; data[MAXSTR] := ENDSTR; restart := False; fld := 0; i := 1; PacketPtr := 0; check := 0; END; END ELSE (* Not restart -pt*) (* have started packet *) BEGIN IF (t = SOH) THEN (* check for restart or EOL *) restart := True ELSE IF (t = myEOL) THEN BEGIN finished := True; good := False; END ELSE BEGIN CASE fld OF (* increment field number *) 0: fld := 1; 1: fld := 2; 2: fld := 3; 3: IF (count = 3) (* no data *) THEN fld := 5 ELSE fld := 4; 4: IF (PacketPtr>=count-3) (* end of data *) THEN fld := 5; END (* case *); IF (fld <> 5) THEN check := check+t; (* add into checksum *) CASE fld OF 1: Field1; 2: Field2; 3: Field3; 4: Field4; 5: Field5; END; (* case *) END; END; IF finished THEN BEGIN IF (ptype = TYPEE) AND good THEN (* error_packets *) BEGIN SendACK(n); (* send ACK *) RAISE GotErrorPacket( data ) ; (* ********** *) END; NumRecvPacks := NumRecvPacks+1; IF Debug THEN BEGIN DebugPacket('Received: ',InputPacket); IF good THEN ErrorMsg('Is Good'); END; temp := CurrentPacket; CurrentPacket := NextPacket; NextPacket := temp; END; END; END; END; FUNCTION ReceivePacket: Boolean; BEGIN WITH PackControl DO BEGIN StartTimer; good := False ; finished := False; restart := True; (* No Keyboard Interupt - Set by ^C handler -pt*) FromConsole := nothing; REPEAT t := GetIn; CheckTimer ; IF (FromConsole = abortnow) THEN BEGIN State := ABORT ; ReceivePacket := False ; EXIT( ReceivePacket ) END; BuildPacket; UNTIL finished OR (TimeLeft <= 0); IF (TimeLeft <= 0) THEN BEGIN Buf[CurrentPacket] := TOPacket; restart := True; IF NOT ((RunType=Transmit) AND (State=Init)) THEN BEGIN ErrorInt('%Timed out ', n) END; END; StopTimer; IF NOT good THEN BadPackets := BadPackets + 1 ; ReceivePacket := good; END; END; FUNCTION ReceiveACK : (* Returning *) Boolean; (* receive ACK with correct number *) VAR Ok: Boolean; BEGIN Ok := ReceivePacket; WITH Buf[CurrentPacket] DO BEGIN IF (ptype = TYPEY) THEN NumACKrecv := NumACKrecv+1 ELSE IF (ptype = TYPEN) THEN NumNAKrecv := NumNAKrecv+1 ELSE NumBadrecv := NumBadrecv +1; (* got right one ? *) ReceiveACK := ( Ok AND (ptype=TYPEY) AND (n=seq)) END; END; PROCEDURE GetFile((* Using *) data:istring); (* create file from fileheader packet *) VAR len: Integer; PROCEDURE Strip( var name: istring ) ; (* Strip off any blanks (usually trailing) from the file name *) VAR i, newpos: integer ; BEGIN (*-Strip-*) newpos := 1 ; (* this is the new character position for non-blanks *) FOR i := 1 TO ilength(name) DO IF (name[i] = blank) THEN (* skip it by not incrementing "newpos" *) ELSE BEGIN (* restore character *) name[newpos] := name[i] ; newpos := newpos + 1 END ; name[newpos] := ENDSTR END ; (*-Strip-*) BEGIN WITH Buf[CurrentPacket] DO BEGIN IF (DiskFile = StdIOError) THEN (* check if we already have a file *) BEGIN Strip( data ) ; (* remove any blanks *) IF Verbosity THEN BEGIN ErrorMsg ('Creating file: '); ErrorStr(data); END; IF Exists(data) AND FileWarning THEN BEGIN ErrorMsg('File already exists '); ErrorStr(data); ErrorMsg('Creating: '); (* Make it .A *) len := ilength(data) + 1 ; (* first free char pos *) data[len] := PERIOD ; data[len+1] := leta ; data[len+2] := ENDSTR; ErrorStr(data) END; IF EightBitFile THEN DiskFile := Sopen(data,StdIO8Write) ELSE DiskFile := Sopen(data,StdIOWrite); END; IF (Diskfile <= StdIOError) THEN ErrorPack('Cannot create file '); END; END; PROCEDURE ReceiveInit; (* receive init packet *) (* respond with ACK and our parameters *) BEGIN IF (NumTry > MaxTry) THEN BEGIN State := Abort; ErrorMsg('Cannot receive init'); END ELSE BEGIN Verbose('Receiving Init'); NumTry := NumTry+1; IF ReceivePacket AND (Buf[CurrentPacket].ptype = TYPES) THEN BEGIN WITH Buf[CurrentPacket] DO BEGIN n := seq; DeCodeParm(data); END; (* now send mine *) WITH Buf[ThisPacket] DO BEGIN count := NUMPARAM; seq := n; Ptype := TYPEY; EnCodeParm(data); END; SendPacket; NumACK := NumACK+1; State := FileHeader; OldTry := NumTry; NumTry := 0; n := (n+1) MOD 64 END ELSE BEGIN IF Debug THEN ErrorMsg('Received Bad init'); SendNAK(n); END; END; END; PROCEDURE DataToFile; (* output to file *) VAR len,i : Integer; temp : istring; BEGIN WITH Buf[CurrentPacket] DO BEGIN len := ilength(data); ChInFile := ChInFile + len ; PutStr(data,DiskFile) END; END; PROCEDURE Dodata; (* Process Data packet *) BEGIN WITH Buf[CurrentPacket] DO BEGIN IF ( seq = ((n + 63) MOD 64)) THEN BEGIN (* data last one *) IF (OldTry > MaxTry) THEN (* number of tries? *) BEGIN State := Abort; ErrorMsg('Old data - Too many'); END ELSE BEGIN SendACK(seq); NumTry := 0; END; END ELSE BEGIN (* data - this one *) IF (n <> seq) THEN SendNAK(n) ELSE BEGIN SendACK(n); (* ACK *) DataToFile; OldTry := NumTry; NumTry := 0; n := (n+1) MOD 64; END; END; END; END; PROCEDURE DoFileLast; (* Process File Packet *) BEGIN (* File header - last one *) IF (OldTry > MaxTry) THEN (* tries ? *) BEGIN State := Abort; ErrorMsg('Old file - Too many '); END ELSE BEGIN OldTry := OldTry+1; WITH Buf[CurrentPacket] DO BEGIN IF (seq = ((n + 63) MOD 64)) THEN (* packet number *) BEGIN (* send ACK *) SendACK(seq); NumTry := 0 END ELSE BEGIN SendNAK(n); (* NAK *) END; END; END; END; PROCEDURE DoEOF; (* Process EOF packet *) BEGIN (* EOF - this one *) IF (Buf[CurrentPacket].seq <> n) THEN (* packet number ? *) SendNAK(n) (* NAK *) ELSE BEGIN (* send ACK *) SendACK(n); Sclose(DiskFile); (* close file *) DiskFile := StdIOError; OldTry := NumTry; NumTry := 0; n := (n+1) MOD 64; (* next packet *) State := FileHeader; (* change state *) END; END; PROCEDURE ReceiveData; (* Receive data packets *) VAR strend: Integer; packetnum: istring; good : Boolean; BEGIN IF (NumTry > MaxTry) THEN (* check number of tries *) BEGIN State := Abort; ErrorInt('Recv data -Too many ', n) END ELSE BEGIN NumTry := NumTry+1; (* increase number of tries *) good := ReceivePacket; (* get packet *) WITH Buf[CurrentPacket] DO BEGIN IF Verbosity THEN BEGIN ErrorInt('Receiving (Data) ', Buf[CurrentPacket].seq); END ; IF ((ptype = TYPED) OR (ptype=TYPEZ) OR (ptype=TYPEF)) AND good THEN (* check type *) CASE ptype OF TYPED: DoData; TYPEF: DoFileLast; TYPEZ: DoEOF; END (* case *) ELSE BEGIN Verbose('Expected data pack'); SendNAK(n); END; END; END; END; PROCEDURE DoBreak; (* Process Break packet *) BEGIN (* Break transmission *) IF (Buf[CurrentPacket].seq <> n) THEN (* packet number ? *) SendNAK(n) (* NAK *) ELSE BEGIN (* send ACK *) SendACK(n) ; State := Complete (* change state *) END END; PROCEDURE DoFile; (* Process file packet *) BEGIN (* File Header *) WITH Buf[CurrentPacket] DO BEGIN IF (seq <> n) THEN (* packet number ? *) SendNAK(n) (* NAK *) ELSE BEGIN (* send ACK *) SendACK(n); ChInFile := ChInFile + ilength(data) ; GetFile(data); (* get file name *) OldTry := NumTry; NumTry := 0; n := (n+1) MOD 64; (* next packet *) State := FileData; (* change state *) END; END; END; PROCEDURE DoEOFLast; (* Process EOF Packet *) BEGIN (* End Of File Last One*) IF (OldTry > MaxTry) THEN (* tries ? *) BEGIN State := Abort; ErrorMsg('Old EOF - Too many'); END ELSE BEGIN OldTry := OldTry+1; WITH Buf[CurrentPacket] DO BEGIN IF (seq =((n + 63 ) MOD 64)) THEN (* packet number *) BEGIN (* send ACK *) SendACK(seq); Numtry := 0 END ELSE BEGIN SendNAK(n); (* NAK *) END END; END; END; PROCEDURE DoInitLast; BEGIN (* Init Packet - last one *) IF (OldTry > MaxTry) THEN (* number of tries? *) BEGIN State := Abort; ErrorMsg('Old init - Too many'); END ELSE BEGIN OldTry := OldTry+1; (* packet number *) IF (Buf[CurrentPacket].seq = ((n + 63) MOD 64)) THEN BEGIN (* send ACK *) WITH Buf[ThisPacket] DO BEGIN count := NUMPARAM; seq := Buf[CurrentPacket].seq; ptype := TYPEY; EnCodeParm(data); END; SendPacket; NumACK := NumACK+1; NumTry := 0; END ELSE BEGIN SendNAK(n); (* NAK *) END; END; END; PROCEDURE ReceiveFile; (* receive file packet *) VAR good: Boolean; BEGIN IF (NumTry > MaxTry) THEN (* check number of tries *) BEGIN State := Abort; ErrorMsg('Recv file - Too many'); END ELSE BEGIN NumTry := NumTry+1; (* increase number of tries *) good := ReceivePacket; (* get packet *) WITH Buf[CurrentPacket] DO BEGIN IF Verbosity THEN BEGIN ErrorInt('Receiving (File) ', seq) END; (* Set up for new file [pgt002] *) OldChInFile := ChInFile ; (* Start value *) BadPackets := 0 ; SSetCursor(250, 100) ; Write('File: '); PutStr(data,stdout); Write(' ':10) ; (* blank the end of any other names *) IF ((ptype = TYPES) OR (ptype=TYPEZ) OR (ptype=TYPEF) OR (ptype=TYPEB)) (* check type *) AND good THEN CASE ptype OF TYPES: DoInitLast; TYPEZ: DoEOFLast; TYPEF: DoFile; TYPEB: DoBreak; END (* case *) ELSE BEGIN IF Debug THEN ErrorMsg('Expected File Pack'); SendNAK(n); END; END; END; END; PROCEDURE RecvSwitch; (* this procedure is the main receive routine *) HANDLER GotErrorPacket( VAR msg: istring ) ; (* Handle any error packets reveived. Write msg and exit *) BEGIN Inverse( TRUE ) ; Writeln ; Writeln('?RECV received error packet from other Host'); putstr(msg, STDOUT) ; Writeln ; Inverse( FALSE ) ; SClose( DiskFile ) ; (* Close the file, if open *) State := Abort ; EXIT( RecvSwitch ) END ; BEGIN State := Init; NumTry := 0; OldChInFile := ChInFile ; (* Start value *) BadPackets := 0 ; (* set up the progress reports (c.f. ReceiveFile too) [pgt002] *) IF NOT Verbosity THEN BEGIN SPutChr(FF) ; (* clear the screen *) SSetCursor(200, 150); Write( 'Current Packet' ); SSetCursor(200, 170); Write( 'Characters received' ); SSetCursor(200, 190); Write( 'Bad packets received' ) END ; REPEAT (* Each time thru' the loop print the values [pgt002] *) IF NOT Verbosity THEN BEGIN SSetCursor(410, 150); Write( n:8 ) ; SSetCursor(410, 170); Write( (ChInFile-OldChInFile):10:0 ) ; SSetCursor(410, 190); Write( BadPackets:8 ) END ; CASE State OF FileData: ReceiveData; Init: ReceiveInit; Break: (* nothing *); FileHeader: ReceiveFile; EOFile: (* nothing *); Complete: (* nothing *); Abort: (* nothing *); END; (* case *) UNTIL ( State = Abort ) OR ( State = Complete ); SSetCursor(10, 250) ; Writeln END. (* <<>> *) MODULE KermitSend ; (* 29-Nov-83 Allow eight bit file transfer (c.f. sopen call) [pgt001] *) EXPORTS PROCEDURE SendPacket; PROCEDURE SendACK((* Using *) n:Integer); (* send ACK packet *) PROCEDURE SendNAK((* Using *) n:Integer); (* send NAK packet *) PROCEDURE SendSwitch; PRIVATE IMPORTS KermitGlobals FROM KermitGlobals ; IMPORTS KermitUtils FROM KermitUtils ; IMPORTS Stdio FROM Stdio ; IMPORTS KermitError FROM KermitError ; IMPORTS KermitRecv FROM KermitRecv ; (* for receiving ACKs and NAKs *) IMPORTS UtilProgress FROM UtilProgress ; IMPORTS Sleep FROM Sleep ; {$RANGE-} (* Range checks off 16-Jan-84 *) VAR DataSendCount: Integer ; (* counter for progress *) PROCEDURE PutOut( p : Ppack); (* Output Packet *) (* Use direct calls to XmtChar to send the characters -pt*) VAR i : Integer; BEGIN IF (NumPad > 0) THEN FOR i := 1 TO NumPad DO XmtChar( Chr(PadChar) ); WITH Buf[p] DO BEGIN XmtChar( Chr(mark) ); XmtChar( Chr(count) ); XmtChar( Chr(seq) ); XmtChar( Chr(ptype) ); FOR i := 1 TO ilength(data) DO XmtChar( Chr(data[i]) ); END; END; PROCEDURE ReSendPacket; (* re -sends previous packet *) BEGIN NumSendPacks := NumSendPacks+1; ChInPack := ChInPack + NumPad + UnChar(Buf[LastPacket].count) + 3 ; IF Debug THEN DebugPacket('Re-Sending: ',LastPacket); PutOut(LastPacket); END; PROCEDURE SendPacket; (* expects count as length of data portion *) (* and seq as number of packet *) (* builds & sends packet *) VAR i,len,chksum : Integer; temp : Ppack; BEGIN IF (NumTry <> 1) AND (RunType = Transmit) THEN ReSendPacket ELSE BEGIN WITH Buf[ThisPacket] DO BEGIN mark :=SOH; (* mark *) len := count; (* save length *) count := MakeChar(len+3); (* count = 3+length of data *) seq := MakeChar(seq); (* seq number *) chksum := count + seq + ptype; IF (len > 0) THEN (* is there data ? *) FOR i:= 1 TO len DO chksum := chksum + data[i]; (* loop for data *) chksum := CheckFunction(chksum); (* calculate checksum *) data[len+1] := MakeChar(chksum); (* make printable & output *) data[len+2] := SendEOL; (* EOL *) data[len+3] := ENDSTR; END; NumSendPacks := NumSendPacks+1; IF Debug THEN DebugPacket('Sending: ',ThisPacket); PutOut(ThisPacket); IF (RunType = Transmit) THEN BEGIN ChInPack := ChInPack + NumPad + len + 6; temp := LastPacket; LastPacket := ThisPacket; ThisPacket := temp; END; END END; PROCEDURE SendACK((* Using *) n:Integer); (* send ACK packet *) BEGIN WITH Buf[ThisPacket] DO BEGIN count := 0; seq := n; ptype := TYPEY; END; SendPacket; NumACK := NumACK+1; END; PROCEDURE SendNAK((* Using *) n:Integer); (* send NAK packet *) BEGIN WITH Buf[ThisPacket] DO BEGIN count := 0; seq := n; ptype := TYPEN; END; SendPacket; NumNAK := NumNAK+1; END; PROCEDURE GetData((* Returning *) VAR newstate:KermitStates); (* get data from file into ThisPacket *) VAR (* and return next state - data & EOF *) x,c : CharBytes; i: Integer; BEGIN IF (NumTry = 1) THEN BEGIN i := 1; x := ENDSTR; WITH Buf[ThisPacket] DO BEGIN WHILE (i< SizeSend - 8 ) AND (x <> ENDFILE) (* leave room for quote & NEWLINE *) DO BEGIN x := getcf(c,DiskFile); IF (x <> ENDFILE) THEN IF IsControl(x) OR (x = SendQuote) THEN BEGIN (* control char -- quote *) IF (x = LF) THEN (* use proper EOL *) BEGIN data[i] := SendQuote; i := i+1; data[i] := Ctl(CR); i := i+1; (* LF will sent below *) END; data[i] := SendQuote; i := i+1; IF (x <> SendQuote) THEN data[i] := Ctl(x) ELSE data[i] := SendQuote; END ELSE (* regular char *) data[i] := x; IF (x <> ENDFILE) THEN BEGIN i := i+1; (* increase count for next char *) ChInFile := ChInFile + 1 ; END; END; data[i] := ENDSTR; (* to terminate string *) count := i -1; (* length *) seq := n; ptype := TYPED; IF (x = ENDFILE) THEN BEGIN newstate := EOFile; Sclose(DiskFile); DiskFile := StdIOError; END ELSE newstate := FileData; SaveState := newstate; (* save state *) END END ELSE newstate := SaveState; (* get old state *) END; FUNCTION GetNextFile: (* Returning *) Boolean; (* get next file to send in ThisPacket *) (* returns true if no more *) (* ---- -- -pt*) VAR result: Boolean; BEGIN result := True; IF (NumTry = 1) THEN WITH Buf[ThisPacket] DO BEGIN IF GetArgument(data) THEN BEGIN (* open file *) IF Exists(data) THEN BEGIN (* Initialise counter for each file to be sent *) DataSendCount := 0 ; IF EightBitFile THEN (* [pgt001] *) DiskFile := Sopen(data,StdIO8Read) ELSE DiskFile := Sopen(data,StdIORead); count := ilength(data); ChInFile := ChInFile + count ; seq := n; ptype := TYPEF; Write('[Sending '); PutStr(data,stdout); Writeln(']') ; IF (DiskFile <= StdIOError) THEN ErrorMsg('?Can''t open file'); result := False; END ELSE (* file does not exist *) BEGIN ErrorMsg('?Can''t find file: ') ; ErrorStr( data ) ; result := True (* I.e. fail: state -> abort *) END END; END ELSE result := False; (* for saved packet *) GetNextFile := result; END; PROCEDURE SendFile; (* send file name packet *) BEGIN Verbose( 'Sending '); IF (NumTry > MaxTry) THEN BEGIN ErrorMsg ('Send file - Too Many'); State := Abort; (* too many tries, abort *) END ELSE BEGIN NumTry := NumTry+1; IF GetNextFile THEN BEGIN State := Break; NumTry := 0; END ELSE BEGIN IF Verbosity THEN IF (NumTry = 1) THEN ErrorStr(Buf[ThisPacket].data) ELSE ErrorStr(Buf[LastPacket].data); SendPacket; (* send this packet *) IF ReceiveACK THEN BEGIN State := FileData; NumTry := 0; n := (n+1) MOD 64; END END; END; END; PROCEDURE SendData; (* send file data packets *) VAR newstate: KermitStates; BEGIN IF (Land(DataSendCount, #03) = 0) THEN WITH OpenList[DiskFile] DO StreamProgress( FileVar ) ; DataSendCount := DataSendCount + 1 ; (* next "SendData" *) IF (NumTry > MaxTry) THEN BEGIN State := Abort; (* too many tries, abort *) ErrorMsg ('Send data - Too many'); END ELSE BEGIN NumTry := NumTry+1; GetData(newstate); SendPacket; IF ReceiveACK THEN BEGIN State := newstate; NumTry := 0; n := (n+1) MOD 64; END END; END; PROCEDURE SendEOF; (* send EOF packet *) BEGIN Verbose ('Sending EOF'); IF (NumTry > MaxTry) THEN BEGIN State := Abort; (* too many tries, abort *) ErrorMsg('Send EOF - Too Many'); END ELSE BEGIN NumTry := NumTry+1; IF (NumTry = 1) THEN BEGIN WITH Buf[ThisPacket] DO BEGIN ptype := TYPEZ; seq := n; count := 0; END END; SendPacket; IF ReceiveACK THEN BEGIN State := FileHeader; NumTry := 0; n := (n+1) MOD 64; END END; END; PROCEDURE SendBreak; (* send break packet *) BEGIN Verbose ('Sending break'); IF (NumTry > MaxTry) THEN BEGIN State := Abort; (* too many tries, abort *) ErrorMsg('Send break -Too Many'); END ELSE BEGIN NumTry := NumTry+1; (* make up packet *) IF (NumTry = 1) THEN BEGIN WITH Buf[ThisPacket] DO BEGIN ptype := TYPEB; seq := n; count := 0; END END; SendPacket; (* send this packet *) IF ReceiveACK THEN BEGIN State := Complete; END END; END; PROCEDURE SendInit; (* send init packet *) BEGIN Verbose ('Sending Init'); IF (NumTry > MaxTry) THEN BEGIN State := Abort; (* too many tries, abort *) ErrorMsg('Cannot Initialize'); END ELSE BEGIN NumTry := NumTry+1; IF (NumTry = 1) THEN BEGIN WITH Buf[ThisPacket] DO BEGIN EnCodeParm(data); count := NUMPARAM; seq := n; ptype := TYPES; END END; SendPacket; (* send this packet *) IF ReceiveACK THEN BEGIN WITH Buf[CurrentPacket] DO BEGIN SizeSend := UnChar(data[1]); TheirTimeOut := UnChar(data[2]); NumPad := UnChar(data[3]); PadChar := Ctl(data[4]); SendEOL := CR; (* default to CR *) IF (ilength(data) >= 5) THEN IF (data[5] <> 0) THEN SendEOL := UnChar(data[5]); SendQuote := SHARP; (* default # *) IF (ilength(data) >= 6) THEN IF (data[6] <> 0) THEN SendQuote := data[6]; END; State := FileHeader; NumTry := 0; n := (n+1) MOD 64; END; END; END; PROCEDURE SendSwitch; (* Send-switch is the state table switcher for sending files. * It loops until either it is finished or a fault is encountered. * Routines called by sendswitch are responsible for changing the state. *) HANDLER GotErrorPacket(VAR msg: istring) ; (* We got an error packet when trying to receive another packet. *) (* (possibly an ACK). Write the packet data and exit SEND command *) BEGIN Inverse( TRUE ) ; Writeln ; Writeln('?SEND received an error packet from the other Host') ; putstr(msg, STDOUT) ; Writeln ; Inverse( FALSE ) ; SClose( DiskFile ) ; (* close the disk file if its open *) State := Abort ; EXIT( SendSwitch ) END ; BEGIN LoadCurs ; (* Load the progress cursors *) State := Init; (* send initiate is the start state *) NumTry := 0; (* say no tries yet *) IF (Delay > 0) THEN Sleep(Delay); REPEAT CASE State OF FileData: SendData; (* data-send state *) FileHeader: SendFile; (* send file name *) EOFile: SendEOF; (* send end-of-file *) Init: SendInit; (* send initialize *) Break: SendBreak; (* send break *) Complete: (* nothing *); Abort: (* nothing *); END (* case *); UNTIL ( (State = Abort) OR (State=Complete) ); QuitProgress ; (* Remove progress cursors *) END. (* <<>> *) MODULE KermitUtils; EXPORTS IMPORTS KermitGlobals FROM KermitGlobals ; PROCEDURE StartTimer; PROCEDURE CheckTimer ; PROCEDURE StopTimer; PROCEDURE XmtChar(ch:Char); (* Perq version -pt*) FUNCTION GetIn :CharBytes; (* get character *) FUNCTION UnChar(c:CharBytes): CharBytes; FUNCTION MakeChar(c:CharBytes): CharBytes; FUNCTION IsControl(c:CharBytes): Boolean; FUNCTION IsPrintable(c:CharBytes): Boolean; FUNCTION Ctl(c:CharBytes): CharBytes; FUNCTION IsValidPType(c:CharBytes): Boolean; FUNCTION CheckFunction(c:Integer): CharBytes; FUNCTION ilength (VAR s : istring) : Integer; FUNCTION GetArgument(VAR arg: istring): Boolean ; PROCEDURE EnCodeParm(VAR data:istring); (* encode parameters *) PROCEDURE DeCodeParm(VAR data:istring); (* decode parameters *) PROCEDURE Inverse( turn_on: Boolean ) ; PRIVATE IMPORTS IOErrors FROM IOErrors ; IMPORTS IO_Unit FROM IO_Unit ; IMPORTS IO_Others FROM IO_Others ; IMPORTS CmdParse FROM CmdParse ; IMPORTS Screen FROM Screen ; {$RANGE-} FUNCTION UnChar(c:CharBytes): CharBytes; (* reverse of makechar *) BEGIN UnChar := c - BLANK END; FUNCTION MakeChar(c:CharBytes): CharBytes; (* convert integer to printable *) BEGIN MakeChar := c + BLANK END; FUNCTION IsControl(c:CharBytes): Boolean; (* true if control *) BEGIN (* Clear the 8th bit *) c := Land( c, #177 ) ; IsControl := (c = DEL) OR (c < BLANK) END; FUNCTION IsPrintable(c:CharBytes): Boolean; (* opposite of iscontrol *) BEGIN IsPrintable := NOT IsControl(c) END; FUNCTION Ctl(c:CharBytes): CharBytes; (* c XOR 100 *) BEGIN Ctl := LXor(c, #100) END; FUNCTION IsValidPType(c:CharBytes): Boolean; (* true if valid packet type *) BEGIN IsValidPType := c IN [TYPEB, TYPED, TYPEE, TYPEF, TYPEN, TYPES, TYPET, TYPEY, TYPEZ] END; FUNCTION CheckFunction(c:Integer): CharBytes; (* calculate checksum *) VAR x: Integer; BEGIN (* CheckFunction := (c + ( c AND 300 ) /100 ) AND 77; *) x := Shift( Land(c, #300), -6) ; CheckFunction := Land(x+c, #077) END; PROCEDURE EnCodeParm((* Updating *) VAR data:istring); (* encode parameters *) VAR i: Integer; BEGIN FOR i:=1 TO NUMPARAM DO data[i] := BLANK; data[NUMPARAM+1] := ENDSTR; data[1] := MakeChar(SizeRecv); (* my biggest packet *) data[2] := MakeChar(MyTimeOut); (* when I want timeout*) data[3] := MakeChar(MyPad); (* how much padding *) data[4] := Ctl(MyPadChar); (* my padding character *) data[5] := MakeChar(myEOL); (* my EOL *) data[6] := MyQuote; (* my quote char *) END; PROCEDURE DeCodeParm(VAR data:istring); (* decode parameters *) BEGIN SizeSend := UnChar(data[1]); TheirTimeOut := UnChar(data[2]); (* when I should time out *) NumPad := UnChar(data[3]); (* padding characters to send *) PadChar := Ctl(data[4]); (* padding character *) SendEOL := UnChar(data[5]); (* EOL to send *) SendQuote := data[6]; (* quote to send *) END; { Copyright (C) 1981 by Bell Laboratories, Inc., and Whitesmiths Ltd. } { length -- compute length of string } FUNCTION ilength (VAR s : istring) : Integer; VAR n : Integer; BEGIN n := 1; WHILE (s[n] <> ENDSTR) DO n := n + 1; ilength := n - 1 END; PROCEDURE StartTimer; (* Start the time count, in clock ticks. -pt*) BEGIN IOGetTime( OldTime ) ; (* Current clock value *) TimeLeft := TheirTimeOut * 60 (* in ticks *) END; PROCEDURE CheckTimer ; (* Decrement "TimeLeft" by time between last call and now -pt*) VAR now: Double ; BEGIN IF (TimeLeft > 0) THEN (* Still counting *) BEGIN IOGetTime( now ) ; TimeLeft := TimeLeft - now[0] + OldTime[0] ; OldTime := now END END ; PROCEDURE StopTimer; BEGIN TimeLeft := Maxint; END; (*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*) PROCEDURE XmtChar(ch:Char); (* Perq version -pt*) BEGIN WHILE IOCWrite(RS232Out, ch) <> IOEIOC DO (* nothing *) ; END; FUNCTION GetIn :CharBytes; (* get character *) (* Should return NULL (ENDSTR) if no characters, Perq version -pt*) VAR byte: CharBytes ; c :Char ; BEGIN IF (IOCRead(RS232In, c) = IOEIOC) THEN BEGIN byte := land( Ord(c), #377 ) (* [pgt001] *) END ELSE byte := ENDSTR ; GetIn := byte ; (* ChInPack := ChInPack + 1.0 (@ AddTo( x, 1) *) END; (*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*) (* Get the next argument from the command line -pt*) (* Return True if an argument is available - returned in "arg" too -pt*) FUNCTION GetArgument(VAR arg: istring): Boolean ; VAR return: Boolean ; (* Return value *) i, len: Integer ; (* index and argument length *) id: String ; (* Identifier/argument from the line *) BEGIN (*-GetArgument-*) dumCh := NextIDString( CmdLine, id, return ) ; (* Get an identifier *) IF (id = '') THEN return := False (* nothing *) ELSE BEGIN return := True ; (* Success *) len := Length( id ) ; (* get the string's length *) FOR i := 1 TO len DO (* put the string in "arg" *) arg[i] := Ord( id[i] ) ; arg[len+1] := ENDSTR (* finish it off *) END ; GetArgument := return END ; (*-GetArgument-*) PROCEDURE Inverse( turn_on: Boolean ) ; (* Change chrsor function for inverse video *) BEGIN (*-Inverse-*) IF turn_on THEN SChrFunc( RNot ) ELSE SChrFunc( RRpl ) END (*-Inverse-*). (* <<>> *) MODULE STDIO ; (* Standard text file I/O *) (* from Kernighan + Plauger *) (* 29-Nov-83 Allow eight bit file transfer [pgt001] *) (* This forces us to make the end of (data) string value -1 *) (* and end of file value -2 because byte values can be 0..255 *) EXPORTS IMPORTS KermitGlobals FROM KermitGlobals ; CONST { standard file descriptors. subscripts in open, etc. } STDIN = 1; { these are not to be changed } STDOUT = 2; STDERR = 3; lineout = 4; linein = 5; FirstUserFile = STDERR ; (* First index available for user's files -pt*) { other io-related stuff } StdIOError = 0; { status values for open files } StdIOAvail = 1; StdIORead = 2; StdIOWrite = 3; StdIO8Read = 4 ; (* [pgt001] *) StdIO8Write = 5 ; (* [pgt001] *) MAXOPEN = 15; { maximum number of open files } { universal manifest constants } ENDFILE = ENDSTR - 1; (* [pgt001] *) TYPE filedesc = StdIOError..MAXOPEN; ioblock = RECORD { to keep track of open files } filevar : Text; mode : StdIOError..StdIO8Write; END; VAR openlist : ARRAY [1..MAXOPEN] OF ioblock; { open files } PROCEDURE StdIOInit; PROCEDURE putch (c : CharBytes); PROCEDURE putcf (c : CharBytes; fd : filedesc); PROCEDURE putstr (VAR s : istring; f : filedesc); FUNCTION getch (VAR c : CharBytes) : CharBytes; FUNCTION getcf (VAR c: CharBytes; fd : filedesc) : CharBytes; FUNCTION getline (VAR s : istring; fd : filedesc; maxsize : Integer) : Boolean; FUNCTION Sopen (name : istring; mode : Integer) : filedesc; PROCEDURE Sclose (fd : filedesc); FUNCTION Exists(s:istring): Boolean; PRIVATE IMPORTS Perq_string FROM Perq_String ; IMPORTS Stream FROM Stream ; IMPORTS FileSystem FROM FileSystem ; { StdIOInit -- initialize open file list } PROCEDURE StdIOInit; VAR i : filedesc; BEGIN openlist[STDIN].mode := StdIORead; openlist[STDOUT].mode := StdIOWrite; { initialize rest of files } FOR i := FirstUserFile TO MAXOPEN DO openlist[i].mode := StdIOAvail; END; { getc (UCB) -- get one character from standard input } FUNCTION getch (VAR c : CharBytes) : CharBytes; VAR ch : Char; BEGIN IF Eof THEN c := ENDFILE ELSE IF Eoln THEN BEGIN Readln; c := LF END ELSE BEGIN Read(ch); c := Ord(ch) END; getch := c END; { Copyright (C) 1981 by Bell Laboratories, Inc., and Whitesmiths Ltd. } { getcf (UCB) -- get one character from file } FUNCTION getcf (VAR c: CharBytes; fd : filedesc) : CharBytes; VAR ch : Char; BEGIN WITH openlist[fd] DO (* [pgt001] *) IF (fd = STDIN) THEN getcf := getch(c) ELSE IF Eof(filevar) THEN c := ENDFILE ELSE IF (mode = StdIO8Read) THEN (* [pgt001] *) BEGIN c := Ord( filevar^ ) ; Get( filevar ) END (* [pgt001] *) ELSE IF Eoln(filevar) THEN BEGIN Readln(filevar); c := LF END ELSE BEGIN Read(filevar, ch); c := Ord(ch) END; getcf := c END; { Copyright (C) 1981 by Bell Laboratories, Inc., and Whitesmiths Ltd. } { getline (UCB) -- get a line from file } FUNCTION getline (VAR s : istring; fd : filedesc; maxsize : Integer) : Boolean; VAR i : Integer; c : CharBytes; BEGIN {$RANGE-} i := 1; REPEAT s[i] := getcf(c, fd); i := i + 1 UNTIL (c = ENDFILE) OR (c = LF) OR (i >= maxsize); IF (c = ENDFILE) THEN i := i - 1 ; { went one too far } s[i] := ENDSTR; getline := (c <> ENDFILE) {$RANGE+} END; { Copyright (C) 1981 by Bell Laboratories, Inc., and Whitesmiths Ltd. } { putch (UCB) -- put one character on standard output } PROCEDURE putch (c : CharBytes); BEGIN IF (c = LF) THEN Writeln ELSE Write(Chr(c)) END; { Copyright (C) 1981 by Bell Laboratories, Inc., and Whitesmiths Ltd. } { putcf (UCB) -- put a single character on file fd } PROCEDURE putcf (c : CharBytes; fd : filedesc); CONST NUL = 0 ; BEGIN WITH openlist[fd] DO IF (fd = STDOUT) THEN putch(c) ELSE IF (mode = StdIO8Write) THEN (* [pgt001] *) BEGIN filevar^ := Chr(c) ; Put( filevar ) END ELSE BEGIN (* Normal text file [pgt001]*) c := Land(c, #177) ; IF (c = LF) THEN Writeln(filevar) ELSE IF (c = CR) OR (c = NUL) THEN (* ignore *) ELSE Write(filevar, Chr( c )) END ; END; { Copyright (C) 1981 by Bell Laboratories, Inc., and Whitesmiths Ltd. } { putstr (UCB) -- put out string on file } PROCEDURE putstr (VAR s : istring; f : filedesc); VAR i : Integer; BEGIN {$RANGE-} i := 1; WHILE (s[i] <> ENDSTR) DO BEGIN putcf(s[i], f); i := i + 1 END {$RANGE+} END; { MakeString -- Convert an istring into a Perq String variable -pt } PROCEDURE MakeString(src: istring; VAR dest: String) ; VAR i: Integer ; BEGIN (*-MakeString-*) i := 1 ; {$RANGE- Checks off because Length(dest) undefined at the moment -pt} WHILE (src[i] <> ENDSTR) AND (src[i] <> LF) DO BEGIN dest[i] := Chr(src[i]) ; i := i + 1 END ; {$RANGE+ Checks back on -pt} Adjust(dest, i-1) (* Set the dynamic length -pt*) END ; (*-MakeString-*) { open -- open a file for reading or writing. Perq version -pt} FUNCTION Sopen (name : istring; mode : Integer) : filedesc; VAR i : Integer; filename : String ; found : Boolean; (* Reset and Rewrite error handlers. Both set "sopen" to IOERROR -pt*) (* This means we set inital value of "sopen" before reset/rewrite -pt*) HANDLER ResetError(filnam: PathName) ; BEGIN sopen := StdIOError END ; HANDLER RewriteError(filnam: PathName) ; BEGIN sopen := StdIOError END ; BEGIN MakeString(name, filename) ; (* Convert to Perq string -pt*) { find a free slot in openlist } Sopen := StdIOError; found := False; i := 1; WHILE (i <= MAXOPEN) AND (NOT found) DO BEGIN IF (openlist[i].mode = StdIOAvail) THEN BEGIN openlist[i].mode := mode ; Sopen := i; (* Here so file handlers can reset value -pt*) IF (mode = StdIORead) OR (mode = StdIO8Read) THEN Reset(openlist[i].filevar, filename) (* [pgt001] *) ELSE Rewrite(openlist[i].filevar, filename); found := True END; i := i + 1 END END; PROCEDURE Sclose (fd : filedesc); BEGIN IF (fd >= FirstUserFile) AND (fd <= MAXOPEN) THEN BEGIN openlist[fd].mode := StdIOAvail; close(openlist[fd].filevar); END END; FUNCTION Exists(s:istring): Boolean; (* returns true if file exists. Perq version -pt*) VAR name: String ; file_id, blocks, bits: Integer ; BEGIN (*-Exists-*) (* Be quick and use a look-up; better than open/close sequence -pt*) MakeString(s, name) ; (* Get the file name as a Perq string *) file_id := FSLookUp(name, blocks, bits) ; (* Do the look-up *) Exists := (file_id <> 0) (* Zero means it does not exist *) END. (*-Exists-*)