-- Transport mechanism: Telnet interface -- [Indigo]<Grapevine>MS>GlassImpl.mesa -- Andrew Birrell 17-May-82 9:59:51 -- Mark Johnson 19-May-81 13:28:28 -- Mike Schroeder, 7-Feb-83 16:00:56 DIRECTORY Ascii, GlassDefs USING[ HandleObject, Handle, StringType ], Inline USING[ BITAND, BITOR ], PolicyDefs USING[ CheckOperation, EndOperation, Operation ], Process USING[ Pause, SecondsToTicks, SetTimeout, Yield ], PupDefs USING[ PupAddress, PupSocketID ], PupStream USING[ CreatePupByteStreamListener, RejectThisRequest, StreamClosing, veryLongWait ], PupTypes USING[ telnetSoc ], Stream, String USING[ AppendDecimal, AppendLongDecimal ]; GlassImpl: MONITOR LOCKS slp USING slp: POINTER TO MONITORLOCK IMPORTS Inline, PolicyDefs, Process, PupStream, Stream, String EXPORTS GlassDefs = BEGIN TimeOut: PUBLIC SIGNAL = CODE; SynchReply: PUBLIC SIGNAL = CODE; ReaderDied: ERROR = CODE; MyStreamClosing: ERROR = CODE; ReadString: PUBLIC PROC[str: GlassDefs.Handle, prompt: STRING, s: STRING, type: GlassDefs.StringType] RETURNS[end: CHARACTER] = BEGIN OPEN str; dummy: CHARACTER = '*; --used for echo if type = pwd-- ShowIt: PROC = BEGIN IF type # pwd THEN WriteString[s] ELSE FOR i: CARDINAL IN [0..s.length) DO WriteChar[dummy] ENDLOOP; END; Unwrite: PROCEDURE = BEGIN IF s.length > 0 THEN BEGIN WriteChar['\]; WriteChar[IF type = pwd THEN dummy ELSE s[s.length-1]]; s.length ← s.length - 1; END; END; ClearWord: PROCEDURE = BEGIN state: { alpha, other } ← other; WHILE s.length > 0 DO SELECT s[s.length-1] FROM IN ['a..'z], IN ['A..'Z], IN ['0..'9] => state ← alpha; ENDCASE => IF state # other THEN EXIT; Unwrite[]; ENDLOOP; END; c: CHARACTER; WriteString[prompt]; ShowIt[]; c ← ReadChar[]; SELECT c FROM Ascii.ControlA, Ascii.BS, Ascii.ControlW, --client wants to edit it-- Ascii.SP, Ascii.CR, Ascii.DEL => NULL; --client accepts it-- ENDCASE => IF s.length > 0 THEN { WriteString["← "L]; s.length ← 0; };--client rejects it-- DO SELECT c FROM Ascii.ControlA, Ascii.BS => Unwrite[]; Ascii.ControlW => ClearWord[]; Ascii.ControlR => { WriteChar[Ascii.CR]; WriteString[prompt]; ShowIt[] }; ENDCASE => BEGIN SELECT c FROM Ascii.SP => IF type # line AND type # any THEN { end ← c; EXIT }; Ascii.CR => IF type # any THEN { end ← c; EXIT }; Ascii.ESC, Ascii.DEL => { end ← c; EXIT }; ENDCASE => NULL; IF s.length < s.maxlength THEN BEGIN s[s.length] ← c; s.length ← s.length+1; WriteChar[IF type = pwd THEN dummy ELSE c]; END ELSE WriteChar[Ascii.BEL]; END; c ← ReadChar[]; ENDLOOP; END; Listen: PUBLIC PROC[ work: PROC[GlassDefs.Handle], socket: PupDefs.PupSocketID ← [0,0] ] = BEGIN TelnetWork: PROC[ str: Stream.Handle, from: PupDefs.PupAddress ] = BEGIN -- Note that there is an over-all assumption that the client calls -- the glass stream from only one process. The monitor locks are -- used only to synchronize between the "Reader" process and the -- client. strLock: MONITORLOCK; -- lock for this stream -- readerPSB: PROCESS; readerWanted: BOOLEAN ← TRUE; -- we maintain a circular buffer of incoming characters, -- primarily to look ahead for DEL -- -- rPos = next index for reading from buffer -- -- wPos = next index for writing into buffer -- -- rPos = wPos iff buffer is empty -- -- (wPos+1)MOD bLength = rPos iff buffer is full -- -- buffer data has "markBit" on iff datum is a "Mark" byte -- charMask: WORD = 177B; markBit: WORD = 200B; bLength: CARDINAL = 100; buffer: PACKED ARRAY [0..bLength) OF CHARACTER; rPos: CARDINAL ← 0; wPos: CARDINAL ← 0; bFuller: CONDITION; bEmptier: CONDITION; delCount: CARDINAL ← 0; charsWritten: BOOLEAN ← FALSE;--chars written but not sent-- readerDead: BOOLEAN ← FALSE; NoteDeadReader: ENTRY PROC [slp: POINTER TO MONITORLOCK] = {readerDead ← TRUE; NOTIFY bFuller}; ChangeWPos: ENTRY PROC[change: CARDINAL, slp: POINTER TO MONITORLOCK] = INLINE BEGIN ENABLE UNWIND => NULL; IF rPos = wPos THEN NOTIFY bFuller; wPos ← wPos + change; IF wPos = bLength THEN wPos ← 0; END; WLimit: ENTRY PROC [slp: POINTER TO MONITORLOCK] RETURNS[ limit: CARDINAL ] = INLINE BEGIN ENABLE UNWIND => NULL; WHILE (limit ← IF wPos >= rPos THEN IF rPos = 0 THEN bLength-1 ELSE bLength ELSE rPos-1 ) = wPos DO WAIT bEmptier ENDLOOP; END; AddDel: ENTRY PROC [slp: POINTER TO MONITORLOCK] = BEGIN ENABLE UNWIND => NULL; delCount ← delCount + 1; Stream.SendAttention[str, 0]; Stream.SetSST[str, 1 --data mark--]; END; GetByte: ENTRY PROC [slp: POINTER TO MONITORLOCK] RETURNS[ c: UNSPECIFIED ] = BEGIN ENABLE UNWIND => NULL; WHILE rPos = wPos DO IF charsWritten THEN { charsWritten ← FALSE; Stream.SendNow[str] }; IF readerDead THEN ERROR ReaderDied[]; WAIT bFuller; IF rPos = wPos THEN SIGNAL TimeOut[]; ENDLOOP; c ← buffer[rPos]; rPos ← rPos+1; IF rPos = bLength THEN rPos ← 0; NOTIFY bEmptier; -- in case buffer was full -- IF c = Ascii.DEL THEN delCount ← delCount-1; END; TerminateReader: ENTRY PROC [slp: POINTER TO MONITORLOCK] = BEGIN -- ensure reader isn't waiting because of a full input buffer -- UNTIL readerDead DO rPos ← wPos ← 0; NOTIFY bEmptier; WAIT bFuller ENDLOOP; END; Reader: PROC = BEGIN Stream.SetInputOptions[str, [terminateOnEndPhysicalRecord: TRUE, signalLongBlock: FALSE, signalShortBlock: FALSE, signalSSTChange: FALSE, signalEndOfStream: FALSE] ]; DO ENABLE BEGIN Stream.TimeOut => RESUME; PupStream.StreamClosing => EXIT; END; used: CARDINAL; why: Stream.CompletionCode; sst: Stream.SubSequenceType; [used, why, sst] ← Stream.GetBlock[str, [ @buffer, wPos, WLimit[@strLock] ] ]; FOR index: CARDINAL IN [wPos..wPos+used) DO SELECT (buffer[index]←Inline.BITAND[buffer[index], charMask]) FROM Ascii.ControlC => { buffer[index] ← Ascii.DEL; AddDel[@strLock] }; Ascii.DEL => AddDel[@strLock]; ENDCASE => NULL; ENDLOOP; ChangeWPos[used, @strLock]; IF why = sstChange THEN BEGIN buffer[wPos] ← Inline.BITOR[sst, markBit]; ChangeWPos[1, @strLock]; IF sst = 6 --timing mark reply-- AND NOT readerWanted THEN EXIT; END; ENDLOOP; NoteDeadReader[@strLock]; END; lineWidth: CARDINAL ← 0; pageHeight: CARDINAL ← 0; terminal: CARDINAL ← 0; charPos: CARDINAL ← 0; linePos: CARDINAL ← 0; ConsiderSST: PROC[thisSST: Stream.SubSequenceType] = BEGIN SELECT thisSST FROM 1 => -- data mark -- NULL; 2 => lineWidth ← GetByte[@strLock]; 3 => pageHeight ← GetByte[@strLock]; 4 => terminal ← GetByte[@strLock]; 5 => Stream.SetSST[str, 6--timing mark reply--]; 6 => SIGNAL SynchReply[]; ENDCASE => NULL--ignore--; END; ReadChar: PROC RETURNS[c: CHARACTER] = BEGIN ENABLE PupStream.StreamClosing => ERROR MyStreamClosing; DO c ← GetByte[@strLock]; IF Inline.BITAND[c, markBit] # 0 THEN ConsiderSST[Inline.BITAND[c, charMask]] ELSE EXIT ENDLOOP; linePos ← 0; -- only count lines between input operations -- END; MyReadString: PROC[prompt: STRING, s: STRING, type: GlassDefs.StringType] RETURNS[end: CHARACTER] = { end ← ReadString[@obj, prompt, s, type] }; WriteChar: PROC[c: CHARACTER] = -- assumed to be called from only one process -- -- otherwise, we need two monitor locks: this may use ReadChar -- BEGIN ENABLE PupStream.StreamClosing => ERROR MyStreamClosing; WS: PROC[s: STRING] = BEGIN -- sneak in a string -- FOR index: CARDINAL IN [0..s.length) WHILE charPos < lineWidth DO PutSingleWidth[s[index]] ENDLOOP; END; Lf: PROC = BEGIN IF linePos+1 >= pageHeight AND pageHeight # 0 THEN BEGIN IF charPos > 0 THEN Stream.PutChar[str, Ascii.CR]; charPos ← 0; Stream.PutChar[str, Ascii.LF]; WS["Type ESC for next page ..."L]; SendNow[]; UNTIL ReadChar[] = Ascii.ESC DO ENDLOOP; Stream.PutChar[str, Ascii.CR]; charPos ← 0; END; Stream.PutChar[str, Ascii.LF]; linePos←linePos+1; END; Newline: PROC = BEGIN Stream.PutChar[str, Ascii.CR]; charPos←0; Lf[]; END; PutSingleWidth: PROC[c: CHARACTER] = INLINE BEGIN IF charPos = lineWidth AND lineWidth > 0 THEN Newline[]; Stream.PutChar[str, c]; charPos ← charPos+1; END; NoteWritten: ENTRY PROC [slp: POINTER TO MONITORLOCK] = INLINE { charsWritten ← TRUE }; Process.Yield[]; IF delCount # 0 THEN RETURN; SELECT c FROM IN [40C..177C] => PutSingleWidth[c]; Ascii.CR => Newline[]; Ascii.LF => Lf[]; Ascii.BEL => Stream.PutChar[str, c]; Ascii.TAB => DO PutSingleWidth[Ascii.SP]; IF charPos MOD 8 = 0 THEN EXIT; ENDLOOP; IN [0C..40C) => { PutSingleWidth['↑]; PutSingleWidth[c+100B] }; ENDCASE => NULL -- illegal character values --; NoteWritten[@strLock]; END; WriteString: PROC[s: STRING] = { FOR i: CARDINAL IN [0..s.length) DO WriteChar[s[i]] ENDLOOP }; WriteDecimal: PROC[n: CARDINAL] = BEGIN s: STRING = [6] -- -65536 --; String.AppendDecimal[s,n]; WriteString[s]; END; WriteLongDecimal: PROC[n: LONG CARDINAL] = BEGIN s: STRING = [11] -- -6553665536 --; String.AppendLongDecimal[s,n]; WriteString[s]; END; NoteSent: ENTRY PROC [slp: POINTER TO MONITORLOCK] = INLINE { charsWritten ← FALSE }; SendNow: PROC = { ENABLE PupStream.StreamClosing => ERROR MyStreamClosing; NoteSent[@strLock]; Stream.SendNow[str ] }; CharsLeft: PROC RETURNS[CARDINAL] = { RETURN[IF lineWidth > 0 THEN lineWidth-charPos ELSE LAST[CARDINAL] ] }; LinesLeft: PROC RETURNS[CARDINAL] = { RETURN[IF pageHeight > 0 THEN pageHeight-linePos ELSE LAST[CARDINAL] ] }; SetWidth: PROC[new: CARDINAL] = { lineWidth ← new }; SetHeight: PROC[new: CARDINAL] = { pageHeight ← new }; DelTyped: PROC RETURNS [BOOLEAN] = BEGIN InnerDelTyped: ENTRY PROC [slp: POINTER TO MONITORLOCK] RETURNS[ BOOLEAN ] = INLINE { RETURN[ delCount # 0 ] }; RETURN [InnerDelTyped[@strLock]] END; Synch: PROC = { ENABLE PupStream.StreamClosing => ERROR MyStreamClosing; Stream.SetSST[str,5] }; Flush: PROC = { WHILE delCount > 0 DO [] ← ReadChar[] ENDLOOP }; obj: GlassDefs.HandleObject ← [ReadChar, MyReadString, WriteChar, WriteString, WriteDecimal, WriteLongDecimal, SendNow, CharsLeft, LinesLeft, SetWidth, SetHeight, DelTyped, Synch, Flush]; Process.SetTimeout[@bFuller, Process.SecondsToTicks[300] ]; readerPSB ← FORK Reader[]; BEGIN ENABLE{ MyStreamClosing, ReaderDied => CONTINUE; TimeOut => RESUME }; work[@obj ! SynchReply => RESUME]; readerWanted ← FALSE; Synch[]; DO [] ← ReadChar[ ! SynchReply => EXIT ] ENDLOOP; END; TerminateReader[@strLock]; JOIN readerPSB; str.delete[str]; PolicyDefs.EndOperation[op]; END; TelnetFilter: PROC[addr: PupDefs.PupAddress] = BEGIN IF NOT PolicyDefs.CheckOperation[op] THEN PupStream.RejectThisRequest["Server full"L]; END; op: PolicyDefs.Operation = IF socket = [0,53B] THEN lily ELSE telnet; [] ← PupStream.CreatePupByteStreamListener[ IF socket = [0,0] THEN PupTypes.telnetSoc ELSE socket, TelnetWork, PupStream.veryLongWait, TelnetFilter ]; DO Process.Pause[Process.SecondsToTicks[600]] ENDLOOP; END; END.