<<>> <> <> <> <> <> <> <> <<>> DIRECTORY Ascii, Commander, IO, MessageWindow, Plumber, Process, PseudoTerminal, RefText, Rope, SimpleFeedback, UnixErrno, UnixSysCallExtensions, UnixSysCalls, UnixTypes, UXStrings; PlumberImpl: CEDAR MONITOR LOCKS c USING c: Conduit IMPORTS Commander, IO, Process, PseudoTerminal, RefText, Rope, UnixErrno, UnixSysCallExtensions, UnixSysCalls, UXStrings, SimpleFeedback -- MessageWindow EXPORTS Plumber ~ BEGIN OPEN Plumber; ROPE: TYPE ~ Rope.ROPE; Error: PUBLIC ERROR [code: ATOM, msg: ROPE] ~ CODE; CHARPtrFromRefText: PROCEDURE [refText: REF TEXT] RETURNS [UnixTypes.CHARPtr] ~ TRUSTED { charPtr: UnixTypes.CHARPtr ~ LOOPHOLE[LOOPHOLE[refText, LONG POINTER]+SIZE[TEXT[0]]]; RETURN [charPtr]; }; CopyFDToStream: PROC [in: UnixTypes.FileDescriptor, out:IO.STREAM, wire:Wire, noisy:BOOL] ~{ buffer: REF TEXT ~ RefText.ObtainScratch[256]; bufPtr: UnixTypes.CHARPtr ~ CHARPtrFromRefText[refText: buffer]; Inner: PROC [in: UnixTypes.FileDescriptor, out: IO.STREAM, wire: Wire] ~ { retryOnReadError: BOOLEAN ¬ TRUE; WHILE wire.active DO { res: INT; TRUSTED { res ¬ UnixSysCalls.Read[d: in, buf: bufPtr, nBytes: buffer.maxLength]; }; IF NOT wire.active THEN { out.PutBlock[" Wire not active! (retry - please note non-zero error code in System Script\n "]; EXIT; }; IF res <= 0 THEN { err: UnixErrno.Errno = UnixErrno.GetErrno[]; wire.result ¬ LIST[$uioerror, NEW[INT ¬ ORD[err]]]; <> IF retryOnReadError AND (err = UnixErrno.Errno.EIO) THEN { retryOnReadError ¬ FALSE; IF noisy THEN { out.PutBlock["Plumb waiting for connection...\n"]; out.Flush[]; }; Process.Pause[Process.SecondsToTicks[1]]; LOOP; }; RETURN; }; retryOnReadError ¬ FALSE; buffer.length ¬ res; out.PutBlock[buffer]; Process.CheckForAbort[]; } ENDLOOP; wire.result ¬ $terminated; }; Inner[in: in, out: out, wire: wire ! IO.Error => { wire.result ¬ LIST[$ioerror, NEW[INT ¬ ORD[ec]]]; <> <> CONTINUE; }; ABORTED => { wire.result ¬ $aborted; <> <> CONTINUE; }; ]; <> RefText.ReleaseScratch[buffer]; IF noisy THEN out.PutBlock["Plumb disconnected. " ! IO.Error => CONTINUE]; IO.Flush[out ! IO.Error => CONTINUE]; }; CopyStreamToFD: PROC [in: IO.STREAM, out: UnixTypes.FileDescriptor, wire: Wire] = { buffer: REF TEXT ~ NEW[TEXT[1]]; bufPtr: UnixTypes.CHARPtr ~ CHARPtrFromRefText[refText: buffer]; Inner: PROC [in: IO.STREAM, out: UnixTypes.FileDescriptor, wire: Wire] ~ { WHILE wire.active DO { res: INT; nBytes: INT ~ IO.GetBlock[in, buffer]; IF NOT wire.active THEN EXIT; IF nBytes = 0 THEN ERROR IO.EndOfStream[in]; res ¬ UnixSysCalls.Write[d: out, buf: bufPtr, nBytes: nBytes]; IF res <= 0 THEN { wire.result ¬ LIST[$uioerror, NEW[INT ¬ ORD[UnixErrno.GetErrno[]]]]; EXIT; }; Process.CheckForAbort[]; } ENDLOOP; wire.result ¬ $terminated; }; Inner[in: in, out: out, wire: wire ! IO.Error => { wire.result ¬ LIST[$ioerror, NEW[INT ¬ ORD[ec]]]; <> CONTINUE; }; IO.EndOfStream => { wire.result ¬ $eof; <> CONTINUE; }; ABORTED => { wire.result ¬ $aborted; <> CONTINUE; }; ]; <> RefText.ReleaseScratch[buffer]; }; <<-- Atomically set closing field of the conduit and return previous value>> SetConduitClosing: ENTRY PROC [c: Conduit] RETURNS [BOOLEAN] ~ { IF c.closing THEN RETURN[TRUE]; c.closing ¬ TRUE; RETURN[FALSE]; }; MakeConduit: PUBLIC PROC [stdin, stdout, stderr: REF, debug:BOOL¬FALSE, noisy:BOOL¬ TRUE] RETURNS [Conduit] ~ { conduit: Conduit ¬ NEW[ConduitRep ¬ [ stdin: NEW[WireRep ¬ [active: TRUE]], stdout: NEW[WireRep ¬ [active: TRUE]], stderr: NIL, message: IF debug THEN stderr ELSE NIL ]]; WITH stdin SELECT FROM rope: ROPE => conduit.stdin.connectedTo ¬ rope; refText: REF TEXT => conduit.stdin.connectedTo ¬ Rope.FromRefText[s: refText]; stream: IO.STREAM => { IF conduit.pty = NIL THEN { conduit.pty ¬ PseudoTerminal.Allocate[]; }; conduit.stdin.connectedTo ¬ conduit.pty.slaveName; TRUSTED { conduit.stdin.process ¬ FORK CopyStreamToFD[ in: stream, out: conduit.pty.controllerFD, wire: conduit.stdin]; Process.Detach[conduit.stdin.process]; }; }; ENDCASE => ERROR; WITH stdout SELECT FROM rope: ROPE => conduit.stdout.connectedTo ¬ rope; refText: REF TEXT => conduit.stdout.connectedTo ¬ Rope.FromRefText[s: refText]; stream: IO.STREAM => { IF conduit.pty = NIL THEN { conduit.pty ¬ PseudoTerminal.Allocate[]; }; conduit.stdout.connectedTo ¬ conduit.pty.slaveName; TRUSTED { conduit.stdout.process ¬ FORK CopyFDToStream[ in: conduit.pty.controllerFD, out: stream, wire: conduit.stdout, noisy:noisy]; Process.Detach[conduit.stdout.process]; }; }; ENDCASE => ERROR; IF stderr = stdout THEN { conduit.stderr ¬ conduit.stdout } ELSE { conduit.stderr ¬ NEW[WireRep ¬ [active: TRUE]]; WITH stderr SELECT FROM rope: ROPE => conduit.stderr.connectedTo ¬ rope; refText: REF TEXT => conduit.stderr.connectedTo ¬ Rope.FromRefText[s: refText]; stream: IO.STREAM => { IF conduit.pty = NIL THEN { conduit.pty ¬ PseudoTerminal.Allocate[]; }; conduit.stderr.connectedTo ¬ conduit.pty.slaveName; IF NOT Rope.Equal[conduit.stderr.connectedTo, conduit.stdout.connectedTo] THEN { <> <> TRUSTED { conduit.stderr.process ¬ FORK CopyFDToStream[ in: conduit.pty.controllerFD, out: stream, wire: conduit.stderr, noisy:noisy]; Process.Detach[conduit.stderr.process]; }; } }; ENDCASE => ERROR; }; RETURN [conduit]; }; DebugPrint: PROC [conduit: Conduit, rope: ROPE] ~ { WITH conduit.message SELECT FROM stream: IO.STREAM => { IO.PutF[stream, "%l%g%l", [rope["sb"]], [rope[rope]], [rope["SB"]]]; }; ENDCASE => NULL; }; Terminate: PUBLIC PROC [conduit: Conduit, reason: ATOM ¬ $Normal] ~ { BreakWire: PROCEDURE [wire: Wire] ~ { wire.active ¬ FALSE; IF wire.process # NIL THEN { Process.Abort[wire.process ! Process.InvalidProcess => { <> CONTINUE; }; ]; wire.process ¬ NIL; }; }; <> <<-- Execute this code at most once.>> <> IF SetConduitClosing[conduit] THEN RETURN; <> BreakWire[wire: conduit.stdin]; BreakWire[wire: conduit.stdout]; BreakWire[wire: conduit.stderr]; <> IF conduit.pty # NIL AND reason = $Poisoned THEN PseudoTerminal.Close[conduit.pty]; IF conduit.pty # NIL THEN PseudoTerminal.Close[conduit.pty]; }; Spawn: PUBLIC PROC [ command: ROPE, wd: ROPE, stdin: REF, stdout: REF, stderr: REF, exec: BOOL, tty: BOOL, debug: BOOL ¬ FALSE, noisy: BOOL ¬ FALSE, reportSourceFD: PROC[fd: UnixTypes.FileDescriptor] ¬ NIL ] RETURNS [Plumber.SpawnResult] ~ { conduit: Conduit ~ MakeConduit[ stdin: stdin, stdout: stdout, stderr: stderr, debug: debug, noisy: noisy ]; RETURN SpawnWithConduit[conduit: conduit, command: command, wd: wd, exec: exec, tty: tty, reportSourceFD: reportSourceFD]; }; SpawnWithConduit: PUBLIC PROC [ conduit: Conduit, command: ROPE, wd: ROPE, exec: BOOLEAN, tty: BOOLEAN, reportSourceFD: PROC[fd: UnixTypes.FileDescriptor] ¬ NIL ] RETURNS [Plumber.SpawnResult] ~ { res: INT; reason: ATOM; completeCommand: ROPE ¬ command; Cleanup: PROC [conduit: Conduit, msg: ROPE, reason: ATOM] ~ { in: ROPE ~ conduit.stdin.connectedTo; out: ROPE ~ conduit.stdout.connectedTo; err: ROPE ~ conduit.stderr.connectedTo; IF plumberDebugMsgs THEN SimpleFeedback.PutFL[$Plumber, oneLiner, $Debug, "Plumber: [[%g]] Terminating %g (in=%g, out=%g, err=%g)\n", LIST[[rope[msg]], [rope[completeCommand]], [rope[in]], [rope[out]], [rope[err]]] ]; Terminate[conduit: conduit, reason: reason]; }; FOR i: INT DECREASING IN [0..Rope.Size[completeCommand]) WHILE Rope.Fetch[completeCommand, i] = '\n DO <> completeCommand ¬ Rope.Substr[completeCommand, 0, i]; ENDLOOP; IF exec THEN { completeCommand ¬ Rope.Concat[base: "exec ", rest: completeCommand]; }; IF tty AND (conduit.pty # NIL) THEN { completeCommand ¬ Rope.Cat[ r1: completeCommand, r2: " -t ", r3: conduit.pty.slaveName, r4: " -p ", r5: conduit.pty.controllerName]; }; <> <<"Spawning %g (in=%g, out=%g, err=%g, wd=%g)\n", >> <<[rope[completeCommand]], >> <<[rope[conduit.stdin.connectedTo]], >> <<[rope[conduit.stdout.connectedTo]], >> <<[rope[conduit.stderr.connectedTo]],>> <<[rope[wd]]>> <<]; >> DebugPrint[ conduit: conduit, rope: IO.PutFLR[" Spawning %g (in=%g, out=%g, err=%g, wd=%g)\n", LIST[[rope[completeCommand]], [rope[conduit.stdin.connectedTo]], [rope[conduit.stdout.connectedTo]], [rope[conduit.stderr.connectedTo]], [rope[wd]]] ] ]; IF reportSourceFD # NIL THEN reportSourceFD[ conduit.pty.controllerFD ]; res ¬ UnixSysCallExtensions.CDSpawn[ cmd: UXStrings.Create[completeCommand], wd: UXStrings.Create[wd], stdin: UXStrings.Create[conduit.stdin.connectedTo], stdout: UXStrings.Create[conduit.stdout.connectedTo], stderr: UXStrings.Create[conduit.stderr.connectedTo] ! UNWIND => Cleanup[conduit: conduit, msg: "Spawn!UNWIND", reason: $Poisoned]]; <> IF res # 0 THEN { <> SimpleFeedback.PutF[$Plumber, oneLiner, $Debug, "Plumber: non-zero exit status %xH\n", [integer[res]] ]; reason ¬ $Poisoned; } ELSE { reason ¬ $Normal; }; Cleanup[conduit: conduit, msg: "Cleanup", reason: reason ! UNWIND => Cleanup[conduit: conduit, msg: "Cleanup!UNWIND", reason: $Normal]]; RETURN [[ res: res, errno: UnixErrno.GetErrno[].ORD, details: LIST[ NEW[INT ¬ res], NEW[INT ¬ UnixErrno.GetErrno[].ORD], conduit.stdin.result, conduit.stdout.result, conduit.stderr.result ] ]] }; plumberDebugMsgs: BOOL ¬ FALSE; DebugMsgsOn: Commander.CommandProc ~ { plumberDebugMsgs ¬ TRUE }; DebugMsgsOff: Commander.CommandProc ~ { plumberDebugMsgs ¬ FALSE }; Commander.Register["PlumberDebugMsgsOn", DebugMsgsOn]; Commander.Register["PlumberDebugMsgsOff", DebugMsgsOff]; END.