[comp.windows.news] psrun: replacement for psh

wim@ecn.UUCP (Wim Rijnsburger) (03/06/90)

Last week I tried to post our source of 'psrun', a 'psh' alike
utility for loose coupling of applications to NeWS user interfaces.
Because problably something went wrong with this posting, here is
another try. This is a corrected version, so the patch for the manual
page, posted yesterday is not necessary anymore.

The psrun command downloads NeWS code into the server, catches errors
and
handles client-server communications appropriate. We use loose coupling
of applications to the NeWS server, by redirecting stdout of the
application to stdin of psrun and vice versa.

Below you will find a shar archive of the complete source of psrun,
including a manual page. After unpacking of this archive, edit the
Makefile to set the BINDIR, MANDIR and LIBDIR appropriate. Then type
'make' to build everything. Test it out by something like:

	psrun -h

to get help on available options and arguments. To start an interactive
debugging session with the server, use:

	psrun -d -P "executive"

All commands typed at stdin are now given to the server. Try to type
some
garbage to test the error catching.

To download NeWS code files from some directory, use:

	psrun -d -L /home/.../mydir file1 file2 ...

If everything looks fine, do a 'make install'. The manual page gives
more explanations on usage.

Wim.

---------- Netherlands Energy Research Foundation, ECN --------------
Wim Rijnsburger                          email: RIJNSBURGER@ECN.NL
P.O. Box 1, 1755 ZG  Petten, Holland     phone: +31 2246 4673

----------- Remove everything below and including this line
-----------
: To unbundle, sh this file
echo main.c
cat >main.c <<'@@@ Fin de main.c'
#include        "def.h"
/*
 * psrun: a general client-server coupling program
 *
 * The main program tries to connect with the server and load the
 * application into the server.
 * A child is forked to read stdin and pass it to the server
 * Then a loop is started to read tagged commands from the server.
 *
 * If the -d (debug) flag was given as an option, the psrun
 * reports the server's messages on stderr, in case
 * the interpreter failed for some reason.
 *
 * Immediate to execute PostScript commands can be passed to the
 * server via the option -P. For instance to get into executive
 * mode, where the server do not stop on errors:
 *
 *      psrun -d -P "executive"
 *
 * Server side code files, passed as arguments to psrun are downloaded
 * by psrun into the server. The server searches files in a library
 * directory as given with the -L <dir> option. The default lib dir
 * is determined by a variable in the Makefile.
 *
 * (c) 1989 Copyright Wim Rijnsburger, ECN Petten, Holland
 *
 * ---------- Netherlands Energy Research Foundation, ECN
 --------------
 * Wim Rijnsburger                      UUCP : wim@ecn.uucp
 * P.O. Box 1, 1755 ZG  Petten(NH)      ARPA : ecn!wim@nluug.nl
 * Holland    phone: +31 2246 4336             ecn!wim@uunet.uu.net
 *
 */

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

#define STRBUFLEN 1024

	char    Buf1[STRBUFLEN], Buf2[STRBUFLEN],
		*LibDir = LIBDIR,
		*PsString = "", *ErrStr;
	int     status = OK, debug = 0,
		pid, i, c, cid;
	FILE    *InPipe;

	/*
	 * Parse the options
	 */
	extern char *optarg;
	extern int optind, opterr;
	opterr = 0;
	while ((c = getopt(argc, argv, "dhL:P:")) != -1)
	     switch (c) {
		 case 'd':
		      debug = 1;
		      break;
		 case 'L':
		      LibDir = optarg;
		      break;
		 case 'P':
		      PsString = optarg;
		      break;
		 case 'h':
		      status = ARG_ERROR;
		      break;
		 case '?':
		      status = ARG_ERROR;
		      break;
	     }
	/*
	 * Connect to the server
	 */
	if (ps_open_PostScript() == NULL)
	    status = SERVER_ERROR;
	if (status == OK) {
	    fprintf(stderr, "%s\n", HEADER);
	    if (argc == 1) {
		fprintf(stderr, "Use 'psrun -h' to get help\n");
	    }
	    /*
	     * Init the cps code
	     */
	    ps_Init();
	    /*
	     * Pass the client parameters and command arg's to the ps
	     environment
	     * Let the server load the application files
	     */
	    fprintf(PostScript, "/ClientPwd (%s) def\n", getwd(Buf1));
	    fprintf(PostScript, "/LibDir (%s) def\n", LibDir);
	    fprintf(PostScript, "/NewsLibDir (XNEWSHOME) getenv
	    (/etc/NeWS) append def\n");
	    fprintf(PostScript, "/Debug? %d 1 eq def\n", debug);
	    fprintf(PostScript, "%s\n", PsString);
	    fprintf(PostScript, "/ClientArgv [\n");
	    for (; optind < argc; optind++)
		fprintf(PostScript, "(%s)\n", argv[optind]);
	    fprintf(PostScript, "] def\n");
	    fprintf(PostScript, "ClientPwd ClientArgv loadFiles\n");
	    ps_flush_PostScript();
	    /*
	     * Fork a child to read PS commands from stdin
	     */
	    pid = fork();
	    if (pid < 0)
		status = FORK_ERROR;
	    else if (pid == 0) {
		while (status == OK) {
		    if (gets(Buf1) == NULL) {
			    status = QUIT;
			    strcpy(Buf1, "quit");
		    }
		    fprintf(PostScript, "%s\n", Buf1);
		    ps_flush_PostScript();
		}
		exit (0);
	    }
	    else {
		/*
		 * Read and handle tagged commands from the server-side
		 */
		while (status == OK) {
			if (psio_error(PostScriptInput))
			    status = PS_ERROR;
			else if (ps_tag_Quit())
			    status = QUIT;
			else if (ps_tag_Print(Buf1)) {
			    printf("%s", Buf1);
			    fflush(stdout);
			}
			else if (ps_tag_Error(Buf1)) {
			    fprintf(stderr, "%s", Buf1);
			    fflush(stderr);
			}
			else if (ps_tag_System(&cid, Buf1)) {
			    system(Buf1);
			    if (cid)
				ps_CidExec(cid, "exit");
			}
			else if (ps_tag_Popen(&cid, Buf1)) {
			    InPipe = (FILE *) popen(Buf1, "r");
			    if (InPipe == NULL)
				status = POPEN_ERROR;
			    else {
				strcpy(Buf1, "[");
				while (fgets(Buf2, STRBUFLEN, InPipe)
				!= NULL) {
				    strcat(Buf1, "(");
				    strncat(Buf1, Buf2, strlen(Buf2) -
				    1);
				    strcat(Buf1, ")");
				}
				strcat(Buf1, "] exit");
				ps_CidExec(cid, Buf1);
				pclose(InPipe);
			    }
			}
			else if (debug) {
			    if (fgets(Buf1, STRBUFLEN, PostScriptInput)
			    > 0)
				  fprintf(stderr, Buf1);
			    else
			      status = PS_ERROR;
			}
			else
			    status = PS_ERROR;
		}
		/*
		 * Clean up and quit
		 */
		ps_close_PostScript();
	    }
	}
	/*
	 * Exit
	 */
	switch (status) {
		case ARG_ERROR:
		    ErrStr = (char *) sprintf(Buf2,
			"usage: %s [-dh] [-L LibDir] [-P PsString] [
			PsFiles ]\n",
			argv[0]);
		    break;
		case IO_ERROR:
		    ErrStr = "IO error";
		    break;
		case SERVER_ERROR:
		    ErrStr = "Cannot contact NeWS server";
		    break;
		case PS_ERROR:
		    ErrStr = "PS error";
		    break;
		case FORK_ERROR:
		    ErrStr = "Fork failed";
		    break;
		case CHILD_ERROR:
		    ErrStr = "Child error";
		    break;
		default:
		    ErrStr = (char *) sprintf(Buf2,
			"abnormal termination, status = %d", status);
		    break;
	}
	if (status != QUIT)
	    fprintf(stderr, "%s: %s\n", argv[0], ErrStr);
	exit(status);
}

@@@ Fin de main.c
echo client.cps
cat >client.cps <<'@@@ Fin de client.cps'
%
% Define the tag's
%
#define QUITTAG         100
#define PRINTTAG        101
#define ERRORTAG        102
#define SYSTEMTAG       103
#define POPENTAG        104

%
% Tag routines to poll the server
%
cdef ps_tag_Quit()                      => QUITTAG
cdef ps_tag_Print(string s)             => PRINTTAG(s)
cdef ps_tag_Error(string s)             => ERRORTAG(s)
cdef ps_tag_System(int i, string s)     => SYSTEMTAG(i, s)
cdef ps_tag_Popen(int i, string s)      => POPENTAG(i, s)

%
% Routine to return a Cid result and exit
%
cdef ps_CidExec(int id, string s)
    id {s cvx exec} sendcidevent

%
% Initialization
%
cdef ps_Init()

    %
    % Tagged commands interface
    %

    /c_Quit {           % - => -
	QUITTAG tagprint
    } def

    /c_Error {          % string => -
	ERRORTAG tagprint typedprint
    } def

    /c_Debug {          % string => -
	Debug? { c_Error } { pop } ifelse
    } def

    /c_Print {          % string => -
	PRINTTAG tagprint
	typedprint
    } def

    /c_System {         % string => -
	SYSTEMTAG tagprint
	0 typedprint
	typedprint
    } def

    /c_SystemAndWait {          % string => -
	% send args: tag id string
	/MyCID uniquecid def
	SYSTEMTAG tagprint
	MyCID typedprint
	typedprint
	% wait until completed
	[ MyCID cidinterest ] forkeventmgr
	waitprocess pop
    } def

    /c_PopenAndWait {           % string => string
	% send args: tag id string
	/MyCID uniquecid def
	POPENTAG tagprint
	MyCID typedprint
	typedprint
	% wait until completed
	[ MyCID cidinterest ] forkeventmgr
	waitprocess
    } def

    %
    % Safe executor
    %

    /safeExec {                 % string|key|proc => boolean
	cvx stopped { ExecutiveErrorHandler true } { false } ifelse
    } def

    %
    % Load files
    %

    /loadFiles {        % dirname [filenames .. ] => -
	{
	    1 index exch
	    safeRun {exit} if
	} forall
	pop
    } def

    /safeRun {          % dirname filename => boolean
	{
	    (r) openFile
	    dup null eq { pop } { cvx exec } ifelse
	} safeExec
    } def

    %
    % Open a file in a directory
    %

    /openFile { % dirname filename mode => file|null
	1 index length 0 eq {
	    pop pop pop null
	} {
	    3 -1 roll
	    % drop the dirname if filename is already complete
	    2 index 0 get (/) 0 get eq {
		pop ()
	    } if
	    % Complete the dirname with a trailing slash
	    dup length 0 gt {
		dup dup length 1 sub get (/) 0 get ne { (/) append } if
	    } if
	    % Complete the path and insert the client PWD, if not
	    complete
	    3 -1 roll append
	    dup 0 get (/) 0 get ne {
		ClientPwd
		dup dup length 1 sub get (/) 0 get ne { (/) append } if
		exch append
	    } if
	    2 copy [ 3 1 roll ] (openFile: (%) %\n) exch sprintf
	    c_Debug
	    exch
	    % Open the file
	    { 2 copy file } stopped {
		pop pop
		(openFile: %\n) [
		    $error /errorname get
		] sprintf c_Error
		null
	    } if
	    % stack: string string file|null
	    % Skip over a #! line if opened for read
	    dup null ne 2 index 0 get (r) 0 get eq and {
		dup 255 string readline pop
		(#!) anchorsearch { pop pop } {
		    pop
		    closefile
		    2 copy file
		} ifelse
	    } if
	    % Return the file object
	    3 1 roll pop pop
	} ifelse
    } def

@@@ Fin de client.cps
echo Makefile
cat >Makefile <<'@@@ Fin de Makefile'
TARGET          =psrun
VERSION         =1.5
AUTHOR          =Wim Rijnsburger
INSTITUTION     =ECN Petten Holland
YEAR            =1989

INSTDIR =$(OPENWINHOME)
BINDIR  =$(INSTDIR)/bin
LIBDIR  =$(INSTDIR)/lib/VisualObjects
MANEXT  =l
MANDIR  =$(INSTDIR)/share/man/man$(MANEXT)
CC      =cc
CFLAGS  =

C_SCR   =main.c
CPS_SRC =client.cps
INCLUDE =def.h
MANPAGE =man.txt

MISC    =Makefile $(MANPAGE)
LIBS    =-I$(OPENWINHOME)/include -L$(OPENWINHOME)/lib -lcps

ALL     =$(C_SCR) $(CPS_SRC) $(MISC) $(INCLUDE)
C_OBJ   =$(C_SCR:.c=.o)
CPS_H   =$(CPS_SRC:.cps=.h)

HEADER  =$(TARGET) $(VERSION) (c) $(YEAR) $(AUTHOR), $(INSTITUTION)

.c.o:
	cc -O -c -DHEADER=\""$(HEADER)"\" -DLIBDIR=\""$(LIBDIR)"\"
	$(LIBS) $<

$(TARGET): $(C_OBJ)
	$(CC) $(CFLAGS) $(C_OBJ) -o $(TARGET) $(LIBS)

$(CPS_H): $(CPS_SRC)
	cps $(CPS_SRC)

$(C_OBJ): $(CPS_H) $(INCLUDE)


new:
	rm -f $(TARGET) $(C_OBJ) $(CPS_H) core *% *.BAK

clean:
	rm -f core *% *.BAK

list: $(ALL)
	enscript -G -b"$(HEADER)" $?
	touch list

backup:
	rm -f bak/*
	cp $(ALL) bak

install:
	install -d $(BINDIR)
	install -s $(TARGET) $(BINDIR)
	install -d $(MANDIR)
	install -c -m 444 $(MANPAGE) $(MANDIR)/$(TARGET).$(MANEXT)

update: $(TARGET)
	make install
	touch update

shar: $(TARGET).shar

$(TARGET).shar: $(ALL)
	\rm -f $(TARGET).shar
	shar $(ALL) > $(TARGET).shar
@@@ Fin de Makefile
echo man.txt
cat >man.txt <<'@@@ Fin de man.txt'

.TH PSRUN 1 "1 MARCH 1990" "ECN, Petten (Holland)"
.SH NAME
psrun \- a general client-server coupling program for NeWS applications
.SH SYNOPSIS
.B psrun [-dh] [-L \fILibDir\fP] [-P \fIPsString\fP] [ \fIPsFiles\fP ]
.SH DESCRIPTION
.LP
\fIpsrun\fP is a utility for loosely coupling of client side code with
the
window server and to down load server side code into the server.

The main program tries to connect with the server and load the
application into the server.
A child is forked to read stdin and pass it to the server.
Then a loop is started to read tagged commands from the server.

If the \fB-d\fP (debug) flag was given as an option, the psrun
reports the server's messages on stderr, in case
the interpreter failed for some reason.
Commands entered at stdin are interpreted by the server and responses
appear at stdout of \fBpsrun\fP.

Server side code files, passed as arguments to psrun are downloaded
by psrun into the server. The server searches files in a library
directory as given with the \fB-L\fP \fILibDir\fP option. The default
\fILibDir\fP
is determined by a variable in the Makefile.

The server side code can communicate with the client side via
\fBc_Print\fP
to send a string to stdout. Via \fBc_Error\fP and \fBc_Debug\fP
messages are send to
stderr. With \fBc_Quit\fP the server side code quits the session.

Immediate to execute PostScript commands can be passed to the
server via the option \fB-P\fP. For instance to get into executive
mode, where the server do not stop on errors:
.LP
.RS
.nf
.IP "\fBpsrun -d -P 'executive (Type 'c_Quit' to stop\n) c_Error'\fP"
.RE
.fi
.LP
Loose coupling with the client side of an application can be done with
redirection of stdin and stdout. For instance:
.LP
.RS
.nf
.IP "\fB/etc/mknod p1 p2\fP"
.IP "\fBpsrun -d -P 'executive' -L . \fImyapplPSfiles\fP < p1 > p2
&\fP"
.IP "\fB\fImyappl\fP < p2 > p1\fP"
.RE
.fi
.LP

.SH FILES
.TP 2.2i
/usr/openwin/bin/psrun
the command is usually installed here
.TP
/usr/openwin/lib/VisualObjects
default directory to search library files
.SH "SEE ALSO"
psh(1)
.SH DIAGNOSTICS
The status messages given by the command should be self explanatory.
.SH AUTHOR
Wim Rijnsburger, ECN, PO box 1, 1755 ZG  Petten (NH), Holland.
.SH BUGS
This is a preliminary release. Please contact me about bugs and
wishes.
.SH NOTES
The development of \fBpsrun\fP is part of the \fBVisualObjects\fP
research project of the
\fINetherlands Energy Research Foundation (ECN), Petten (NH),
Holland\fP.
@@@ Fin de man.txt
echo def.h
cat >def.h <<'@@@ Fin de def.h'
#include "client.h"

#define NOT             !
#define AND             &&
#define OR              ||

#define ARG_ERROR       2
#define QUIT            1
#define OK              0
#define IO_ERROR        -1
#define SERVER_ERROR    -2
#define PS_ERROR        -3
#define FORK_ERROR      -4
#define CHILD_ERROR     -5
#define POPEN_ERROR     -6

@@@ Fin de def.h
exit 0