[comp.windows.news] Handling PostScript Errors

Anthony.D.Worrall@reading.ac.uk (Anthony Worrall) (02/19/90)

I am in the process of linking a high level language (pop11) to NeWS.
Abitrary PostScript code can be sent from the client to the server. The problem is that what is the best way to catch any errors in the postscipt
code down loaded. I can think of two posible ways of doing this.

1.	Down load the code in the context of a stopped opperator and 
	return to the client the error condition.

2.	Put error handlers in errordict that do not call stop and modify
	handleerror to report the error to the client.

Which method have other people used? Are there any other ways of catching
errors? Example code would be most welcome.


Anthony.Worrall@Reading.ac.uk

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

Anthony Worrall posted a request a few days ago for a more debug
supporting version of
psh. I think I have what he needs.

We use as long as NeWS exists our own replacement for psh, called
psrun. This command downloads server side code, 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
	psrun -d -L . -P "executive"

All commands typed at stdin are given to the server. Try to type
garbage to test the error catching. 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 BACKUP 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

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

A couple of days ago I posted the source of our 'psrun', a 'psh' alike
utility
for loose coupling of applications with NeWS user interfaces.

Unfortunately there was a minor incorrectness in the manual page file
'man.txt':

	2c2
	< .TH PSRUN 1 "1 MARCH 1990" "ECN, Petten (Holland)"
	---
	> .TH BACKUP 1 "1 MARCH 1990" "ECN, Petten (Holland)"

The error resulted from the fact that I'm to lazy to write manual pages
from scratch and use as mutch as I can from existing manual pages. I
forgot to grep on the old command name in capitals too :-(


Wim.

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