UnixSpawnTCPImpl.mesa
Copyright Ó 1991 by Xerox Corporation. All rights reserved.
Jules Bloomenthal November 28, 1992 9:09 am PST
Thanks to Alan Demers, Jim Foote, and Michael Plass
Chauser, September 22, 1992 3:29 pm PDT
Michael Plass, October 15, 1992 11:01 am PDT
DIRECTORY CedarProcess, Convert, IO, Rope, UnixSpawnTCP, UnixSysCalls, UnixTypes, UXStrings;
UnixSpawnTCPImpl: CEDAR MONITOR
IMPORTS CedarProcess, Convert, IO, Rope, UnixSysCalls, UXStrings 
EXPORTS UnixSpawnTCP
~ BEGIN
ROPE:   TYPE ~ Rope.ROPE;
SpawnData: TYPE ~ UnixSpawnTCP.SpawnData;
FD:   TYPE ~ UnixTypes.FileDescriptor;
CHARPtr:  TYPE ~ UnixTypes.CHARPtr;
Unix Spawn Procedures using TCP
pidArrived: CONDITION;
Spawn: PUBLIC PROC [
command: ROPE,
out, err: IO.STREAM ¬ NIL,
finish: UnixSpawnTCP.FinishProc ¬ NIL,
readOut: CedarProcess.ForkableProc ¬ NIL,
clientData: REF ANY ¬ NIL]
RETURNS [s: REF SpawnData]
~ {
FDPair: TYPE ~ RECORD [fd1, fd2: FD];
POpen: PROC [cmd: ROPE] RETURNS [fdp: FDPair] ~ {
Fork Unix process "cmd" and connect fd to stdin/stdout and fdErr to stderr.
XR←PPPOpen found in URExec.o; see xr/UIO.h.
pppopen: PROC [p: POINTER TO FDPair, separate: BOOL, cmd: CHARPtr]
RETURNS [INT] ~ TRUSTED MACHINE CODE {"XR←PPPOpen"};
TRUSTED {IF pppopen[@fdp, TRUE, UXStrings.Create[cmd]] # 0 THEN fdp ¬ [error,error]};
};
WaitForPID: ENTRY PROC [s: REF SpawnData] ~ {
WHILE s.pid= -1 DO WAIT pidArrived ENDLOOP;
};
PPPOpen runs a shell that substitutes its process id for $$ and runs echo in a subprocess;
then the exec runs command using the same process as the shell's.
fdp: FDPair ¬ POpen[Rope.Concat["echo $$; exec ", command]];
s ¬ NEW[SpawnData];
IF (s.fd1 ¬ fdp.fd1) = error OR (s.fd2 ¬ fdp.fd2) = error
THEN RETURN[NIL]
ELSE {
s.pid ¬ -1;
s.finish ¬ finish;
s.clientData ¬ clientData;
s.out ¬ out;
s.err ¬ err;
s.readOut ¬ IF readOut # NIL THEN readOut ELSE ReadOut;
s.watch1 ¬ CedarProcess.Fork[WatchOut, s, [, TRUE]];
s.watch2 ¬ CedarProcess.Fork[WatchErr, s, [, TRUE]];
WaitForPID[s];
};
};
WatchOut: CedarProcess.ForkableProc ~ TRUSTED {
s: REF SpawnData ¬ NARROW[data];
fd: UnixTypes.FileDescriptor ¬ s.fd1;
buf: PACKED ARRAY[0..3] OF CHAR;
nBytes: INT;
rope: ROPE ¬ "0";
NotifyPID: ENTRY PROC ~ CHECKED {
BROADCAST pidArrived;
};
WHILE (nBytes ¬ UnixSysCalls.Read[fd, LOOPHOLE [@buf], 1]) > 0 DO
char: CHAR ¬ buf[0];
IF char IN ['0..'9] THEN rope ¬ rope.Concat[Rope.FromChar[char]]
ELSE EXIT;
ENDLOOP;
s.pid ¬ Convert.IntFromRope[rope ! Convert.Error => CONTINUE];
IF s.pid = -1 THEN s.pid ¬ 0;
s.fdOut ¬ fd;
s.readOutProcess ¬ CedarProcess.Fork[WrapReadOut, s, [, TRUE]];
NotifyPID[];
RETURN;
};
outReaderDone: CONDITION;
WatchErr: CedarProcess.ForkableProc ~ TRUSTED {
s: REF SpawnData ¬ NARROW[data];
fd: UnixTypes.FileDescriptor ¬ s.fd2;
nBytes: INT;
stat: {bad, unknown} ¬ unknown;
buf: UnixTypes.CHARPtr ¬ UXStrings.ObtainScratch[1024];
WaitForOutReaderDone: ENTRY PROC ~ CHECKED {
WHILE s.readOut#NIL DO WAIT outReaderDone ENDLOOP;
};
WHILE (nBytes ¬ UnixSysCalls.Read[fd, buf, 1023]) > 0 DO
rope: ROPE ¬ UXStrings.ToRope[buf, nBytes];
IF Rope.Find[rope, "not found",, FALSE] # -1 THEN stat ¬ bad;
IF s.err # NIL THEN IO.PutRope[s.err, rope];
IF CedarProcess.GetStatus[] = aborted THEN EXIT;
ENDLOOP;
UXStrings.ReleaseScratch[buf];
IF s.finish # NIL THEN {
WaitForOutReaderDone[];
s.finish[IF stat = bad THEN $NotFound ELSE $Completed, s.clientData];
};
};
Kill: PUBLIC PROC [s: REF SpawnData] ~ {
Close[s];
IF s # NIL AND s.pid # -1 THEN [] ¬ UnixSysCalls.Kill[s.pid, SIGTERM];
SIGKILL more reliable but less polite than SIGTERM
};
Write: PUBLIC PROC [s: REF SpawnData, rope: ROPE] ~ {
string: UXStrings.CString ¬ UXStrings.CreateScratch[rope];
[] ¬ UnixSysCalls.Write[s.fdOut, string, Rope.Length[rope]];
UXStrings.ReleaseScratch[string];
};
Close: PUBLIC PROC [s: REF SpawnData] ~ {
IF s # NIL THEN {
CedarProcess.Abort[s.watch1];
CedarProcess.Abort[s.watch2];
CedarProcess.Abort[s.readOutProcess];
[] ¬ UnixSysCalls.Shutdown[s.fd1, noSend];
[] ¬ UnixSysCalls.Shutdown[s.fd2, noSend];
};
};
WrapReadOut: CedarProcess.ForkableProc ~ {
s: REF SpawnData ¬ NARROW[data];
NotifyOutReaderDone: ENTRY PROC [] ~ {
s.readOut ¬ NIL;
BROADCAST outReaderDone;
};
{
ENABLE UNWIND => NotifyOutReaderDone[];
results ¬ s.readOut[data];
NotifyOutReaderDone[];
};
};
ReadOut: CedarProcess.ForkableProc ~ TRUSTED {
nBytes: INT;
s: REF SpawnData ¬ NARROW[data];
buf: CHARPtr ¬ UXStrings.ObtainScratch[1024];
WHILE (nBytes ¬ UnixSysCalls.Read[s.fdOut, buf, 1023]) > 0 DO
IF s.out # NIL THEN IO.PutRope[s.out, UXStrings.ToRope[buf, nBytes]];
CedarProcess.CheckAbort[];
ENDLOOP;
UXStrings.ReleaseScratch[buf];
CedarProcess.CheckAbort[];
Close[s];
};
END.
..
Test Application
Cat: Commander.CommandProc ~ {
in: IO.STREAM;
v: ViewerClasses.Viewer;
s: REF UnixSpawnTCP.SpawnData;
args: CommanderOps.ArgumentVector ← CommanderOps.Parse[cmd];
IF args.argc # 2 THEN RETURN[$Failure, "need filename"];
in ← FS.StreamOpen[args[1] ! FS.Error => msg ← IO.PutFR1["no %g\n", IO.rope[args[1]]]];
IF msg # NIL THEN RETURN[$Failure, msg];
IF (s ← UnixSpawnTCP.Spawn["cat", cmd.out,, Fin]) = NIL THEN RETURN[$Failure, "failed"];
v ← ViewerOps.CreateViewer[$Container, [name: "Test", openHeight: 52, scrollable: FALSE]];
[] ← Buttons.Create[[name: IO.PutFR1["STOP (pid=%g)", IO.int[s.pid]], wx: 10, wy: 10, ww: 575, wh: 18, parent: v], Kill, s];
ViewerOps.OpenIcon[v];
DO
UnixSpawnTCP.Write[s, Rope.Concat[IO.GetLineRope[in ! IO.EndOfStream => EXIT], "\n"]];
ENDLOOP;
};
Fin: UnixSpawnTCP.FinishProc ~ {MessageWindow.Append["DONE!", TRUE]};
Kill: ViewerClasses.ClickProc ~ {UnixSpawnTCP.Kill[NARROW[clientData]]};
PID: Commander.CommandProc ~ {
s: REF UnixSpawnTCP.SpawnData ← UnixSpawnTCP.Spawn["ps -x", cmd.out];
IF s = NIL THEN RETURN[$Failure, "can't spawn"];
IO.PutF1[cmd.out, "s.pid = %g\n", IO.int[s.pid]];
};
Commander.Register["SpawnPID", PID, "print pid and spawn ps"];
Commander.Register["SpawnCat", Cat, "SpawnCat <file name>\ncat the file with optional kill"];
POpen: PROC [cmd: ROPE] RETURNS [FD] ~ {
Fork a Unix process running cmd, connecting fd to stdin/(stdout, stderr); see xr/UIO.h.
popen: PROC [cmd: CHARPtr] RETURNS [FD] ~ TRUSTED MACHINE CODE {"XR←PPOpen"};
RETURN[popen[UXStrings.Create[cmd]]];
};
Miscellaneous C Fragments (for acquiring pid)
/* a.c - named pipe writer */

#include <stdio.h>
#include <fcntl.h>

main()
{
 int fd = open("/tmp/myfifo", O←WRONLY);
 char *output = "This is some output to a named pipe\n";
 write(fd, output, strlen(output));
}

/* b.c - named pipe reader */

/* advantage: can transmit EOF to the input of the invoked process, but how get back anything printed to stdout or stderr by the invoked process? also, the named pipes are not transient, and should be killed (unlink) after use */

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

main(ac, av)
int ac;
char **av;
{
 char *name = ac == 2 ? av[1] : "/tmp/myfifo";
 char buf[1024];
 int fd;
 mkfifo(name, 0666);
 fd = open(name, O←RDONLY);
 (void) read(fd, buf, 1024);
 printf("result of read = %s\n", buf);
}

/* c.c */
#include <xr/UIO.h>

char output[1024];

/* the advantage here is that the transmission medium is transient, also the Unix process stdout and stderr is fed back to the invoking XR environment. disadvantsges: no EOF can be sent to the input to the invoked process and no method for killing the invoked process */

/* see /pseudo/xrhome/INSTALLED/INCLUDE/xr/*.h */

int
TestPPOpen(cmd)
char *cmd;
{
XR𡤏ildes fd;
int readAns;

fd = XR←PPOpen(cmd);
if( fd < 0 ) {
XR𡤌onsoleMsg("PPOpen failed %d err %d\n", fd, XR←GetErrno() );
return (-1);
}
(void)bzero(output, 1024);
readAns = XR←Read(fd, output, 1023);
XR𡤌lose(fd);
return readAns;
}
/* pid.c */
#include <sys/types.h>
#include <stdio.h>
main() { fprintf(stderr, "(pid %d)\n", getppid()); exit(0); }
/* doit.c */
#include <stdio.h>

main(argc, argv)
int argc;
char **argv;
{
int childid;

childid = fork();
if( childid == 0 ) /* this is child */ {
sleep(5);
printf("hello world %s\n", argv[1]);
exit(0);
} else /* parent */ {
printf("%6d", childid);
fflush(stdout);
exit(0);
}
}