ftp.nice.ch/pub/next/tools/printer/JetDirectDriver.0.96.NI.bs.tar.gz#/JetDirectDriver/tcp-lp.c

This is tcp-lp.c in view mode; [Download] [Up]

/*
tcp-lp.c - A 'if' printer filter(driver) for HP JetDirect TCP socket (9100) for
	NEXTSTEP.  Hacked version of one in PLP (see below).

*** Compile as follows

Single arch compile:
cc -Wall -s -o tcp-lp tcp-lp.c

Fat compile (add -arch xxxx as needed):
cc -Wall -s -arch m68k -arch i386 -o tcp-lp tcp-lp.c

Fat from separate arch binaries:
lipo tcp-lp.i386 tcp-lp.m68k -create -output tcp-lp

*** Revision History
96-09-20 Version 0.96: Izumi Ohzawa
	minor stuff
95-04-10 Version 0.94: Izumi Ohzawa
	Won't accept file unless it starts with "%!" to avoid
	getting killed by possible junk PS generated from Windows.

94-12-09 Version 0.93: Izumi Ohzawa
	Transfer rate logging.

94-11-25 Version 0.92: Izumi Ohzawa
	It does page accouting now normally to the file specified for "af"
	in NetInfo.  To do page accounting, tcp-lp now makes two connections
	to the printer in sequence: one to get page count before the job and
	to send actual print job, and another to obtain the page count after
	the job.  This was necessary to do it reliably for all HP printers
	and JetDirect cards we use.  Apparently, there are subtle differences
	between JetDirect-equipped printers (see code).
	It also creates /usr/adm/<printername>.pages for storing current
	page count after each job.  This is to account for printing that does
	not go through this server (e.g. via parallel, serial, local talk ports
	or via other network protocols or hosts).
	These unaccounted for pages are recorded in the accounting file using
	"other_ports:unknown" instead of the usual "hostname:userid".

94-11-17 Version 0.91: Izumi Ohzawa
	Looks up hostname and port in /usr/local/lib/<printername>.conf
	where <printername> is the local name of the printer in the local
	NetInfo domain.
	E.g., if the printername is "hp_DJ1200", the file
	/usr/local/lib/hp_DJ1200.conf should contain a line:
	myhpdj 9100

94-11-16 Version 0.9:  Izumi Ohzawa <izumi@pinoko.berkeley.edu>


*** NOTES

JetDirect support in NS3.3(PR2 and probably final) drops all
PostScript errors messages, and users won't know what happened when
an error occurs.  It can often happen when printing PostScript of
non-NEXTSTEP origin.  Also, there is an obscure bug of prserver
dropping JetDirect connection before it completes output.  Hence
this replacement until NeXT fixes 'prserver' which I consider as
not really usable with JetDirect.  I could not make 'prserver' work
reliably with JetDirect(J2550A)+DeskJet1200C/PS.

With tcp-lp, PostScript errors are mailed to the user.  Since I do
not have info on how 'prserver' messages 'npd' of the originating
host which in turn launches /usr/lib/NextPrinter/Inform.app/Inform
and produces voice alert, I opted for this option for error
notification.


*** Caveats

Since it replaces 'prserver', a lot of features may be missing.
* No page reversal (problem for marked-page-up printers, but OK for HPLJ4).
* PostScript only.  No PCL's please.

[Although tcp-lp is far better in returning PS errors than 'prserver'
which doesn't even attempt to report PS errors for JetDirect.]
* It will NOT catch all PostScript errors for jobs that take a long
    time to execute.  It will wait ERROR_WAIT_SEC seconds (see #define
    below) _after_ all PS code has been transmitted to the printer.
    If the error is not returned before ERROR_WAIT_SEC expires, it will close
    the connection.  PS errors can still occur for the remaining portion of PS
    code in the printer's input buffer.  Short jobs will signal the end of
    PS interpretation by sending back "%%PrintDone".  If this is returned,
    the connection is closed with no further wait, because we know that
    everything has been executed successfully.
        ERROR_WAIT_SEC must be shorter than "Idle Timeout" duration
    that is set on the printer (factory default 90 secs).  The printer will
    close the connection upon idle timeout on the printer.   If this filter
    has the connection still up when this happens, tcp-lp is killed, and "lpd"
    will try to restart the print job.  This will result in an endless loop
    where the same pages are printed over and over again.


*** To Do (by you):

* Use of NetInfo to get printer's hostname and TCP port? (Now info comes from a file).
	ni_propval.c in the package contains necessary code for using NetInfo
	for config info, but is this any better?  I will keep the .conf
	file method as it seems easier.
* Modify to use Alert Panel (Inform) for error indication instead of e-maill.


*** Files:
	/usr/local/lib/tcp-lp 			(this executable)
	/usr/local/lib/<printername>.conf	(printer hostname and TCP port#)
	/usr/adm/<printername>.pages		(page count after last run)
	/usr/adm/<printername>.acct		(accounting file - from NetInfo)
	/usr/adm/lpd-errs			(stderr output goes there)

** Originally from:
[See file LICENSE.original.PLP  - I did not want to replace all of lpd stuff.]

    PLP Portable Line Printer Spooler release 3.4
    Copyright 1988 (C) Patrick Powell
    
    Patrick Powell,
    Dept. of Computer Science,
    University of Minnesota,
    Minneapolis, Minnesota.
    Wed Apr 13 14:53:36 CDT 1988

This person must also have written tcp-lp.c --> Justin Mason 26 Aug 94.


--- Note to myself ---

NS3.2 arguments that lpd passes to "if" filter:
argv[0]: tcp-lp
argv[1]: -w0
argv[2]: -l0
argv[3]: -i0
argv[4]: -f
argv[5]: dfA006hostname
argv[6]: -n
argv[7]: izumi
argv[8]: -h
argv[9]: hostname
argv[10]: -p
argv[11]: Local_LPT
argv[12]: /usr/adm/label.acct

% PostScript Page Counting code
%!
% Level-1 way
/str 16 string def
statusdict begin pagecount str cvs print (\n) print flush end

%!
% Level-2 way
/str 16 string def
currentsystemparams begin PageCount str cvs print(\n) print flush end


*/

#include "lp-pipe.h"
#include <stdio.h>
#include <string.h>
#include <libc.h>
#include <stdlib.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>

/* #define	DEBUG */

#ifndef DEF_PRINTER_HOST
#define DEF_PRINTER_HOST	"printerhost"
#endif

#define DEF_PRINTER_PORT	9100

/* ERROR_WAIT_SEC is a timeout count in seconds that the connection is kept
 * after all PS code is transmitted.  For large PS jobs, it takes some time
 * until error message is generated (if any).  Wait is terminated if
 * "%%[ Flushing" or "%%PrintDone" is returned, indicating fatal PS error
 * or normal job completion, respectively. */
#define	ERROR_WAIT_SEC		60	/* seconds to keep connection after transmission done */
#define OPEN_RETRY_DELAY	15	/* delay for open attempts */
#define WAIT_RETRY_DELAY	5	/* delay while waiting for print */

int pages;		/* # of pages printed in this run */
int pagesbefore=0;	/* # of pages printed before this run */
int pagesafter=0;	/* # of pages printed after this run */
int pageslast=0;	/* pages-printed from file in /usr/adm */
int pagecountCame = 0;
char *ohost="nohost";
char *user="izumi";
char *printername="hp_DJ1200";
char *acctfile="/usr/adm/hp-jd.acct";
char *tempfile="/tmp/printer-errors";

/* OLD
%!
statusdict /pagecount get exec(                )cvs print ( \n) print flush
*/

char *pages_before_ps = "%!\n\
(%%PagesBefore: ) print\n\
statusdict begin pagecount == end flush\n";

char *pages_after_ps = "%!\n\
(%%PagesAfter: ) print\n\
statusdict begin pagecount == end flush\n\004";

char *print_done_ps = "%!\n(%%PrintDone\\n) print flush\n\004";
char timestr[64];



int sockfd = -1;
char Host[64];
unsigned short Port;


void open_sock(void) {
    struct hostent *host;       /* host entry pointer */
    struct sockaddr_in sin;

    if ((host = gethostbyname (Host)) == NULL) {
	fprintf (stderr, "unknown host %s: %s\n", Host, ERRSTR);
	exit (LP_FAILED);
    }
    bzero ((char *) &sin, sizeof (sin));
    sin.sin_family = AF_INET;
    bcopy (host->h_addr, (caddr_t) & sin.sin_addr, host->h_length);
    sin.sin_family = host->h_addrtype;
    sin.sin_port = htons (Port);

    do {	/* open the socket. */

        if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
            perror("can't open socket");
        }

        if (connect(sockfd, (struct sockaddr *) & sin, sizeof (sin)) == 0) {
            break;
        } else {
	    close(sockfd);
	    sleep(OPEN_RETRY_DELAY);
        }
    } while (1);                /* block forever */
}

int
do_write (int fd, char *buf, int n) {
    int i;
    int N;

    N = n;
    while (n > 0) {
	if ((i = write (fd, buf, n)) < 0)
	    return i;
	n -= i;
	buf += i;
    }
    return N;
}

void
tcp_pipe(void) {
    int in, out;
    int eoifromstdin = 0;
    int eoifromprn = 0;
    int pseofdone = 0;
    int significant_error = 0;
    int printDone = 0;
    long currtime = 0;
    int firsttime = 1;	/* to analyze the first buf-ful for %%Title: */
    int do_exit = 0;
    int isPostScript = 0;
    fd_set rmask;
    char buf[BUFSIZ];
    char bufcopy[BUFSIZ+1];
    char pstitle[BUFSIZ], pscreator[BUFSIZ];
    char junkbuf[BUFSIZ];
    char *cptr;
    FILE *fp;
    struct timeval tp;
    struct timezone tzp;
    double xstarttime, xendtime;
    unsigned long bytestotal;

    if((fp=fopen(tempfile, "w")) != NULL) {
	    fprintf(fp,"\nThe following error(s) occurred while printing your job:\n\n");
	    fprintf(fp,"User: %s,   Host: %s,   Printer: %s\n",
		    user, ohost, printername);
    }


    time(&currtime);
    strcpy(timestr, ctime(&currtime));
    timestr[24] = '\0';
    fprintf(stderr, "%s tcp-lp: %s %s@%s - start ================\n",
		timestr+4, printername, user, ohost);

    open_sock();

    time(&currtime);
    strcpy(timestr, ctime(&currtime));
    timestr[24] = '\0';
    fprintf(stderr, "%s tcp-lp: %s %s@%s - connected\n",
		timestr+4, printername, user, ohost);

    gettimeofday(&tp, &tzp);
    xstarttime = (double)(tp.tv_usec);
    xstarttime /= 1000000.0;
    xstarttime += (double)(tp.tv_sec);

    /* Send PS code to get page count before this job */
    do_write(sockfd, pages_before_ps, strlen(pages_before_ps));
    bytestotal = strlen(pages_before_ps);

    /* copy stdin to socket to the printer */
    while ( (!eoifromstdin) || (!printDone && pseofdone < ERROR_WAIT_SEC) ) {
	FD_ZERO (&rmask);
	FD_SET (0, &rmask);	/* stdin */
	FD_SET (sockfd, &rmask);	/* socket */
	/* Find out where input is available */
	select (sockfd + 1, &rmask, 0, 0, 0);
	/* == Process input from stdin == */
	if (FD_ISSET (0, &rmask)) {
	    in = read (0, buf, BUFSIZ);
	    switch (in) {
		case -1:	/* error */
		    perror ("read error (std input)");
		    do_exit = LP_RETRY;
		    eoifromstdin = 1;
		    break;
		case 0:		/* EOF */
		    eoifromstdin = 1;
		    break;
		default:
		    if(firsttime) {
			/* copy to terminate it as a string */
			bcopy(buf, bufcopy, in);
			bufcopy[in] = 0;
			cptr = strstr(bufcopy, "%%Title:");	/* get Title of ps job */
			if(cptr == NULL) strcpy(pstitle, "(none)");
			else {
			    cptr = index(cptr, ':'); cptr++;
			    sscanf(cptr, "%[^\n]", pstitle);
			}
			if(fp) fprintf(fp,"Title: %s\n", pstitle);
			fprintf(stderr,"  Title: %s\n", pstitle);
			cptr = strstr(bufcopy, "%%Creator:");	/* get Creator of ps job */
			if(cptr == NULL) strcpy(pscreator, "(none)");
			else {
			    cptr = index(cptr, ':'); cptr++;
			    sscanf(cptr, "%[^\n]", pscreator);
			}
			if(fp) fprintf(fp,"Creator: %s\n---\n", pscreator);
			fprintf(stderr,"  Creator: %s\n", pscreator);
			/* Allow only PostScript */
			if(strncmp(bufcopy, "%!", 2) == 0) {
			    isPostScript = 1;		/* OK, PS file */
			} else {
			    isPostScript = 0;		/* not a PS file */
			    significant_error = 1;
			    sscanf(bufcopy, "%[^\n]", junkbuf);
			    fprintf(stderr, "File does not start with PostScript ID: %%!\n");
			    fprintf(stderr, "%s\nFlushing print job...\n", junkbuf);
			    if(fp) {
				fprintf(fp, "File does not start with PostScript ID: %%!\n");
			        fprintf(fp, "%s\nFlushing print job...\n", junkbuf);
				fprintf(fp, "If you are using Windows, disable Ctrl-D.\n");
				fprintf(fp,
			    "(see ftp://tuna2/pub/dos-win/win/lpr_printing/psctrld.ver1.0/)\n");
			    }
			}

			firsttime = 0;	/* do it only once */
		    }
		    if(isPostScript) {	/* send only if valid PS */
			out = do_write (sockfd, buf, in);
			bytestotal += out;				/* count total # bytes */
			if (in != out) {
			    perror ("write error (socket)");
			    do_exit = LP_RETRY;
			    eoifromstdin = 1;
			}
		    }
		    break;
	    }
	}

	if(eoifromstdin) { /* input is EOF, wait for error message if any */
	    if(pseofdone == 0) {
		gettimeofday(&tp, &tzp);
		xendtime = (double)(tp.tv_usec);
		xendtime /= 1000000.0;
		xendtime += (double)tp.tv_sec;
		/* let the printer echo "%%PrintDone" when it finishes PS code */
		do_write(sockfd, print_done_ps, strlen(print_done_ps));
		time(&currtime);
		strcpy(timestr, ctime(&currtime));
		timestr[24] = '\0';
		fprintf(stderr, "%s tcp-lp: %s %s@%s - transmit complete\n",
			    timestr+4, printername, user, ohost);
		xendtime -= xstarttime;		/* xendtime is # secs for xmit */
		fprintf(stderr, "Sent %lu bytes in %.3f seconds (%.2f kbytes/sec)\n",
			bytestotal, xendtime, ((double)bytestotal/1024.0)/xendtime);
	    }
	    pseofdone++;
	    sleep(1);
	}

	/* == Now check output from the printer == */
	if(FD_ISSET (sockfd, &rmask)) {
	    in = read (sockfd, buf, BUFSIZ);
	    switch (in) {
		case -1:	/* error */
		    perror ("read error (socket)");
		    do_exit = LP_RETRY;
		    eoifromprn = 1;
		    break;
		case 0:		/* EOF */
		    eoifromprn = 1;
		    break;
		default:
		    /* out = do_write (0, buf, in); *//* yes, we write to stdin! */
		    out = do_write( 2, buf, in);
		    if (in != out) {
			perror ("write error (std input)");
			do_exit = LP_RETRY;
			eoifromprn = 1;
		    }
		    if(in > 10) {
			/* needs copying because buf is not a string */
			bcopy(buf, bufcopy, in);
			bufcopy[in] = 0;
			/* The following is for filtering out non-fatal errors such as:
			   [SomeFont not found, using Courier]
			*/ 
			if( strstr(bufcopy, "%%[ Error:") ) {
			    significant_error = 1;	/* set flag for error reporting */
			}

			if( strstr(bufcopy, "%%[ Flushing:") ) {
			    significant_error = 1;	/* set flag for error reporting */
			    printDone = 1;		/* nothing more will come back */
			}

			if( (cptr = strstr(bufcopy, "%%PagesBefore:")) != NULL) {
			    cptr = index(cptr, ':'); cptr++;
			    sscanf(cptr, "%d", &pagesbefore);
			}

			if( (cptr = strstr(bufcopy, "%%PrintDone")) != NULL) {
			    printDone = 1;
			}
		    }
		    if(fp) fwrite(buf, 1, in, fp);
		    break;
	    }  /* end switch() */
	}   /* end if(FD_ISSET (sockfd, &rmask)) */
    } /* end while() */
    close (sockfd);

    time(&currtime);
    strcpy(timestr, ctime(&currtime));
    timestr[24] = '\0';
    if(printDone)
        fprintf(stderr, "%s tcp-lp: %s %s@%s - imaging complete\n",
		timestr+4, printername, user, ohost);
    else
        fprintf(stderr, "%s tcp-lp: %s %s@%s - timeout idle connection(%ds)\n",
		timestr+4, printername, user, ohost, pseofdone);


    /* Now make a new connection again to get page count after printing.
     * This was necessary because there are subtle differences in
     * the way PS interpreters and/or JetDirect behave upon receiving
     * ^D char, and other differences.  Specifically,
     * HPLJ4M/PS + J2552A JetDirect worked with PS codes with
     * multiple jobs separated by ^D sent via a single connection,
     * but HPDJ1200C/PS + J2550A JetDirect did not work.
     * (J2552A has 10BaseT, BNC and LocalTalk connector)
     * (J2550A is 10BaseT Ethernet only)
     * ^D really must be sent to make it work even with PS errors which
     * flushes the rest of the job.  Better kill the connection and start
     * fresh.  So, I had to close the connection for the main job, and
     * open a separate connection just to get the page count afterwards.
    */
 
    pseofdone = 0;
    eoifromprn = 0;
    pagecountCame = 0;
    open_sock();
    /* send PS code to print page count out to PS stdout */
    do_write(sockfd, pages_after_ps, strlen(pages_after_ps));

    while ( !pagecountCame && pseofdone < ERROR_WAIT_SEC ) {
	FD_ZERO (&rmask);
	FD_SET (sockfd, &rmask);	/* socket */
	/* Find out where input is available */
	select (sockfd + 1, &rmask, 0, 0, 0);
	if(FD_ISSET (sockfd, &rmask)) {  /* socket has data for reading */
	    in = read (sockfd, buf, BUFSIZ);
	    switch (in) {
		case -1:	/* error */
		    perror ("read error (socket)");
		    do_exit = LP_RETRY;
		    eoifromprn = 1;
		    break;
		case 0:		/* EOF */
		    eoifromprn = 1;
		    break;
		default:
		    /* out = do_write (0, buf, in); *//* yes, we write to stdin! */
		    if(in > 10) {
			/* needs copying because buf is not a string */
			bcopy(buf, bufcopy, in);
			if(bufcopy[in-1] == '\n') {
			    bufcopy[in] = 0;
			}
			else {
			    bufcopy[in] = '\n';
			    bufcopy[in+1] = 0;
			}
			if( (cptr = strstr(bufcopy, "%%PagesAfter:")) != NULL) {
			    cptr = index(cptr, ':'); cptr++;  /* now on space */
			    sscanf(cptr, "%d", &pagesafter);
			    /* some fixing needed for JetDirect on HP1200C/PS */
			    // cptr++; cptr = index(cptr, ' '); /* space after pg# */
			    // cptr++; *cptr++ = '\n'; *cptr = '\0';
			    // in = cptr - bufcopy;
			    pagecountCame = 1;
			}
		    }
		    out = do_write( 2, bufcopy, strlen(bufcopy));	/* stderr -> syslog */
		    if(fp) fwrite(bufcopy, 1, in, fp);
		    break;
	    } /* end switch() */
	}   /* end if(FD_ISSET (sockfd, &rmask)) */
	pseofdone++;
	sleep(1);
    } /* end while() for getting pages_after */
    close (sockfd);


    if(pagesbefore < pageslast)
	pagesbefore = pageslast;    /* could not get pages before from printer */
    pages = pagesafter - pagesbefore;
    if(pages < 0) pages = 0;

    time(&currtime);
    strcpy(timestr, ctime(&currtime));
    timestr[24] = '\0';
    fprintf(stderr, "%s tcp-lp: %s %s@%s - pages: %d\n",
		timestr+4, printername, user, ohost, pages);
    if(fp) {
	fprintf(fp, "\n---\nTime: %s\n", timestr);
	fclose(fp);
    }

    /* write page accounting info to file specified in "af" field of printcap in NetInfo. */
    if((fp=fopen(acctfile, "a")) != NULL) {
	    /* Added extra date and time info., which seems OK with 'pac' command. */
	    /*     3.0 hostname:user    ; Jan 22 12:09:18 1996 */
	    fprintf(fp,"%7.1f %s:%s	; %s\n", (float)pages, ohost, user, timestr+4);
	    /* If the above causes problems, use the standard format below. */
	    /* fprintf(fp,"%7.1f %s:%s\n", (float)pages, ohost, user); */
	    fclose(fp);
    }


    if(significant_error) {
	sprintf(buf, "exec /usr/ucb/Mail -s \"Printing Error\" %s < %s", user, tempfile);
	system(buf);
    }
    if (do_exit)
	exit (do_exit);
}

plp_signal_t
kill_job (int signal)	 /* the current job has been lprm'ed */
{
    if (sockfd > 0) {
	/* send an interrupt character to stop the current job */
        (void) write (sockfd, "\003", 1);
	(void) close (sockfd);
    }
    /* exit with a success code; we don't want the queue to stop */
    exit (LP_SUCCESS);
}


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

extern int optind;
extern char *optarg;
int c;
FILE *fpin, *fpout;
char configpath[1025];


	while ((c = getopt(argc, argv, "f:h:i:l:n:p:w:")) != EOF)
	    switch (c) {
		case 'f':
			break;
		case 'h':
			ohost = optarg;
			break;
		case 'i':
			break;
		case 'l':
			break;
		case 'n':
			user = optarg;
			break;
		case 'p':
			printername = optarg;
			break;
		case 'w':
		default:
			break;
	    }

    if(optind < argc)
		acctfile=argv[optind];

    (void) signal (SIGINT, kill_job);

    strcpy(Host, DEF_PRINTER_HOST);
    Port = DEF_PRINTER_PORT;

    /* Get printer configuration info from file */
    sprintf(configpath, "/usr/local/lib/%s.conf", printername);
    if( (fpin = fopen(configpath, "r")) != NULL) {
	fscanf(fpin, "%s %hu", Host, &Port);
	if(Port < 1000) {
	    fprintf(stderr,"Bad config file format in %s.  It must contain:\n", configpath);
	    fprintf(stderr,"printer_hostname port#\nE.g.:\nmyhplj4 9100\n");
	    strcpy(Host, DEF_PRINTER_HOST);
	    Port = DEF_PRINTER_PORT;
	    fprintf(stderr,"Using default: %s %d\n", Host, Port);
	}
	fclose(fpin);
    }
    else {
	fprintf(stderr,"No config file: %s\n", configpath);
	fprintf(stderr,"Using default: %s %d\n", Host, Port);
    }

    /* Get last page count from printed via this server.  This is
	to do rough accounting for jobs printed via different ports.
    */
    sprintf(configpath, "/usr/adm/%s.pages", printername);
    if( (fpin = fopen(configpath, "r")) != NULL) {
	fscanf(fpin, "%d", &pageslast);
	fclose(fpin);
    }

    tcp_pipe();	/* This will do the printing */

    /* Do the following only if page count could be obtained after printing */
    if(pagecountCame) {
	/* Save page count in file for next time */
	if( (fpout = fopen(configpath, "w")) != NULL) {
	    fprintf(fpout, "%d\n", pagesafter);	/* this will be 'pageslast' next time */
	    fclose(fpout);
	}
    
	if(pagesbefore > pageslast) {
	    /* Printing via other ports, e.g., parallel/serial/localtalk and ohter
		Ethernet protocols, occurred.  Do accounting for this as non_unix:unknown.
	     */
	    if((fpout=fopen(acctfile, "a")) != NULL) {
		    fprintf(fpout,"%7.1f other_ports:unknown\n",
			    (float)(pagesbefore-pageslast));
		    fclose(fpout);
	    }
	}
    }
    exit (LP_SUCCESS);
}

These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.