DIRECTORY Ascii, Atom, Basics, BasicTime, BootTime, Commander, CommanderBackdoor, CommanderOps, Convert, IO, LocalRegistryAgent, NetworkName, NetworkStream, Process, ProcessProps, Rope, SystemNames, Termination; NetCommanderImpl: CEDAR MONITOR IMPORTS Atom, BasicTime, BootTime, Commander, CommanderBackdoor, CommanderOps, Convert, IO, LocalRegistryAgent, NetworkName, NetworkStream, Process, ProcessProps, Rope, SystemNames, Termination ~ { OPEN NetworkStream; NetLSCommand: Commander.CommandProc = { arg: ROPE = CommanderOps.NextArgument[cmd]; P: EnumerateCallbackProc = { cmd.out.PutF["%g %g\n", [atom[protocolFamily]], [atom[transportClass]]]; }; Enumerate[NIL, NIL, P]; }; listeners: LIST OF ListenerReg ¬ NIL; ListenerReg: TYPE ~ RECORD [ l: NetworkStream.Listener, protocolFamily, transportClass: ATOM _ NIL, local, localPort: ROPE _ NIL, sr: LocalRegistryAgent.ServiceRegistration _ NIL]; NetUnListenCommand: Commander.CommandProc = { UNTIL listeners = NIL DO LocalRegistryAgent.StopService[listeners.first.sr]; DestroyListener[listeners.first.l]; listeners ¬ listeners.rest; ENDLOOP; }; NetListenCommand: Commander.CommandProc = { ENABLE { NetworkName.Error => CommanderOps.Failed[msg]; NetworkStream.Error => CommanderOps.Failed[msg]; IO.Error => CommanderOps.Failed[Rope.Concat["i/o error ... ", GetIOErrorDetails[stream].msg]]; }; portHint, lock: ROPE; transportClass: ATOM; listener: NetworkStream.Listener; lr: ListenerReg; props: LocalRegistryAgent.PropList _ NIL; [portHint, lock, transportClass] ¬ GetNetComArgs[cmd]; listener ¬ NetworkStream.CreateListener[ protocolFamily: $ARPA, transportClass: transportClass, local: NetworkName.AddressFromName[family: $ARPA, name: NIL, portHint: portHint, components: port].addr, listenerWorkerProc: CommanderWork ]; lr ¬ [l: listener]; IF lock # NIL THEN passkey ¬ lock; [lr.protocolFamily, lr.local, lr.transportClass,,] _ GetListenerInfo[listener]; lr.localPort _ NetworkName.NameFromAddress[$ARPA, lr.local, port].name; props _ Atom.PutPropOnList[props, $BootTime, Convert.RopeFromTimeRFC822[BootTime.Get[]]]; props _ Atom.PutPropOnList[props, $StartTime, Convert.RopeFromTimeRFC822[BasicTime.Now[]]]; props _ Atom.PutPropOnList[props, $port, lr.localPort]; props ¬ Atom.PutPropOnList[props, $UserName, SystemNames.UserName[]]; lr.sr _ LocalRegistryAgent.MaintainService[ "Cedar/NetCommander(29-Jun-92)", props, 1200*1000, 400*1000]; listeners ¬ CONS[lr, listeners]; IO.PutF[cmd.out, "Connection command: NetCommander %g %g\n", [rope[lr.local]], [atom[lr.transportClass]]]; RETURN}; NetLockCommand: Commander.CommandProc = { passkey ¬ CommanderOps.NextArgument[cmd]; }; NetListenersCommand: Commander.CommandProc = { FOR tail: LIST OF ListenerReg ¬ listeners, tail.rest UNTIL tail = NIL DO lr: ListenerReg ~ tail.first; IO.PutFL[cmd.out, "%g %g (%g) %g\n", LIST[[rope[lr.local]], [atom[lr.transportClass]], [atom[lr.protocolFamily]], [refAny[lr.l]]] ]; ENDLOOP; }; GetNetComArgs: PROC[cmd: Commander.Handle] RETURNS[portHint, passkey: ROPE, transportClass: ATOM] ~ { DO arg: ROPE ¬ CommanderOps.NextArgument[cmd]; IF arg = NIL THEN EXIT; SELECT TRUE FROM Rope.Equal[arg, "-lock"] => passkey ¬ CommanderOps.NextArgument[cmd]; Rope.Fetch[arg, 0] = '- => CommanderOps.Failed[IO.PutFR1["bad argument %g\n", [rope[arg]]] ]; portHint = NIL => portHint ¬ arg; transportClass = NIL => transportClass ¬ Atom.MakeAtom[arg]; ENDCASE => CommanderOps.Failed[IO.PutFR1["Too many args %g\n", [rope[arg]]] ]; ENDLOOP; IF transportClass = NIL THEN transportClass ¬ $TCP; }; defaultPrompt: ROPE ¬ "RCedar %l%% %l"; NetworkPrompt: PROC [cmd: Commander.Handle] ~ { prompt: ROPE ~ WITH CommanderOps.GetProp[cmd, $Prompt] SELECT FROM rope: ROPE => rope, ENDCASE => defaultPrompt; IO.PutF[cmd.err, prompt, [rope["b"]], [rope["B"]]]; IO.Flush[cmd.err]; }; ExitCommander: ERROR = CODE; CommanderWork: PROC [listener: NetworkStream.Listener, in, out: IO.STREAM] ~ { ENABLE { IO.Error => IF stream=in OR stream=out THEN GOTO closeit }; filteredin: IO.STREAM ~ CRFilterStream[in]; Inner: PROC = { commander: Commander.Handle = CommanderOps.CreateFromStreams[in: filteredin, out: FlushOutStream[out]]; CommanderBackdoor.GetCommandToolData[commander].Prompt ¬ NetworkPrompt; CommanderOps.PutProp[commander, $Prompt, Rope.Concat[GetListenerInfo[listener].local, " %l%% %l"]]; [] ¬ CommanderOps.ReadEvalPrintLoop[commander]; }; IF Authentic[filteredin, out] THEN ProcessProps.AddPropList[LIST[NEW[Atom.DottedPairNode ¬ [$listener, listener]]], Inner]; GOTO closeit; EXITS closeit => { IO.Close[in ! IO.Error => CONTINUE]; IO.Close[out ! IO.Error => CONTINUE]; }; }; CRFilterData: TYPE ~ REF CRFilterDataRep; CRFilterDataRep: TYPE ~ RECORD [ source: IO.STREAM, state: {normal, postLF, postCR} ¬ normal, peeked: BOOL ¬ FALSE, peekChar: CHAR ¬ 0C, eofOnControlD: BOOL ¬ TRUE ]; CRFilterGetChar: PROC [self: STREAM] RETURNS [CHAR] ~ { data: CRFilterData ~ NARROW[self.streamData]; IF data.peeked THEN {data.peeked ¬ FALSE; RETURN [data.peekChar]}; DO ch: CHAR ~ IO.GetChar[data.source]; SELECT ch FROM Ascii.CR => IF data.state = postCR THEN {data.state ¬ normal} ELSE {data.state ¬ postLF; RETURN ['\n]}; Ascii.LF => IF data.state = postLF THEN {data.state ¬ normal} ELSE {data.state ¬ postCR; RETURN ['\n]}; Ascii.ControlD => { data.state ¬ normal; IF data.eofOnControlD THEN ERROR IO.EndOfStream[self]; RETURN [ch] }; ENDCASE => { data.state ¬ normal; RETURN [ch] }; ENDLOOP; }; CRFilterCharsAvail: PROC [self: STREAM, wait: BOOL] RETURNS [INT] ~ { data: CRFilterData ~ NARROW[self.streamData]; n: INT ¬ IO.CharsAvail[data.source]; IF n # INT.LAST THEN n ¬ n + ORD[data.peeked]; RETURN [n] }; CRFilterBackup: PROC [self: STREAM, char: CHAR] ~ { data: CRFilterData ~ NARROW[self.streamData]; IF data.peeked THEN {IO.Backup[data.source, data.peekChar]}; data.peekChar ¬ char; data.peeked ¬ TRUE; }; CRFilterEndOf: PROC [self: STREAM] RETURNS [BOOL] ~ { data: CRFilterData ~ NARROW[self.streamData]; IF data.peeked THEN RETURN[data.peekChar # Ascii.ControlD]; IF IO.EndOf[data.source] THEN RETURN[TRUE]; data.eofOnControlD ¬ FALSE; data.peekChar ¬ CRFilterGetChar[self]; data.peeked ¬ TRUE; data.eofOnControlD ¬ TRUE; RETURN [data.peekChar # Ascii.ControlD]; }; CRFilterReset: PROC [self: STREAM] ~ { data: CRFilterData ~ NARROW[self.streamData]; data.peeked ¬ FALSE; IO.Reset[data.source]; }; CRFilterClose: PROC [self: STREAM, abort: BOOL] ~ { data: CRFilterData ~ NARROW[self.streamData]; data.peeked ¬ FALSE; IO.Close[data.source, abort]; }; CRFilterStream: PROC [source: IO.STREAM] RETURNS [IO.STREAM] ~ { RETURN [IO.CreateStream[ streamProcs: IO.CreateStreamProcs[variety: input, class: $CRFilter, getChar: CRFilterGetChar, endOf: CRFilterEndOf, charsAvail: CRFilterCharsAvail, backup: CRFilterBackup, reset: CRFilterReset, close: CRFilterClose], streamData: NEW[CRFilterDataRep ¬ [source: source]] ]]; }; FlushOutData: TYPE ~ REF FlushOutDataRep; FlushOutDataRep: TYPE ~ RECORD [ dest: IO.STREAM, specialSeen: BOOL ¬ FALSE ]; PromptChar: PROC [ch: CHAR] RETURNS [BOOL] = { RETURN [ch IN (' ..'A) AND ch NOT IN ['0..'9] AND ch # ', AND ch # ';] }; FlushOutUnsafePutBlock: PROC [self: IO.STREAM, block: IO.UnsafeBlock] ~ TRUSTED { data: FlushOutData ~ NARROW[self.streamData]; base: POINTER TO Basics.RawBytes ~ block.base; IO.UnsafePutBlock[data.dest, block]; IF block.count # 0 THEN { ss: BOOL = IF block.count = 1 THEN data.specialSeen ELSE PromptChar[VAL[base[block.startIndex+block.count-2]]]; ch: CHAR = VAL[base[block.startIndex+block.count-1]]; data.specialSeen ¬ PromptChar[ch]; IF ss AND ch = ' THEN {NetworkStream.SendSoon[data.dest, 250]; RETURN}; }; FOR i: CARD DECREASING IN [block.startIndex..block.startIndex+block.count) DO SELECT base[i] FROM ORD[Ascii.LF], ORD[Ascii.CR], ORD['?], ORD[':] => { NetworkStream.SendSoon[data.dest, 250]; EXIT; }; ENDCASE => NULL; ENDLOOP; }; FlushOutFlush: PROC [self: IO.STREAM] ~ { data: FlushOutData ~ NARROW[self.streamData]; IO.Flush[data.dest]; }; FlushOutClose: PROC [self: IO.STREAM, abort: BOOL] ~ { data: FlushOutData ~ NARROW[self.streamData]; IO.Close[data.dest, abort]; }; FlushOutEraseChar: PROC [self: IO.STREAM, char: CHAR] ~ { data: FlushOutData ~ NARROW[self.streamData]; IO.EraseChar[data.dest, char]; data.specialSeen ¬ TRUE; -- Conservative hint. }; FlushOutStream: PROC [dest: IO.STREAM] RETURNS [IO.STREAM] ~ { RETURN [IO.CreateStream[ streamProcs: IO.CreateStreamProcs[variety: output, class: $FlushOut, unsafePutBlock: FlushOutUnsafePutBlock, flush: FlushOutFlush, close: FlushOutClose, eraseChar: FlushOutEraseChar], streamData: NEW[FlushOutDataRep ¬ [dest: dest]] ]]; }; passkey: Rope.ROPE ¬ NIL; Authentic: PROC [in, out: STREAM] RETURNS [BOOL] ~ { name, password: Rope.ROPE ¬ NIL; name ¬ WITH CommanderOps.GetProp[NIL, $USER] SELECT FROM rope: ROPE => rope ENDCASE => NIL; IO.PutRope[out, "User name is "]; IO.PutRope[out, name]; IO.PutRope[out, "\n"]; IF password = NIL THEN password ¬ passkey; IF password = NIL THEN RETURN [TRUE]; IO.PutRope[out, "Password:"]; IO.Flush[out]; RETURN [Rope.Equal[password, IO.GetLineRope[in]]]; }; Terminator: TYPE = REF TerminatorRep; TerminatorRep: TYPE = RECORD [ doneFlag: BOOL ¬ FALSE, doneMsg: ROPE ¬ NIL, done: CONDITION ]; Done: ENTRY PROC [terminator: Terminator, msg: ROPE] = { IF terminator # NIL THEN { terminator.doneFlag ¬ TRUE; terminator.doneMsg ¬ msg; BROADCAST terminator.done; }; }; Wait: ENTRY PROC [terminator: Terminator] RETURNS [ROPE] = { ENABLE UNWIND => NULL; UNTIL terminator.doneFlag DO WAIT terminator.done ENDLOOP; RETURN [terminator.doneMsg] }; CopyStream: PROC [from, to: IO.STREAM, terminator: Terminator] = { ENABLE { IO.Error => { Done[terminator, GetIOErrorDetails[stream].msg]; GOTO end }; IO.EndOfStream => { Done[terminator, eofRope]; GOTO end }; }; buf: REF TEXT ~ NEW[TEXT[256]]; UNTIL terminator.doneFlag DO { ENABLE IO.Rubout => { IO.PutChar[to, '\n]; IO.Reset[from]; CONTINUE }; n: INT ~ IO.CharsAvail[from]; IF n > 0 THEN { try: NAT ~ MIN[n, buf.maxLength]; bytesRead: NAT ~ from.GetBlock[block: buf, startIndex: 0, count: try]; IF bytesRead > 0 THEN to.PutBlock[buf]; IF bytesRead = try THEN LOOP; }; IO.Flush[to]; IO.PutChar[to, IO.GetChar[from]]; } ENDLOOP; IO.Flush[to]; EXITS end => {} }; eofRope: ROPE = "EOF"; abortRope: ROPE = "ABORTED"; NetCommanderCommand: Commander.CommandProc = { ENABLE { NetworkName.Error => CommanderOps.Failed[msg]; NetworkStream.Error => CommanderOps.Failed[msg]; IO.Error => { CommanderOps.Failed[GetIOErrorDetails[stream].msg] }; }; remote, lock: ROPE ; transportClass: ATOM; terminator: Terminator = NEW[TerminatorRep ¬ []]; in, out: IO.STREAM; CloseUp: PROC = { IF in # NIL THEN IO.Close[in ! IO.Error => CONTINUE]; in ¬ NIL; IF out # NIL THEN IO.Close[out ! IO.Error => CONTINUE]; out ¬ NIL; }; [remote, lock, transportClass] ¬ GetNetComArgs[cmd]; TRUSTED { Process.EnableAborts[@terminator.done] }; remote ¬ NetworkName.AddressFromName[family: $ARPA, name: remote].addr; [in, out] ¬ CreateStreams[protocolFamily: $ARPA, remote: remote, transportClass: transportClass, timeout: 15000]; IF lock = NIL THEN lock ¬ passkey; IF lock # NIL THEN { IO.PutRope[out, lock]; IO.PutRope[out, "\n"]; }; WITH CommanderOps.GetProp[cmd, $WorkingDirectory] SELECT FROM rope: ROPE => { IO.PutF1[out, "CD %g\n", [rope[rope]] ! IO.Error => { msg ¬ GetIOErrorDetails[stream].msg; CloseUp[]; CommanderOps.Failed[msg]; } ]; }; ENDCASE; BEGIN ENABLE UNWIND => CloseUp[]; p1: PROCESS = FORK CopyStream[from: in, to: cmd.out, terminator: terminator]; p2: PROCESS = FORK CopyStream[from: cmd.in, to: out, terminator: terminator]; msg ¬ Wait[terminator ! ABORTED => { msg ¬ abortRope; CONTINUE }]; Process.Abort[p1]; Process.Abort[p2]; CloseUp[]; TRUSTED { [] ¬ JOIN p1; [] ¬ JOIN p2 }; result ¬ IF msg = eofRope THEN NIL ELSE $Failure; END; IF msg = abortRope THEN ERROR ABORTED; }; Stop: PROC [REF ANY] ~ { [] _ NetUnListenCommand[NIL]; RETURN}; Termination.CallBeforeQuitWorld[Stop]; Commander.Register["NetCommanderOn", NetListenCommand, "Make a Commander listener.\nargs: port transportClass [-lock password]"]; Commander.Register["NetCommanderLock", NetLockCommand, "Set a password for incoming connections.\nargs: password"]; Commander.Register["NetCommanderOff", NetUnListenCommand, "Destroy current Commander listeners"]; Commander.Register["NetCommanderListeners", NetListenersCommand, "List current Commander listeners"]; Commander.Register["NetCommander", NetCommanderCommand, "Talk to a listener.\nargs: port transportClass [-lock password]"]; }. -- CreateStreams: PROC [protocolFamily: ATOM, remote: ROPE, transportClass: ATOM ¬ NIL, timeout: Milliseconds ¬ waitForever, transportParameters: REF ¬ NIL] RETURNS [in: STREAM, out: STREAM]; -- Initiate connection to (remote) host. -- If transportClass = NIL, use any registered transport in given protocol family. -- GetStreamInfo: PROC [stream: STREAM] RETURNS [protocolFamily: ATOM, local: ROPE, remote: ROPE, transportClass: ATOM]; -- GetTimeout: PROC [stream: STREAM] RETURNS [timeout: Milliseconds, signalTimeout: BOOL]; -- SetTimeout: PROC [stream: STREAM, timeout: Milliseconds ¬ waitForever, signalTimeout: BOOL ¬ FALSE]; -- SendSoon: PROC [out: STREAM, when: Milliseconds ¬ 0]; -- Listener Operations -- A transport provider is free not to implement listeners. -- ListenerWorkerProc: TYPE ~ PROC [listener: Listener, in: STREAM, out: STREAM]; -- CreateListener: PROC [protocolFamily: ATOM, local: ROPE ¬ NIL, -- transportClass: ATOM ¬ NIL, -- transportParameters: REF ¬ NIL, -- listenerWorkerProc: ListenerWorkerProc, -- listenerWorkerClientData: REF ¬ NIL] -- RETURNS [listener: Listener]; -- Create a listener at the specified local address (which may include a port). -- If transportClass = NIL, use any registered transport in given protocol family. -- The ListenerWorkerProc is called with a newly-created stream pair for each connection request. The stream timeouts are initially infinite, but can be changed with SetTimeout. -- GetListenerInfo: PROC [listener: Listener] RETURNS [protocolFamily: ATOM, local: ROPE, transportClass: ATOM, proc: ListenerWorkerProc, clientData: REF]; -- DestroyListener: PROC [listener: Listener]; --EnumerateCallbackProc: TYPE ~ PROC [protocolFamily: ATOM, transportClass: ATOM] RETURNS [continue: BOOL ¬ TRUE]; -- Enumerate: PROC [families: ATOM, classes: ATOM, proc: EnumerateCallbackProc]; RNetCommanderImpl.mesa Copyright Σ 1990, 1991 by Xerox Corporation. All rights reserved. Michael Plass, April 22, 1993 9:53 am PDT Willie-s, June 29, 1992 1:40 pm PDT EnumerateCallbackProc: TYPE ~ PROC [protocolFamily: ATOM, transportClass: ATOM] RETURNS [continue: BOOL _ TRUE]; This procedure attempts to do SendSoon at newlines and at places that may require user action (prompts), while not generating too many extraneous packets. [name, password] _ UserCredentials.Get[]; Commander.Register["NetTransportLS", NetLSCommand, "list registered (protocolFamily, transportClass) pairs"]; Κv–(cedarcode) style•NewlineDelimiter ˜code™Kšœ Οeœ7™BK™)K™#—K˜KšΟk œ`žœh˜ΣK˜KšΟnœžœž˜KšžœQžœg˜ΑKšœžœ˜˜šŸ œ˜'Kšœžœ"˜+šŸœ˜Kšœžœžœžœžœžœ žœžœ™pK˜HK˜—Kšœ žœžœ˜K˜—K˜Kšœ žœžœžœ˜%šœ žœžœ˜Kšœ˜Kšœ žœžœ˜+Kšœžœžœ˜Kšœ-žœ˜2—K˜šŸœ˜-šžœ žœž˜Kšœ3˜3K˜#K˜Kšžœ˜—Kšœ˜K˜—šŸœ˜+šžœ˜Kšœ.˜.Kšœ0˜0Kšžœ\˜^Kšœ˜—Kšœžœ˜Kšœžœ˜K˜!K˜Kšœ%žœ˜)K˜6˜(Kšœ˜K˜Kšœ8žœ-˜hKšœ!˜!Kšœ˜—K˜Kšžœžœžœ˜"KšœO˜OKšœG˜GKšœY˜YKšœ[˜[Kšœ7˜7K˜EK˜iKšœ žœ˜ Kšžœh˜jKšžœ˜K˜—šŸœ˜)K˜)K˜K˜—šŸœ˜.š žœžœžœ$žœžœž˜HKšœ˜Kšžœ#žœ[˜„Kšžœ˜—Kšœ˜K˜—š Ÿ œžœžœžœžœ˜ešž˜Kšœžœ"˜+Kšžœžœžœžœ˜šžœžœž˜K˜EKšœ/žœ,˜]Kšœ žœ˜!Kšœžœ(˜šžœžœ˜Kšœ žœ¨˜·Kšœ žœ ˜/Kšœ˜—Kšœ˜K˜—Kšœžœžœ˜š Ÿ œžœ žœžœžœ˜4Kšœžœžœ˜ Jšœ)™)Kšœžœžœ žœžœžœ žœžœ˜[Kšžœ˜!Kšžœ˜Kšžœ˜Kšžœ žœžœ˜*Kš žœ žœžœžœžœ˜%Kšžœ˜Kšžœ ˜Kšžœžœ˜2Kšœ˜K˜—Kšœ žœžœ˜%šœžœžœ˜Kšœ žœžœ˜Kšœ žœžœ˜Kšœž ˜Kšœ˜K˜—šŸœžœžœžœ˜8šžœžœžœ˜Kšœžœ˜K˜Kšž œ˜Kšœ˜—Kšœ˜K˜—š Ÿœžœžœžœžœ˜