[comp.protocols.appletalk] Page-reversal and job-banner enhancements for CAP's papif.c

dplatt@coherent.uucp (Dave Platt) (02/13/88)

This posting contains a set of enhancements I've made to papif., the
AppleTalk/LaserWriter "input filter" in the Columbia AppleTalk Package.
There are two major enhancements:

1) If you have TranScript from Adobe, papif will filter "conforming"
   PostScript files through psrev, thus reversing the order in which
   the pages are printed.  Net result: output is stacked as you'd like
   (page 1 on top).  Page-reversal is performed for files produced by
   the pstext filter and for any user-supplied PostScript file that is
   "minimally conforming" (begins with "%!PS-Adobe-");  PostScript
   files that are not minimally conforming are not filtered through
   psrev.

2) Whether or not you have TranScript, you can now request the printing
   of a job-trailer page.  The page contains the submitting user's name
   (printed several times at the top of the page) and a log of all of
   the major events that occurred during printing: job start, print
   start, the use of the pstext and/or psrev filters, and any printer
   errors that may have occurred.  The trailer page will be printed
   even if a PostScript error in the job causes the LaserWriter to
   flush the remainder of the job (papif sends an EOF before sending
   the trailer).

I've also added an optional "bogon" filter, which strips out the bogus
control-D character(s) that the PC-NFS print spooler insists on
adding.

These context diffs are based on the predistribution release 4 of CAP,
upgraded with the level 5, 6, and 7 patch sets in the current Columbia
and SUMEX distributions.  My sources here have had all of the bug.NNNN
patches up through bug.0014 installed in them, and thus these diffs
reflect the inclusion in papif.c of bug.0003 (removes the race
condition in the IDLESTUFF) and the one-line addition from bug.0014
(the call to ATPSetResponseTimeout).  If you have installed these bug
patches already, you should install this diff file with "patch -N" so
that patch doesn't reject the duplicate diffs.  If you haven't
installed the bug.NNNN patches, you can simply run patch on these
diffs, and then comment out the ATPSetResponseTimeout call... but I
_strongly_ suggest that you acquire and install the bug patches
(especially 3, 5, and 14), as printer reliability will be much better
once you've done so.

[Special offer... if you haven't been able to get the bug files from
 Columbia or from phri, drop me a note and I'll mail you these three.
 This offer void if I end up being swamped with requests]

Happy printing!  Comments, suggestions, and bug-reports are welcome.
 
 
Dave Platt
  UUCP:	...!{ames,sun,uunet}!coherent!dplatt
  Internet: coherent!dplatt@ames.arpa, ...@sun.com, ...@uunet.uu.net


*** Makefile.patch7	Tue Feb  9 17:31:06 1988
--- Makefile	Fri Feb 12 13:26:24 1988
***************
*** 2,8 ****
  #
  # If you have Transcript from Adobe for your laserWriter and want to
  #  print text files, uncomment the next line and set the location properly
! # ADOBEPS="-DPSTEXT=\"/usr/local/lib/ps/pstext\""
  # 
  # Note: for papif, there is another define called "IDLESTUFF" which
  # handles a problem that some sites see in papif seeing a long standing
--- 2,15 ----
  #
  # If you have Transcript from Adobe for your laserWriter and want to
  #  print text files, uncomment the next line and set the location properly
! #ADOBEPS='-DPSTEXT="/usr/local/lib/lw/pstext"'
! 
! # If you have TranScript, have uncommented the ADOBEPS line above, and
! # wish to have conforming PostScript files printed last-page-first
! # so that they stack nicely, uncomment the next line and set the
! # location properly.
! #ADOBEREV='-DPSREVERSE="/usr/local/lib/lw/psrev"'
! 
  # 
  # Note: for papif, there is another define called "IDLESTUFF" which
  # handles a problem that some sites see in papif seeing a long standing
***************
*** 10,15 ****
--- 17,35 ----
  #
  # Define SFLOWQ=1 in CFLAGS if you seem to have problems with your LW dropping
  # pkts
+ #
+ # Define TRAILER in CFLAGS if you want an end-of-job trailer page, which
+ # contains the user's name and gives a time-stamped log of the printing
+ # process (including any printer errors).
+ #
+ # Define BOGON in CFLAGS if you want the bogon (control-D) filter
+ # activated.  This filter is provided for the benefit of PC-NFS, which
+ # insists on adding a control-D at the end of its headers.
+ #
+ # Define NOSTATUS in CFLAGS if you want to suppress the periodic
+ # status-checking code (some users find that it causes the printer
+ # to hang).
+ 
  CFLAGS=-O
  LFLAGS=
  MAC_EFSSRC=/usr/src/local/mac/efs/src/mac
***************
*** 69,75 ****
  	cc ${LFLAGS} -o papif papif.o $(O)  $(CAPLIB)
  
  papif.o: papif.c
! 	cc ${CFLAGS} ${ADOBEPS} -c papif.c
  
  #
  # look, looks, and pinger all have a common source
--- 89,95 ----
  	cc ${LFLAGS} -o papif papif.o $(O)  $(CAPLIB)
  
  papif.o: papif.c
! 	cc ${CFLAGS} ${ADOBEPS} ${ADOBEREV} -c papif.c
  
  #
  # look, looks, and pinger all have a common source
*** papif.c.patch7	Tue Feb  9 17:33:03 1988
--- papif.c	Fri Feb 12 13:31:01 1988
***************
*** 17,22 ****
--- 17,24 ----
   *  July  5, 1986    CCKim		Clean up
   *  Aug  20, 1986    CCKim, Gave up on old version, use lwpr routines instead.
   *  Nov      1986    croft, restart after "status: idle", call pstext
+  *  Feb      1988    coherent!dplatt, add psrev, trailer page w/job log,
+  *                   control-D filter for PC-NFS.
   *
   */
  
***************
*** 30,35 ****
--- 32,38 ----
  #include <sys/param.h>
  #include <signal.h>
  #include <strings.h>
+ #include <ctype.h>
  
  #include <netat/appletalk.h>		/* include appletalk definitions */
  #include <netat/compat.h>
***************
*** 45,50 ****
--- 48,55 ----
  int cno;
  int ppid;
  int pid;
+ int eof, rlen, rcomp, wcomp, paperr;
+ int spc, epc;
  char host[30],printer[30],user[30];
  #define RFLOWQ 8
  #ifndef SFLOWQ
***************
*** 57,62 ****
--- 62,78 ----
  int xdebug = TRUE;
  char *ptime();
  
+ #ifdef TRAILER;
+ struct status_msg
+ {
+   struct status_msg *flink;
+   char               text[128];
+ };
+ 
+ struct status_msg *status_head, *status_tail;
+ int errors_received;
+ #endif
+ 
  /* quit gets called when we've been killed by lprm via SIGINT */
  quit()
  {
***************
*** 143,150 ****
    char *getlwname();
    char *acctfile;
  
- 
-   int spc, epc;
  #ifdef DEBUG
    int abort();
  #endif
--- 159,164 ----
***************
*** 156,174 ****
--- 170,197 ----
    getargs(argc, argv, printer, user, host, &acctfile);
  
    lwname = getlwname(printer);	/* based on this */
+ 
    if (lwname == NULL) {
      fprintf(stderr,"Cannot map name %s to LaserWriter name\n",printer);
      exit(lpd_REPRINT);
    }
  
+ #ifdef TRAILER
+   status_head = status_tail = (struct status_msg *) NULL;
+   errors_received = 0;
+   log_message("Job started.");
+ #endif
+ 
    /* init cap */
    abInit(xdebug);		/* initialize appletalk driver */
    nbpInit();
    PAPInit();			/* init PAP printer routines */
+   ATPSetResponseTimeout(sectotick(60*4)); /* set to 4 minutes */
  
    /* log message */
    fprintf(stderr,"PAPIF: Starting job for %s@%s at %s on printer %s\n",
  	  user,host,ptime(),lwname);
+   fflush(stderr);
  
  #ifdef DEBUG
    signal(SIGEMT, abort);
***************
*** 184,189 ****
--- 207,216 ----
  
    cno = openlw(lwname);
  
+ #ifdef TRAILER
+   log_message("Printer open.");
+ #endif;
+ 
  #ifndef NOSTATUS
    /* base runs status process */
    if ((pid = fork()) < 0) {
***************
*** 237,243 ****
--- 264,272 ----
    }
    do {
      abSleep(16, TRUE);
+ #ifndef NOSTATUS
      pstatus(status.StatusStr);
+ #endif
    } while (ocomp  > 0);
    return(cno);
  }
***************
*** 249,269 ****
  */
  pstext()
  {
!   char magic[2];
!   int fdpipe[2];
!   int fpid;
  
!   if (read(fileno(stdin),magic,2) != 2) {
      perror("pstext setup: read");
      quit();
    }
    rewind(stdin);
    lseek(fileno(stdin), 0L, 0);
!   if (strncmp(magic,"%!",2) == 0) /* postscript file? */
!     return;			/* yes, no more to do */
!   /* else assume a text file and pipe thru pstext */
    if (pipe(fdpipe) != 0) {
!     perror("pstext setup: pipe");
      quit();
    }
    fpid = vfork();
--- 278,330 ----
  */
  pstext()
  {
!   char magic[11];
  
!   if (read(fileno(stdin),magic,11) != 11) {
      perror("pstext setup: read");
      quit();
    }
    rewind(stdin);
    lseek(fileno(stdin), 0L, 0);
!   if (strncmp(magic, "%!", 2) == 0)
!     { /* It's PostScript;  no need to filter through pstext */
! #ifdef PSREVERSE
!       if (strncmp(magic, "%!PS-Adobe-", 11) != 0)
! 	{
! 	  return; /* Nonconforming PostScript;  send as-received */
! 	}
! #else
!       return;
! #endif
!     }
!   else
!     {
!       fork_filter(PSTEXT);   /* Not PostScript;  filter it */
! #ifdef TRAILER
!       log_message("Text-to-PostScript filter open.");
! #endif
!     }
! #ifdef PSREVERSE
!   fork_filter(PSREVERSE);
! #ifdef TRAILER
!  log_message("Page-reversal filter open.");
! #endif
! #endif
! }
! 
! /*
!   Run a filter program in a child process;  diddle the descriptors so that
!   the filter eats the parent process's former stdin, and pipes its output
!   into the parent's new stdin.
! */
! fork_filter(whatever)
! char *whatever;
! {
!   int fdpipe[2];
!   int fpid;
! 
    if (pipe(fdpipe) != 0) {
!     perror("filter setup: pipe");
      quit();
    }
    fpid = vfork();
***************
*** 270,294 ****
    switch (fpid) {
    case 0:			/* child */
      if (dup2(fdpipe[1], fileno(stdout)) == -1) {
!       perror("pstext setup: child dup2");
        quit();
      }
      close(fdpipe[1]);		/* ignore errs */
      close(fdpipe[0]);
!     execl(PSTEXT, "pstext", 0);
      /* if we are here again, then... */
!     perror("pstext setup: child exec");
      kill(ppid, SIGINT);
      quit();
      break;
    case -1:
!     perror("pstext setup: fork");
      quit();
      break;
    default:			/* parent continues */
      /* set up stdin to be pipe */
      if (dup2(fdpipe[0],fileno(stdin)) == -1) {
!       perror("pstext setup: parent dup2");
        quit();
      }
      close(fdpipe[1]);		/* ignore errs */
--- 331,355 ----
    switch (fpid) {
    case 0:			/* child */
      if (dup2(fdpipe[1], fileno(stdout)) == -1) {
!       perror("filter setup: child dup2");
        quit();
      }
      close(fdpipe[1]);		/* ignore errs */
      close(fdpipe[0]);
!     execl(whatever, "psfilter", 0);
      /* if we are here again, then... */
!     perror("filter setup: child exec");
      kill(ppid, SIGINT);
      quit();
      break;
    case -1:
!     perror("filter setup: fork");
      quit();
      break;
    default:			/* parent continues */
      /* set up stdin to be pipe */
      if (dup2(fdpipe[0],fileno(stdin)) == -1) {
!       perror("filter setup: parent dup2");
        quit();
      }
      close(fdpipe[1]);		/* ignore errs */
***************
*** 304,317 ****
  sendfile(cno)
  int cno;
  {
!   int eof, rlen, rcomp, wcomp, paperr, err, fd;
  
    fd = fileno(stdin);
    /* post initial read from LW */
    if ((paperr = PAPRead(cno, rbuf, &rlen, &eof, &rcomp)) < 0) {
  	fprintf(stderr,"PAPRead error %d\n",paperr);
    }
!     
    wcomp = 0;
    /* this is the main read/write loop */
    do {
--- 365,395 ----
  sendfile(cno)
  int cno;
  {
!   int err, fd;
  
+ #ifdef TRAILER
+   static char trailercode[] = "\ngsave initgraphics /Helvetica findfont 36\
+  scalefont setfont /gl 0 def 700 -36 400 {200 exch moveto gl setgray (%s)\
+  show /gl gl .12 add def} for /Helvetica findfont 8 scalefont setfont 0\
+  setgray\n";
+   static char logcode[] = "50 %d moveto (%s) show\n";
+   char trailercmd[256];
+   enum {sendbody, sendEOF1, sendident, sendlog, sendEOF2}
+    trailer_phase = sendbody;
+   int log_offset = 350;
+ #endif  
+ 
    fd = fileno(stdin);
    /* post initial read from LW */
    if ((paperr = PAPRead(cno, rbuf, &rlen, &eof, &rcomp)) < 0) {
  	fprintf(stderr,"PAPRead error %d\n",paperr);
+ 	fflush(stderr);
    }
!   fprintf(stderr,"Sending file");
!   fflush(stderr);
! #ifdef TRAILER
!   log_message("Printing starts.");
! #endif  
    wcomp = 0;
    /* this is the main read/write loop */
    do {
***************
*** 318,331 ****
--- 396,416 ----
      if (rcomp <= 0) {
        if (rcomp != noErr) {
  	fprintf(stderr,"PAPRead error %d\n",rcomp);
+ 	fflush(stderr);
  	break;
        } else if (rlen > 0) {
  	rbuf[rlen] = '\0';
  	fprintf(stderr,"%s",rbuf);
+ 	fflush(stderr);
+ #ifdef TRAILER
+ 	errors_received++;
+ 	log_message(rbuf);
+ #endif
        }
        paperr = PAPRead(cno, rbuf, &rlen, &eof, &rcomp);
        if (paperr < 0) {
  	fprintf(stderr,"PAPRead error %d\n",paperr);
+ 	fflush(stderr);
  	break;
        }
      }
***************
*** 332,344 ****
      if (wcomp <= 0) {
        if (wcomp != noErr) {
  	fprintf(stderr,"PAPWrite error %d\n",wcomp);
  	kill(ppid, SIGINT);	/* signal abort to parent */
        }
        else {
! 	err = read(fd, buf, SBUFMAX);
! 	if (err > 0) 
! 	  if ((paperr = PAPWrite(cno, buf, err, FALSE, &wcomp)) < 0)
  	    break;
        }
      }
      abSleep(4, TRUE);		/* wait a bit */
--- 417,509 ----
      if (wcomp <= 0) {
        if (wcomp != noErr) {
  	fprintf(stderr,"PAPWrite error %d\n",wcomp);
+ 	fflush(stderr);
  	kill(ppid, SIGINT);	/* signal abort to parent */
        }
        else {
! #ifdef TRAILER
! 	switch (trailer_phase)
! 	  {
! 	  case sendbody:
! 	    err = read(fd, buf, SBUFMAX);
! 	    if (err != 0) break;
! 	    trailer_phase = sendEOF1;
! 	  case sendEOF1:
! 	    sendPAPeof();
! 	    epc = getpagecount(cno);
! 	    if (spc != -1 && epc != -1)
! 	      {
! 	        if (epc - spc == 1)
! 	          {
! 	            log_message("End-of-file; 1 page printed.");
! 	          }
! 	        else
! 	          {
! 		    sprintf(buf, "End-of-file; %d page printed.", epc-spc);
! 		    log_message(buf);
! 		  }
! 	      }
! 	    else
! 	      {
! 		log_message("End-of-file.");
! 	      }
! 	    fprintf(stderr, " EOF... ");
! 	    fflush(stderr);
! 	    trailer_phase = sendident;
! 	  case sendident:
! 	    fprintf(stderr, " trailer");
! 	    fflush(stderr);
! 	    log_message("Job completed.");
! 	    if (strlen(user) > 0)
! 	      {
! 		sprintf(buf, trailercode, user);
! 	      }
! 	    else
! 	      {
! 		sprintf(buf, trailercode, "no name");
! 	      }
! 	    err = strlen(buf);
! 	    trailer_phase = sendlog;
  	    break;
+ 	  case sendlog:
+ 	    if (status_head != (struct status_msg *) NULL)
+ 	      {
+ 		sprintf(buf, logcode, log_offset, status_head->text);
+ 		log_offset -= 9;
+ 		status_head = status_head->flink;
+ 	      }
+ 	    else
+ 	      {
+ 		strcpy(buf, "showpage grestore\n");
+ 		trailer_phase = sendEOF2;
+ 	      }
+ 	    err = strlen(buf);
+ 	    break;
+ 	  case sendEOF2:
+ 	    err = 0;
+ 	    break;
+ 	  }
+ #else
+ 	err = read(fd, buf, SBUFMAX);
+ #endif
+ 	if (err > 0)
+ 	  {
+ #ifdef BOGON
+ 	    register int bogon_scan;
+ 	    for (bogon_scan = 0; bogon_scan < err; bogon_scan++)
+ 	      {
+ 		if (buf[bogon_scan] == '\004')
+ 		  {
+ 		    buf[bogon_scan] = '\n';
+ 		  }
+ 	      }
+ #endif
+ 	    if ((paperr = PAPWrite(cno, buf, err, FALSE, &wcomp)) < 0)
+ 	      break;
+ 	    fprintf(stderr, ".");
+ 	    fflush(stderr);
+ 	  }
+ 	
        }
      }
      abSleep(4, TRUE);		/* wait a bit */
***************
*** 346,370 ****
  
    if (err < 0)			/* this is a little overloaded */
      perror("read");
!   paperr = PAPWrite(cno, NULL, 0, TRUE, &wcomp); /* send eof */
!   while (!eof) {		/* wait for completion */
!     if (rcomp <= 0) {
!       if (rcomp != noErr)  {
! 	fprintf(stderr,"PAPRead error %d\n",rcomp);
! 	break;
!       } else if (rlen > 0) {
! 	rbuf[rlen] = '\0';
! 	fprintf(stderr,"%s",rbuf);
!       }
!       if (eof) break;
!       PAPRead(cno, rbuf, &rlen, &eof, &rcomp);
!     }
!     abSleep(4,TRUE);
!   } 
!   if (paperr != noErr)
!     fprintf(stderr,"PAPWrite error %d\n",paperr);
!   else
!     do { abSleep(4, TRUE); } while (wcomp > 0);
  }
  
  /*
--- 511,521 ----
  
    if (err < 0)			/* this is a little overloaded */
      perror("read");
!   fprintf(stderr, " EOF... ");
!   fflush(stderr);
!   sendPAPeof();
!   fprintf(stderr, " fini.\n");
!   fflush(stderr);
  }
  
  /*
***************
*** 527,533 ****
    AddrBlock addr;
    int hangup();
    PAPStatusRec status;
!   
    PAPHalfClose(cno);
    signal(SIGCHLD, hangup);
    addr.net = 0;		/* sufficient */
--- 678,687 ----
    AddrBlock addr;
    int hangup();
    PAPStatusRec status;
! #ifdef IDLESTUFF
!   int count = 0;
! #endif IDLESTUFF
! 
    PAPHalfClose(cno);
    signal(SIGCHLD, hangup);
    addr.net = 0;		/* sufficient */
***************
*** 542,557 ****
        cpyp2cstr(tmpbuf, status.StatusStr);
        if (strncmp("status: idle", tmpbuf, 12) == 0
  	  || access("/tmp/papifidletest", F_OK) == 0) {
! 	fprintf(stderr,"PAPIF:  status: idle bug; restarting\n");
! 	fflush(stderr);
! 	unlink("/tmp/papifidletest");
! 	for (i = 0 ; i < NSIG ; i++)
! 	  signal(i, SIG_IGN);
! 	sprintf(retry,
! 		"(sleep 2;/etc/lpc abort %s;sleep 2;/etc/lpc start %s)&", 
! 		printer, printer);
! 	system(retry);
! 	exit(lpd_REPRINT);
        }
      }
  #endif IDLESTUFF
--- 696,716 ----
        cpyp2cstr(tmpbuf, status.StatusStr);
        if (strncmp("status: idle", tmpbuf, 12) == 0
  	  || access("/tmp/papifidletest", F_OK) == 0) {
! 	count++;
! 	if (count >= 3) {	/* Idle 30 seconds?  Could be made shorter. */
! 	  fprintf(stderr,"PAPIF:  status: idle bug; restarting\n");
! 	  fflush(stderr);
! 	  unlink("/tmp/papifidletest");
! 	  for (i = 0 ; i < NSIG ; i++)
! 	    signal(i, SIG_IGN);
! 	  sprintf(retry,
! 		  "(sleep 2;/etc/lpc abort %s;sleep 2;/etc/lpc start %s)&", 
! 		  printer, printer);
! 	  system(retry);
! 	  exit(lpd_REPRINT);
! 	}
!       } else {
!         count = 0;	/* once it goes non-idle; restart count */
        }
      }
  #endif IDLESTUFF
***************
*** 558,561 ****
--- 717,800 ----
      pstatus(status.StatusStr);
      sleep(10);			/* update every 10 seconds */
    } while (1);
+ }
+ 
+ #ifdef TRAILER
+ 
+ int
+ log_message(what)
+      char *what;
+ {
+   struct status_msg *msg;
+   char text[512], *next;
+ 
+   msg = (struct status_msg *) malloc(sizeof(struct status_msg));
+   msg->flink = (struct status_msg *) NULL;
+ 
+   if (status_tail)
+     {
+       status_tail->flink = msg;
+     }
+   else
+     {
+       status_head = msg;
+     }
+   status_tail = msg;
+ 
+   strcpy(text, ptime());
+   strcat(text, "    ");
+ 
+   next = text+strlen(text);
+   
+   while (*what)
+     {
+       if (*what >= ' ')
+ 	{
+ 	  if (!isspace(*what) && !isalnum(*what)) /* Overkill, but WTF */
+ 	    {
+ 	      *next++ = '\\';
+ 	    }
+ 	  *next++ = *what;
+ 	}
+       what++;
+     }
+ 
+   *next = '\0';
+ 
+   strncpy(msg->text, text, sizeof msg->text);
+ }
+ #endif
+ 
+ int
+ sendPAPeof()
+ {
+ 
+   paperr = PAPWrite(cno, NULL, 0, TRUE, &wcomp); /* send eof */
+   while (!eof) {		/* wait for completion */
+     if (rcomp <= 0) {
+       if (rcomp != noErr)  {
+ 	fprintf(stderr,"PAPRead error %d\n",rcomp);
+ 	fflush(stderr);
+ 	break;
+       } else if (rlen > 0) {
+ 	rbuf[rlen] = '\0';
+ 	fprintf(stderr,"%s",rbuf);
+ 	fflush(stderr);
+ #ifdef TRAILER
+ 	log_message(rbuf);
+ 	errors_received++;
+ #endif
+       }
+       if (eof) break;
+       PAPRead(cno, rbuf, &rlen, &eof, &rcomp);
+     }
+     abSleep(4,TRUE);
+   } 
+   if (paperr != noErr)
+     {
+       fprintf(stderr,"PAPWrite error %d\n",paperr);
+       fflush(stderr);
+     }
+   else
+     do { abSleep(4, TRUE); } while (wcomp > 0);
  }
-- 

Dave Platt
  UUCP:	...!{ames,sun,uunet}!coherent!dplatt
  Internet: coherent!dplatt@ames.arpa, ...@sun.com, ...@uunet.uu.net