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.