PlumberImpl.mesa
Copyright Ó 1989, 1991, 1992 by Xerox Corporation. All rights reserved.
Michael Plass, September 25, 1989 3:54:40 pm PDT
Peter B. Kessler, January 16, 1990 10:32:25 am PST
Norman Adams, June 8, 1990 4:25 pm PDT
Welch, March 8, 1991 6:44 pm PST
Willie-s, June 12, 1992 2:39 pm PDT
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]]];
SimpleFeedback.PutF[$Plumber, oneLiner, $Debug,
"Plumb: Unix error = %g", IO.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]]];
SimpleFeedback.Append[$Plumber, oneLiner, $Debug, "CopyFDToStream IO.error"];
out.PutBlock[" CopyFDToStream IO.error\n "];
CONTINUE;
};
ABORTED => {
wire.result ¬ $aborted;
SimpleFeedback.Append[$Plumber, oneLiner, $Debug, "CopyFDToStream aborted"];
out.PutBlock[" CopyFDToStream aborted\n "];
CONTINUE;
};
];
SimpleFeedback.Append[$Plumber, oneLiner, $Debug, "CopyFDToStream ending"];
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]]];
SimpleFeedback.Append[$Plumber, oneLiner, $Debug, "CopyStreamToFD IO.error"];
CONTINUE;
};
IO.EndOfStream => {
wire.result ¬ $eof;
SimpleFeedback.Append[$Plumber, oneLiner, $Debug, "CopyStreamToFD IO.eos"];
CONTINUE;
};
ABORTED => {
wire.result ¬ $aborted;
SimpleFeedback.Append[$Plumber, oneLiner, $Debug, "CopyStreamToFD aborted"];
CONTINUE;
};
];
SimpleFeedback.Append[$Plumber, oneLiner, $Debug, "CopyStreamToFD ending"];
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 {
If stdout is already reading from the pty, stderr shouldn't.
A pity that stdout and stderr are merged into the output of a pty.
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 => {
SimpleFeedback.Append[$Plumber, oneLiner, $Debug, "Plumb:Invalid Process"];
CONTINUE;
};
];
wire.process ¬ NIL;
};
};
MessageWindow.Append["..Terminating.."];
-- Execute this code at most once.
SimpleFeedback.Append[$Plumber, oneLiner, $Debug, "Plumb:In Terminate"];
IF SetConduitClosing[conduit] THEN RETURN;
SimpleFeedback.Append[$Plumber, oneLiner, $Debug, "Plumb:Start breaking"];
BreakWire[wire: conduit.stdin];
BreakWire[wire: conduit.stdout];
BreakWire[wire: conduit.stderr];
SimpleFeedback.Append[$Plumber, oneLiner, $Debug, "Plumb:All broken"];
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
Hopefully we go through this loop just once!
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];
};
SimpleFeedback.PutF[$Plumber, oneLiner, $Debug,
"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]];
MessageWindow.Append["Plumb:CDSpawn returned"];
IF res # 0 THEN {
This is a big hammer. If the shell exits badly (for any reason), we don't use the PseudoTerminal any more. This is to hack around the Wire not active problem, but is overly concervative. If all the PseudoTerminals get marked bad and we run out, then PseudoTerminal.Allocate clears the inuse symtab and we'll try them all again
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.