/* * tcpprint.c * * This program reads the standard input and sends its output to * a remote printer. This program was modified from one we found * many years ago, and we no longer recall where we got it. We've * used this software for more than 10 years. * * It was originally written for the Apple LaserWriter and later * modified to interface to generic printer devices such as the HP * LaserJet family. * * Compile-time flags: * * DEBUG causes numerous debug messages to be printed * PS build for PostScript printer like the Apple LaserWriter */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /*----------------------------------------------------------------------- * various exit statuses that we should be using... */ #define EXIT_OK 0 /* no errors */ #define EXIT_REPRINT 1 /* read or write failure, reprint job */ #define EXIT_ERROR 2 /* printer reported error in job */ #define EXIT_USAGE 3 /* bad argument to this command */ #define EXIT_NOCONNECT 4 /* unable to connect to printer */ #define EXIT_INTERNAL 5 /* internal error (usually select failed) */ #define BUFSIZE 1015 #define JETDIRECT 9100 /* TCP port on HP LaserJet Si MX */ #define RESET_TIMEOUT 10 /* seconds to wait on reset before timing out */ #define MAX_RETRIES 100 /* number of times we'll retry initial reset */ #ifdef PS #define CTRLD '\004' #endif /* PS */ #define TRUE 1 #define FALSE 0 #define UNKNOWN_ERROR "Unknown error" static int exit_status = EXIT_OK; static int make_connection(const char *, int); static void catcher(int); static void eprintf(const char *, ... ); static char *Prog_name = 0; int Sleep_time = 3, Port_number = JETDIRECT, Debug = 0, Timeout_secs = 0; char *Host_name = 0; /* * This utility is invoked with the host name or IP address of an NCD * network display station as its only argument. It will take characters * from STDIN (assumed to be PostScript) and write them to the serial port * on the specified NCD network display station. The utility will also * monitor status from the printer and report any information returned * by the printer to STDERR. */ static void print_job (int input, int printer); int main(int argc, char **argv) { int sfd, c; Prog_name = argv[0]; /*--------------------------------------------------------------- * first process the command line */ #define OPTS "Dp:s:T:" #define USAGE "[-D] [-p port] [-s sleep] [-T secs] hostname" while ( (c = getopt(argc, argv, OPTS)) != EOF ) { switch (c) { case 'T': /* timeout amount */ Timeout_secs = atoi(optarg); break; case 'D': /* enable debug */ Debug++; break; case 's': /* specify sleep time */ Sleep_time = atoi(optarg); break; case 'p': /* specify TCP/IP port number */ Port_number = atoi(optarg); break; default: fprintf(stderr, "usage: %s %s\n", argv[0], USAGE); exit(EXIT_USAGE); } } /*--------------------------------------------------------------- * now process the non-option parameters. We expect to find a * host name only (no filenames!), so exit with error if we have * too many or too few. Also exit of course if we don't have any * host name! */ if (optind < argc) Host_name = argv[optind++]; if (optind < argc) { fprintf(stderr, "too many arguments to %s\n\n", argv[0]); fprintf(stderr, "usage: %s %s\n", argv[0], USAGE); exit(EXIT_USAGE); } if (Host_name == 0) { fprintf(stderr, "Missing host name!\n"); fprintf(stderr, "usage: %s %s\n", argv[0], USAGE); exit(EXIT_USAGE); } if (Port_number <= 0) { fprintf(stderr, "bogus port number: %d\n", Port_number); exit(EXIT_USAGE); } /*--------------------------------------------------------------- * now we should have a host and a port, so set up the execution * environment for this. First catch a load of signals and then * make the connection to the printer. */ signal(SIGPIPE, catcher); signal(SIGINT, catcher); signal(SIGTERM, catcher); while ( (sfd = make_connection(Host_name, Port_number)) < 0) { eprintf("(sleeping on initial connection)\n"); sleep(Sleep_time); } #ifdef DEBUG if (Debug) eprintf("got connection, trying to reset printer\n"); #endif /* DEBUG */ #ifdef PS /* reset the printer */ reset_printer (sfd); #ifdef DEBUG if (Debug) eprintf("initial printer reset worked, sending job\n"); #endif /* DEBUG */ #endif /* PS */ /* send the data to the printer and wait for completion (if possible) */ print_job (0, sfd); if (Debug) eprintf("job done\n"); /* clean up */ exit (exit_status); } #ifdef PS /* * This function attempts to write an EOF (^D) to the LaserWriter * and then waits for the ^D to be echoed by the printer. It will * only try to read the echoed ^D MAX_RETRIES times. Each time it * cannot read from the printer it will wait RESET_TIMEOUT seconds * before attempting another read. If it cannot complete it's task * it causes the utility to exit with status FAILED_REQUEUE. This * is intended to tell the BSD spooler to requeue the request even * though it could not be printed. */ reset_printer (fd) int fd; { char buf[BUFSIZE]; int len; int err; fd_set rfds; struct timeval timeout; int retry_count = 0; /* As an aside here: With the way the NCD RS-232 daemon works * we will almost always get to the point where we can send the * ^D. If the NCD network display station is not set up to * use the serial port for printing, the NCD network display * station side will close the network connection. This will * cause the read of the echoed ^D to fail and the utility * will exit. The reason the NCD network display station doesn't * just refuse the connection is one of performance. Quite simply, * it's just a lot cheaper to let the connection be established * and then have the NCD side immediately close it. */ /* write ^D to printer */ buf[0] = CTRLD; errno = 0; err = write (fd, buf, 1); if (err != 1) { eprintf ("Write of reset EOF failed [%s]\n", strerror(errno)); exit (EXIT_REPRINT); } /* establish timeout values */ timeout.tv_sec = RESET_TIMEOUT; timeout.tv_usec = 0; /* we'll loop until the echoed ^D is read at which time we return */ while (TRUE) { /* see if read on socket will work */ FD_ZERO (&rfds); FD_SET (fd, &rfds); if ((err = select(FD_SETSIZE, &rfds, NULL, NULL, &timeout)) < 0) { eprintf ("Reset select failed [%s]\n", strerror(errno)); exit (EXIT_INTERNAL); } /* read from network connection, watch for echoed ^D */ if (FD_ISSET (fd, &rfds)) { if ((len = read (fd, buf, sizeof (buf)-1)) > 0) { buf[len] = '\000'; if (index ((char *)buf, CTRLD) != (char *)0) { #ifdef DEBUG eprintf("printer reset\n"); #endif /* DEBUG */ return; #ifdef DEBUG } else { eprintf("reset got \"%s\" while waiting for EOF\n", buf); } #else /* no DEBUG */ } #endif /* DEBUG */ } else { /* read error or EOF */ if (len == 0) eprintf ("Reset read returned EOF.\n"); else eprintf ("Reset read failed [%s]\n", strerror(errno)); exit (EXIT_REPRINT); } } else { /* timeout */ ++retry_count; if (retry_count < MAX_RETRIES) { eprintf ("Reset of printer failed -- timed out at retry %d.\n", retry_count); } else { eprintf ("Reset of printer failed -- too many retries.\n"); exit (EXIT_REPRINT); } } } } #endif /* PS */ /* * * */ static void print_job (int input, int printer) { int no_eof_yet = TRUE, err, len, written; char buf[BUFSIZE]; struct timeval timeout; /* establish timeout values */ timeout.tv_sec = RESET_TIMEOUT; timeout.tv_usec = 0; /* make sure we don't get stuck in a write to the socket */ err = fcntl (printer, F_SETFL, O_NDELAY); /* we'll copy bytes from the input to the printer until we get * an EOF on the input side. */ while (no_eof_yet) { fd_set rfds; fd_set wfds; /* see if we can read */ FD_ZERO (&rfds); FD_SET (input, &rfds); if ((err = select(FD_SETSIZE, &rfds, NULL, NULL, &timeout)) < 0) { eprintf ("Read select failed [%s]\n", strerror(errno)); exit (EXIT_INTERNAL); } /* We must be able to read input. Otherwise we'll delay and * go back to the select. */ if (FD_ISSET (input, &rfds)) { if ((len = read (input, buf, sizeof (buf))) <= 0) { /* EOF or error - manufacture ^D for the printer */ no_eof_yet = FALSE; #ifdef PS buf[0] = CTRLD; len = 1; #ifdef DEBUG eprintf("manufactured EOF for printer\n"); #endif /* DEBUG */ #endif /* PS */ } #ifdef DEBUG if (Debug) eprintf("read %d chars from stdin\n", len); #endif /* DEBUG */ /* send read data to printer - watch out for partial writes */ written = 0; while (written < len) { /* To make sure we don't spin on non-blocking write, let's * select first ... */ FD_ZERO (&wfds); FD_SET (printer, &wfds); if ((err = select(FD_SETSIZE, NULL, &wfds, NULL, &timeout)) < 0) { eprintf ("Write select failed [%s]\n", strerror(errno)); exit (EXIT_INTERNAL); } /* make sure we can write something */ if (FD_ISSET (printer, &wfds)) { err = write (printer, buf+written, len-written); #ifdef DEBUG if (Debug) eprintf("write to printer %d chars from %d\n", err, written); #endif /* DEBUG */ /* this isn't really an error ... */ if (err == -1 && errno == EWOULDBLOCK) { err = 0; #ifdef DEBUG eprintf("write to printer would block\n"); #endif /* DEBUG */ } else if (err < 0) { eprintf ("Write to printer failed [%s]\n", strerror(errno)); exit (EXIT_REPRINT); } /* update # bytes written and check printer status */ written += err; #ifdef PS err = check_printer_for_errors (printer, 0); } else /* can't write right now ... */ /* check printer status - allow some delay */ err = check_printer_for_errors (printer, RESET_TIMEOUT); /* we shouldn't see ^D from printer yet */ if (no_eof_yet && (err < 0)) { eprintf ("Spurious EOF from printer.\n"); } #else /* not PS */ } /* can't write right now ... */ #endif /* PS */ } } } #ifdef PS /* watch for final ^D echo */ while (check_printer_for_errors (printer, RESET_TIMEOUT*2) >= 0) ; #endif /* PS */ } #ifdef PS /* * This function reads text back from the LaserWriter and parses it * into lines. It then emits these lines on the standard error. */ int prtbufi = 0; char prtbuf[BUFSIZE]; int check_printer_for_errors (fd, delay) int fd; int delay; { int state; char *left; char *right; int err; int len; int retval = 0; fd_set rfds; struct timeval timeout; #ifdef DEBUG eprintf("check printer with delay %d\n", delay); #endif /* DEBUG */ /* get set for the select - timeout is specified to this function */ FD_ZERO (&rfds); FD_SET (fd, &rfds); timeout.tv_sec = delay; timeout.tv_usec = 0; /* make sure we can read something */ if ((err = select(FD_SETSIZE, &rfds, NULL, NULL, &timeout)) < 0) { eprintf ("Error select failed [%s]\n", strerror(errno)); exit (EXIT_INTERNAL); } /* If we can't read, then return */ if (!FD_ISSET (fd, &rfds)) { #ifdef DEBUG eprintf("no printer status to read\n"); #endif /* DEBUG */ return (retval); } /* if we can read, then let's do it ... */ len = read (fd, prtbuf+prtbufi, sizeof (prtbuf)-prtbufi-1); if (len < 0 && errno == EWOULDBLOCK) return (retval); /* just paranoid about blocking ... */ if (len < 0) { eprintf ("Read from printer failed [%s]\n", strerror(errno));); exit (EXIT_REPRINT); } prtbufi += len; /* terminate the current input buffer and parse away */ prtbuf[prtbufi] = '\000'; left = right = prtbuf; while (*right) { switch (*right) { case CTRLD: /* ^D seen - make sure we return -1 */ retval = -1; #ifdef DEBUG eprintf("EOF from printer\n"); #endif /* DEBUG */ /* drop through and treat ^D as line break */ case '\012': /* LF */ case '\015': /* CR */ /* if we see adjacent line break characters ignore them */ if (left == right) { left = ++right; break; } /* terminate the line string and pass it to the reporter */ *right = '\000'; printer_status (left); left = ++right; break; default: /* just skip over other chars */ right++; } } /* If we've not collected anything to report, just return */ if (left == prtbuf) { #ifdef DEBUG eprintf("no printer status line collected\n"); #endif /* DEBUG */ return (retval); } /* Otherwise copy the partial line back to the beginning of the buffer */ right = left; left = prtbuf; while (*right) *left++ = *right++; prtbufi = (left-prtbuf); return (retval); } /* * Just report the status line to the specified file. */ printer_status (line) char *line; { eprintf ("%s\n", line); if (strncmp (line, "%%[ Error:", 10) == 0) exit_status = EXIT_ERROR; } #endif /* PS */ /* * generate time stamp for logging messages. */ static void stamp (FILE *ofp) { struct tm *tm; long now; now = time (0); tm = localtime (&now); fprintf(ofp, "%02d-%02d-%02d %02d:%02d:%02d: %s: %s: ", tm->tm_year, tm->tm_mon+1, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec, Prog_name, Host_name); } /* * eprintf() * * Do a printf but to the standard error stream */ static void eprintf(const char *format, ... ) { va_list args; stamp(stderr); va_start(args, format); vfprintf(stderr, format, args); va_end(args); } static void catcher(int signo) { eprintf("caught signal %d, exiting\n", signo); exit(1); } /* * make_connection() * * Given the name of a device and a TCP port number, open a socket * and connect to it, returning the socket descriptor. If we are * not able to make this connection because the printer is busy, just * return a -1 so the calling routine knows to sleep a bit and try * again. Otherwise exit with an appropriate error message so we * know that things are wrong. */ static int make_connection(const char *hostname, int portnum) { int sfd; struct sockaddr_in inaddr; struct hostent *hp; /*--------------------------------------------------------------- * try to connect to a socket, sleeping if it doesn't work. */ if ( (sfd = socket(AF_INET, SOCK_STREAM, 0)) < 0 ) { eprintf("Can't open socket [%s]\n", strerror(errno)); return -1; } if (Debug) eprintf("socket is made: sfd=%d\n", sfd); inaddr.sin_family = AF_INET; inaddr.sin_port = htons (portnum); if ((inaddr.sin_addr.s_addr = inet_addr(hostname)) == (unsigned long)-1) { if ((hp = gethostbyname (hostname)) == NULL || hp->h_addrtype != AF_INET) { eprintf("Can't resolve name %s.\n", hostname); exit (EXIT_NOCONNECT); } memcpy(&inaddr.sin_addr.s_addr, hp->h_addr, hp->h_length); } /*--------------------------------------------------------------- * now do the actual connection to the printer! */ if (Debug) { eprintf("socket is made: sfd=%d\n", sfd); eprintf("Trying connect(%d, %s.%d, %d)...\n", sfd, hostname, inaddr.sin_port, sizeof(inaddr)); } if (connect(sfd, (struct sockaddr *)&inaddr, sizeof(inaddr)) < 0) { close(sfd); if (errno == ECONNREFUSED) { if (Debug) eprintf("connect: %s\n", strerror(errno)); return -1; } eprintf("connect failed [%s] -- exiting \n", strerror(errno)); eprintf("***failure: exiting\n"); exit(EXIT_NOCONNECT); } if (Debug) eprintf("returning sfd=%d\n", sfd); return sfd; }