/* * Copyright (c) 1997, 1998, 1999, 2000, 2001 * Gesellschaft fuer wissenschaftliche * Datenverarbeitung mbH Goettingen. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by the Gesellschaft * fuer wissenschaftliche Datenverarbeitung mbH Goettingen. * 4. The name of the Gesellschaft fuer wissenschaftliche Datenverarbeitung * mbH Goettingen may not be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE GESELLSCHAFT FUER WISSENSCHAFTLICHE * DATENVERARBEITUNG MBH GOETTINGEN ``AS IS'' AND ANY EXPRESS OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE GESELLSCHAFT FUER WISSENSCHAFTLICHE DATENVERARBEITUNG * MBH GOETTINGEN BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #ifndef lint static const char copyright[] = "@(#) Copyright (c) 1997, 1998, 1999, 2000, 2001\n\ Gesellschaft fuer wissenschaftliche\n\ Datenverarbeitung mbH Goettingen. All rights reserved.\n"; static const char rcsid[] = "$Id: rprint.c,v 3.2 2001/07/19 13:58:48 kheuer Exp $"; #endif /* not lint */ /* * Open TCP/IP connection to a network printer, send and receive data. * * The code has been developed for use on hardware driven by the FreeBSD * operating system. Portability to other operating systems has not been * an important design goal. Nevertheless, ports to operating systems * providing the typical BSD API may be possible without too much effort. * * Compile, link and install commands: * * cc -O rprint.c -o rprint -s * * 1997/01/02 initial, very simple version * 1997/01/17 handling of incomplete data transfer, status inquiry etc. * 1997/03/07 no status inquiry while sending data, minor improvements * 1997/05/13 important bug fix and read buffer overflow check * 1997/08/13 send/receive via one or two sockets, command line options * 1997/08/29 minor bug fix * 1997/09/10 status inquiry separated from waiting for idle status * 1997/09/11 command line option to change block size * 1998/08/09 minor big fix * 1999/07/06 moved sleep period while trying to read page counter * 1999/11/16 write to log on interrupt * 2000/01/14 changed type of variable intr to enhance reliability * 2000/01/21 new option for eof ack and improved interrupt handling * 2000/04/27 license text included * 2000/09/19 data transmit rate improved * 2001/06/19 RCS check in */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define NBLOCK 16 /* block report frequency */ #define STRLEN 256 /* default string length */ /* possible printer status */ enum prt_stat { busy, error, idle, initializing, offline, printing, waiting, warming, unknown }; /* possible i/o types */ enum io_types { noblock, block }; static char *buf; /* data buffer */ static char *cpy; /* data buffer copy */ static char *eofchar = "\004"; /* end of file string */ /* interrupt string */ static char *intchar = "\003\n"; static char *pgm; /* program name */ static char fname[STRLEN]; /* file name */ static char prt[STRLEN]; /* printer name */ /* status report string */ static char *statchar = "\024\n"; static float xm_secs = 0.1; /* time to wait before retrying transmit */ static int buf_size = 4096; /* buffer and block size */ static int con_ret = 60; /* # of retries to establish connection */ static int con_secs = 1; /* secs to wait before retrying above */ static int debug = 0; /* debug data transfer flag */ static int file = 0; /* input file descriptor */ static int eofack = 0; /* end-of-file acknowledgement flag */ static int idl_natt = 0; /* # of attemps to inquire status */ static int idl_secs = 1; /* secs to wait before retrying above */ static int literal = 0; /* literal data transfer flag */ static int pc_natt = 0; /* # of attempts to inquire page counter */ static int pc_secs = 20; /* secs to wait before page counter inquiry */ static int port_rcv = 9100; /* server port receiving data */ static int port_snd = 9100; /* server port sending data */ static int slp_secs = 5; /* # of secs to wait for delayed reply */ static int sock_rcv; /* socket fd for receiving data */ static int sock_snd; /* socket fd for sending data */ static int stat_iq = 0; /* status inquiry */ static int timeout = 1800; /* secs to wait before giving up */ static int warntime = 300; /* write warning to syslog each # of secs */ volatile sig_atomic_t intr = 0; /* interrupt flag */ int comlin ( int, char ** ); /* process command line */ int con2prt ( void ); /* connect to printer */ void intrhdl ( int ); /* SIGINT and SIGTERM handler */ int istatus ( char * ); /* inquire printer status */ int message ( char *, int * ); /* analyze printer messages */ void pipehdl ( int ); /* SIGPIPE handler */ int process ( void ); /* main data processing */ int rcvdata ( int, char * ); /* receive data from printer */ int reqpcnt ( void ); /* require page counter reply */ int reqstat ( void ); /* require status reply */ int snddata ( char *, int ); /* send data to printer */ int statprt ( int ); /* print printer status on stderr */ void usage ( void ); /* print usage note on stderr */ int comlin ( int argc, char *argv[] ) { int ch; int nf; opterr = 0; while ((ch = getopt(argc, argv, "a:b:deEf:i:lp:r:s:t:uw:x:")) != EOF) switch(ch) { case 'a': if (sscanf(optarg, "%d,%d", &pc_natt, &pc_secs) != 2) { usage(); return 1; } break; case 'b': if (sscanf(optarg, "%d", &buf_size) != 1) { usage(); return 1; } break; case 'd': debug = 1; break; case 'e': eofack = 1; break; case 'E': eofack = 2; break; case 'f': strncpy(fname, optarg, STRLEN); file = 1; break; case 'i': if (sscanf(optarg, "%d,%d", &idl_natt, &idl_secs) != 2) { usage(); return 1; } stat_iq = 0; break; case 'l': literal = 1; break; case 'p': nf = sscanf(optarg, "%d,%d", &port_rcv, &port_snd); if (nf == 1) port_snd = port_rcv; else if (nf != 2) { usage(); return 1; } break; case 'r': if (sscanf(optarg, "%d,%d", &con_ret, &con_secs) != 2) { usage(); return 1; } break; case 's': if (sscanf(optarg, "%d", &slp_secs) != 1) { usage(); return 1; } break; case 't': if (sscanf(optarg, "%d", &timeout) != 1) { usage(); return 1; } break; case 'u': idl_natt = 0; idl_secs = 1; stat_iq = 1; break; case 'w': if (sscanf(optarg, "%d", &warntime) != 1) { usage(); return 1; } break; case 'x': if (sscanf(optarg, "%f", &xm_secs) != 1 || xm_secs == 0) { usage(); return 1; } break; case '?': default: usage(); break; } if (argv[optind]) strncpy(prt, argv[optind], STRLEN); else { usage(); return 1; } return 0; } int con2prt ( ) { int nret = 0; unsigned long inadd; struct hostent *host; struct sockaddr_in server_rcv; struct sockaddr_in server_snd; memset(&server_rcv, 0, sizeof server_rcv); if ((inadd = inet_addr(prt)) == INADDR_NONE) { if (!(host = gethostbyname(prt))) { fprintf(stderr, "%s: printer unknown: %s\n", pgm, prt); return 1; } server_rcv.sin_family = host->h_addrtype; memcpy(&server_rcv.sin_addr, host->h_addr, host->h_length); } else { server_rcv.sin_family = AF_INET; server_rcv.sin_addr.s_addr = inadd; } memcpy(&server_snd, &server_rcv, sizeof server_rcv); server_rcv.sin_port = htons(port_rcv); server_snd.sin_port = htons(port_snd); if (file) { if ((file = open(fname, O_RDONLY, 0)) == -1) { fprintf(stderr, "%s: cannot open file: %s\n", pgm, fname); return 2; } } if ((sock_snd = socket(PF_INET, SOCK_STREAM, 0)) < 0) { fprintf(stderr, "%s: cannot open socket\n", pgm); return 2; } while (connect(sock_snd, (struct sockaddr *) &server_rcv, sizeof server_rcv) < 0) { if (nret++ >= con_ret) { fprintf(stderr, "%s: cannot connect to server even after %d retries\n", pgm, nret); return 2; } sleep(con_secs); } if (fcntl(sock_snd, F_SETFL, O_NONBLOCK) == -1) { fprintf(stderr, "%s: cannot change socket file descriptor to non-blocking\n", pgm); return 2; } if (port_rcv == port_snd) { sock_rcv = sock_snd; goto finish; } nret = 0; if ((sock_rcv = socket(PF_INET, SOCK_STREAM, 0)) < 0) { fprintf(stderr, "%s: cannot open socket\n", pgm); return 2; } while (connect(sock_rcv, (struct sockaddr *) &server_snd, sizeof server_snd) < 0) { if (nret++ >= con_ret) { fprintf(stderr, "%s: cannot connect to server even after %d retries\n", pgm, nret); return 2; } sleep(con_secs); } if (fcntl(sock_rcv, F_SETFL, O_NONBLOCK) == -1) { fprintf(stderr, "%s: cannot change socket file descriptor to non-blocking\n", pgm); return 2; } finish: if (debug) fprintf(stderr, "%s: connected to %s, buffer and block size is %d\n" "\tcon_ret = %d\t con_secs = %d\t debug = %d\n" "\tfile = %d\t eofack = %d\t idl_natt = %d\n" "\tidl_secs = %d\t literal = %d\t pc_natt = %d\n" "\tpc_secs = %d\t port_rcv = %d\t port_snd = %d\n" "\tslp_secs = %d\t sock_rcv = %d\t sock_snd = %d\n" "\tstat_iq = %d\t timeout = %d\t warntime = %d\n", "\txm_secs = %f\n\n", pgm, prt, buf_size, con_ret, con_secs, debug, file, eofack, idl_natt, idl_secs, literal, pc_natt, pc_secs, port_rcv, port_snd, slp_secs, sock_rcv, sock_snd, stat_iq, timeout, warntime, xm_secs); return 0; } void intrhdl ( int sig ) { static char msg[STRLEN]; snddata(intchar, strlen(intchar)); sprintf(msg, "%s: SIGINT or SIGTERM received - aborting", pgm); fprintf(stderr, "%s\n", msg); intr = 1; } int istatus ( char *buf ) { int rc; int sec = 0; int stat; int totsec = 0; do { sleep(sec); if (rc = reqstat()) return rc; if (rc = rcvdata(block, buf)) return rc; if (rc = message(buf, &stat)) return rc; statprt(stat); if (stat_iq) break; sec = idl_secs; if (++totsec > idl_natt) { fprintf(stderr, "%s: timeout - printer not idle even after %d secs\n", pgm, --totsec); return 2; } } while (stat != idle); return 0; } int message ( char *buf, int *flag ) { char *arg; char *key; char *pfx; char *pos; static char *dlm = " :;\n"; if (debug) { fputs("-----> begin of printer message <----\n", stderr); fputs(buf, stderr); fputs("-----> end of printer message <------\n", stderr); } if (*buf == *eofchar) { if (flag) (*flag)++; return 0; } strcpy(cpy, buf); pos = cpy; do { if (!(pfx = strtok(pos, dlm))) return 0; if (strcmp(pfx, "%%[") || !(key = strtok(NULL, dlm))) { fprintf(stderr, "%s: strange printer reply: %s\n", pgm, buf); printf("%s", buf); return 0; } if (!strcmp(key, "Flushing")) { printf("%s", buf); return 3; } else if (!strcmp(key, "pagecount")) { if (!(arg = strtok(NULL, dlm))) { fprintf(stderr, "%s: page number missing in printer reply\n", pgm); return 2; } if (flag) *flag = atoi(arg); } else if (!strcmp(key, "status")) { if (!(arg = strtok(NULL, dlm))) { fprintf(stderr, "%s: status argument missing in printer reply\n", pgm); return 2; } if (flag) { if (!strcmp(arg, "busy")) *flag = busy; else if (!strcmp(arg, "idle")) *flag = idle; else if (!strcmp(arg, "initializing")) *flag = initializing; else if (!strcmp(arg, "off-line")) *flag = offline; else if (!strcmp(arg, "PrinterError")) *flag = error; else if (!strcmp(arg, "printing")) *flag = printing; else if (!strcmp(arg, "waiting")) *flag = waiting; else if (!strcmp(arg, "warming")) *flag = warming; else *flag = unknown; } } else { printf("%s", buf); return 0; } strcpy(cpy, buf); } while (pos = strchr(++pos, '\n')); return 0; } void pipehdl ( int sig ) { static char msg[STRLEN]; sprintf(msg, "%s: SIGPIPE received while communicating with printer: %s", pgm, prt); syslog(LOG_ERR, msg); fprintf(stderr, "%s\n", msg); exit(2); } int process ( ) { char *pos; int eofcnt = 0; int nblck = 0; int nbytes; int pc1; int pc2; int pcret = 0; int pctmp; int rc; int rcloop; int stat; if ((idl_natt && (rc = istatus(buf))) || (stat_iq && (rc = istatus(buf)))) return rc; if (pc_natt) { do { if (rc = reqpcnt()) return rc; if (rc = rcvdata(block, buf)) return rc; if (rc = message(buf, &pc1)) return rc; if (rc = reqpcnt()) return rc; if (rc = rcvdata(block, buf)) return rc; if (rc = message(buf, &pctmp)) return rc; } while (pctmp != pc1 && ++pcret < pc_natt); pcret = 0; fprintf(stderr, "%d", pc1); } if ((signal(SIGINT, intrhdl) == (void *) -1) || (signal(SIGPIPE, pipehdl) == (void *) -1) || (signal(SIGTERM, intrhdl) == (void *) -1)) { fprintf(stderr, "%s: could not install signal handler\n"); return 2; } while ((nbytes = read(file, buf, buf_size)) > 0 && !intr) { if (!literal) { for (pos = buf + nbytes; pos >= buf; pos--) if (*pos == *eofchar || *pos == *intchar || *pos == *statchar) *pos = ' '; } if (rc = snddata(buf, nbytes)) break; if (++nblck <= NBLOCK || !(nblck % NBLOCK)) { if (!(nblck % NBLOCK)) fprintf(stderr, "%dx", NBLOCK); putc('.', stderr); } if (rc = rcvdata(noblock, buf)) break; if (*buf) { if (rc = message(buf, NULL)) break; } } if (eofack == 2) { if (rc = snddata(eofchar, strlen(eofchar))) return rc; } if (pc_natt) sleep(pc_secs); else sleep(slp_secs); if (!(rcloop = rc)) { if (!(rcloop = rcvdata(noblock, buf))) { if (*buf) rcloop = message(buf, NULL); } } if (eofack) { if (eofack == 2) { if (rc = rcvdata(noblock, buf)) return rc; } else { if (rc = snddata(eofchar, strlen(eofchar))) return rc; if (rc = rcvdata(block, buf)) return rc; } if (*buf) rc = message(buf, &eofcnt); if (!rc && eofcnt) putc('$', stderr); else putc('!', stderr); } else putc(':', stderr); if ((idl_natt && (rc = istatus(buf))) || (stat_iq && (rc = istatus(buf)))) /* do not return rc */; if (pc_natt) { do { if (rc = reqpcnt()) return rc; if (rc = rcvdata(block, buf)) return rc; if (rc = message(buf, &pc2)) return rc; sleep(pc_secs); if (rc = reqpcnt()) return rc; if (rc = rcvdata(block, buf)) return rc; if (rc = message(buf, &pctmp)) return rc; } while ((pctmp != pc2 || pc2 <= pc1) && ++pcret < pc_natt); fprintf(stderr, "%d\n", pc2); if (pc2 - pc1 > 0) printf("#%1d page(s)\n", pc2 - pc1); else printf("#0 page(s)\n"); } else putc('\n', stderr); if ((signal(SIGINT, SIG_IGN) == (void *) -1) || (signal(SIGTERM, SIG_IGN) == (void *) -1)) { fprintf(stderr, "%s: could not deinstall signal handler\n"); return 2; } close(file); close(sock_snd); if (sock_rcv != sock_snd) close(sock_rcv); return rcloop; } int rcvdata ( int block, char *buf ) { int cnt; int nbytes = 0; int sec = 1; int totsec = 0; static char msg[STRLEN]; if (block) { while ((cnt = read(sock_rcv, buf, buf_size)) < 1) { sleep(sec); if (++totsec >= (timeout * (10 - 9 * intr)) / 10) { fprintf(stderr, "%s: timeout - printer not responding even after %d secs\n", pgm, totsec); return 2; } else if (!(totsec % warntime)) { sprintf(msg, "%s: WARNING - no response from printer %s after %d secs", pgm, prt, totsec); syslog(LOG_WARNING, msg); fprintf(stderr, "%s\n", msg); } } buf[cnt > buf_size - 1 ? buf_size - 1 : cnt] = '\0'; } else { *buf = '\0'; while ((cnt = read(sock_rcv, buf + nbytes, buf_size - nbytes)) > 0) { buf[nbytes += cnt > buf_size - 1 ? buf_size - 1 : nbytes] = '\0'; } } return 0; } int reqpcnt ( ) { static char *pcstring = "(%%[ pagecount: ) print statusdict begin" " pagecount end 20 string cvs print ( ]%%) print flush\n"; return snddata(pcstring, strlen(pcstring)); } int reqstat ( ) { return snddata(statchar, strlen(statchar)); } int snddata ( char *buf, int buflen ) { char *bufptr = buf; int nbytes; int nret = 0; static char msg[STRLEN]; if (debug) { fputs("-----> begin of block to send <------\n", stderr); write(2, buf, buflen); fputs("-----> end of block to send <--------\n", stderr); } while ((nbytes = write(sock_snd, bufptr, buflen)) != buflen) { if (nbytes == -1) { if (nret >= ((int) (timeout / xm_secs) * (10 - 9 * intr)) / 10) { fprintf(stderr, "%s: write failed even after %d retries\n", pgm, nret); return 2; } else if (!(++nret % (int) (warntime / xm_secs))) { sprintf(msg, "%s: WARNING - couldn't write to printer %s for %d secs", pgm, prt, (int) (xm_secs * nret)); syslog(LOG_WARNING, msg); fprintf(stderr, "%s\n", msg); } usleep(1e6 * xm_secs); } else { bufptr += nbytes; buflen -= nbytes; } } return 0; } int statprt ( int stat ) { switch (stat) { case busy: putc('b', stderr); break; case error: putc('e', stderr); break; case idle: putc('i', stderr); break; case initializing: putc('n', stderr); break; case offline: putc('o', stderr); break; case printing: putc('p', stderr); break; case waiting: putc('w', stderr); break; case warming: putc('r', stderr); break; default: putc('u', stderr); break; } return 0; } void usage ( ) { fprintf(stderr, "Usage: %s [] \n" "-a,\t\tdo accounting: # of attempts, secs to wait\n" "-b\t\tbuffer and block size\n" "-d\t\t\tdebug mode\n" "-e\t\t\twait -t secs for end-of-file ack\n" "-E\t\t\twait -a or -s secs for end-of-file ack\n" "-f\t\tinput file\n" "-i,\t\tidle inquiry: # of attempts, secs to wait\n" "-l\t\t\tliteral printing\n" "-p[,]\tserver port(s): <-> or ->,<-\n" "-r,\t\t# of retries to establish connection\n" "-s\t\t# of secs to wait for a reply after sending all data\n" "-t\t\ttimeout interval in secs\n" "-u\t\t\tstatus inquiry before and after sending data\n" "-w\t\twrite warning to syslog each # of secs\n" "-x\t\ttime to wait before retrying transmit (float > 0)\n", pgm); } int main ( int argc, char *argv[] ) { int rc; pgm = argv[0]; if (rc = comlin(argc, argv)) return rc; if (!(buf = malloc(buf_size)) || !(cpy = malloc(buf_size))) { fprintf(stderr, "%s: cannot allocate enough memory\n", pgm); return 1; } if (rc = con2prt()) return rc; return process(); }