Subversion Repositories planix.SVN

Rev

Blame | Last modification | View Log | RSS feed

#define _BSD_EXTENSION
#define _NET_EXTENSION
#define _POSIX_SOURCE

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <ctype.h>
#include <signal.h>
#include <errno.h>
#include <select.h>
#include <sys/types.h>
#include <sys/time.h>
#include <sys/socket.h>
#include <sys/wait.h>

extern  int dial_debug;
extern  int dial(char*, char*, char*, int*);


/* debug = 0 for no debugging */
/* debug = 1 for readprinter debugging */
/* debug = 2 for sendprinter debugging */
/* debug = 3 for full debugging, its hard to read the messages */

int debug = 0;
#define READTIMEOUT     300
#define RCVSELTIMEOUT   30
#define SNDSELTIMEOUT   300

void
rdtmout(void) {
        fprintf(stderr, "read timeout occurred, check printer\n");
}

int
getline(int fd, char *buf, int len) {
        char *bp, c;
        int i = 0, n;

        bp = buf;
        while (alarm(READTIMEOUT),(n=read(fd, bp, 1)) == 1) {
                alarm(0);
                if (*bp == '\r') continue;
                i += n;

                c = *bp++;
                if (c == '\n' || c == '\004' || i >= len-1)
                        break;
        }
        alarm(0);
        if (n < 0)
                return(n);
        *bp = '\0';
        return(i);
}

typedef struct {
        char    *state;                 /* printer's current status */
        int     val;                    /* value returned by getstatus() */
} Status;

/* printer states */
#define INITIALIZING    0
#define IDLE            1
#define BUSY            2
#define WAITING         3
#define PRINTING        4
#define PRINTERERROR    5
#define ERROR           6
#define FLUSHING        7
#define UNKNOWN         8

/* protocol requests and program states */
#define START   'S'
unsigned char Start[] = { START };
#define ID_LE           'L'
unsigned char Id_le[] = { ID_LE };
#define REQ_STAT        'T'
unsigned char Req_stat[] = { REQ_STAT };
#define SEND_DATA       'D'
unsigned char Send_data[] = { SEND_DATA };
#define SENT_DATA       'A'
unsigned char Sent_data[] = { SENT_DATA };
#define WAIT_FOR_EOJ    'W'
unsigned char Wait_for_eoj[] = { WAIT_FOR_EOJ };
#define END_OF_JOB      'E'
unsigned char End_of_job[] = { END_OF_JOB };
#define FATAL_ERROR     'F'
unsigned char Fatal_error[] = { FATAL_ERROR };
#define WAIT_FOR_IDLE   'I'
unsigned char Wait_for_idle[] = { WAIT_FOR_IDLE };
#define OVER_AND_OUT    'O'
unsigned char Over_and_out[] = { OVER_AND_OUT };

Status  statuslist[] = {
        "initializing", INITIALIZING,
        "idle", IDLE,
        "busy", BUSY,
        "waiting", WAITING,
        "printing", PRINTING,
        "printererror", PRINTERERROR,
        "Error", ERROR,
        "flushing", FLUSHING,
        NULL, UNKNOWN
};


/* find returns a pointer to the location of string str2 in string str1,
 * if it exists.  Otherwise, it points to the end of str1.
 */
char *
find(char *str1, char *str2) {
        char *s1, *s2;

        for (; *str1!='\0'; str1++) {
                for (s1=str1,s2=str2; *s2!='\0'&&*s1==*s2; s1++,s2++) ;
                if ( *s2 == '\0' )
                        break;
        }

        return(str1);
}

#define MESGSIZE        16384
int blocksize = 1920;           /* 19200/10, with 1 sec delay between transfers
                                 * this keeps the queues from building up.
                                 */
char    mesg[MESGSIZE];                 /* exactly what came back on ttyi */

int
parsmesg(char *buf) {
        static char sbuf[MESGSIZE];
        char    *s;             /* start of printer messsage in mesg[] */
        char    *e;             /* end of printer message in mesg[] */
        char    *key, *val;     /* keyword/value strings in sbuf[] */
        char    *p;             /* for converting to lower case etc. */
        int     i;              /* where *key was found in statuslist[] */

        if (*(s=find(buf, "%[ "))!='\0' && *(e=find(s, " ]%"))!='\0') {
                strcpy(sbuf, s+3);      /* don't change mesg[] */
                sbuf[e-(s+3)] = '\0';   /* ignore the trailing " ]%" */

                for (key=strtok(sbuf, " :"); key != NULL; key=strtok(NULL, " :"))  {
                        if (strcmp(key, "Error") == 0)
                                return(ERROR);
                        if ((val=strtok(NULL, ";")) != NULL && strcmp(key, "status") == 0)
                                key = val;

                        for (; *key == ' '; key++) ;    /* skip any leading spaces */
                        for (p = key; *p; p++)          /* convert to lower case */
                                if (*p == ':')  {
                                        *p = '\0';
                                        break;
                                } else if (isupper(*p)) *p = tolower(*p);

                        for (i=0; statuslist[i].state != NULL; i++) {
                                if (strcmp(statuslist[i].state, key) == 0)
                                        return(statuslist[i].val);
                        }
                }
        }
        return(UNKNOWN);
}

char buf[MESGSIZE];
fd_set readfds, writefds, exceptfds;
struct timeval rcvtimeout = { RCVSELTIMEOUT, 0 };
struct timeval sndtimeout = { SNDSELTIMEOUT, 0 };

int
readprinter(int printerfd, int pipefd)
{
        unsigned char proto;
        int progstate = START;
        int print_wait_msg = 0;
        int tocount = 0;
        int c, printstat, lastprintstat, n, nfds;


        nfds = ((pipefd>printerfd)?pipefd:printerfd) + 1;
        printstat = 0;
        signal(SIGALRM, rdtmout);
        do {

reselect:
                /* ask sending process to request printer status */
                if (write(pipefd, Req_stat, 1) != 1) {
                        fprintf(stderr, "request status failed\n");
                        progstate = FATAL_ERROR;
                        continue;
                }
                FD_ZERO(&readfds);      /* lets be anal */
                FD_SET(printerfd, &readfds);
                FD_SET(pipefd, &readfds);
                FD_ZERO(&exceptfds);
                FD_SET(printerfd, &exceptfds);
                FD_SET(pipefd, &exceptfds);
                n = select(nfds, &readfds, (fd_set *)0, &exceptfds, &rcvtimeout);
                if (debug&0x1) fprintf(stderr, "readprinter select returned %d\n", n);
                if (n == 0) {
                        /* a timeout occurred */
                        if (++tocount > 4) {
                                fprintf(stderr, "printer appears to be offline.\nHP4m printers may be out of paper.\n");
                                tocount = 0;
                        }
                        goto reselect;
                }
                if (n > 0 && FD_ISSET(printerfd, &exceptfds)) {
                        /* printer problem */
                        fprintf(stderr, "printer exception\n");
                        if (write(pipefd, Fatal_error, 1) != 1) {
                                fprintf(stderr, "'fatal error' write to pipe failed\n");
                        }
                        progstate = FATAL_ERROR;
                        continue;
                }
                if (n > 0 && FD_ISSET(pipefd, &exceptfds)) {
                        /* pipe problem */
                        fprintf(stderr, "pipe exception\n");
                        progstate = FATAL_ERROR;
                        continue;
                }
                if (n > 0 && FD_ISSET(pipefd, &readfds)) {
                        /* protocol pipe wants to be read */
                        if (debug&0x1) fprintf(stderr, "pipe wants to be read\n");
                        if (read(pipefd, &proto, 1) != 1) {
                                fprintf(stderr, "read protocol pipe failed\n");
                                progstate = FATAL_ERROR;
                                continue;
                        }
                        if (debug&0x1) fprintf(stderr, "readprinter: proto=%c\n", proto);
                        /* change state? */
                        switch (proto) {
                        case SENT_DATA:
                                break;
                        case WAIT_FOR_EOJ:
                                if (!print_wait_msg) {
                                        print_wait_msg = 1;
                                        fprintf(stderr, "waiting for end of job\n");
                                }
                                progstate = proto;
                                break;
                        default:
                                fprintf(stderr, "received unknown protocol request <%c> from sendfile\n", proto);
                                break;
                        }
                        n--;
                }
                if (n > 0 && FD_ISSET(printerfd, &readfds)) {
                        /* printer wants to be read */
                        if (debug&0x1) fprintf(stderr, "printer wants to be read\n");
                        if ((c=getline(printerfd, buf, MESGSIZE)) < 0) {
                                fprintf(stderr, "read printer failed\n");
                                progstate = FATAL_ERROR;
                                continue;
                        }
                        if (debug&0x1) fprintf(stderr, "%s\n", buf);
                        if (c==1 && *buf == '\004') {
                                if (progstate == WAIT_FOR_EOJ) {
                                        if (debug&0x1) fprintf(stderr, "progstate=%c, ", progstate);
                                        fprintf(stderr, "%%[ status: endofjob ]%%\n");
/*                                      progstate = WAIT_FOR_IDLE; */
                                        progstate = OVER_AND_OUT;
                                        if (write(pipefd, Over_and_out, 1) != 1) {
                                                fprintf(stderr, "'fatal error' write to pipe failed\n");
                                        }
                                        continue;
                                } else {
                                        if (printstat == ERROR) {
                                                progstate = FATAL_ERROR;
                                                continue;
                                        }
                                        if (progstate != START && progstate != WAIT_FOR_IDLE)
                                                fprintf(stderr, "warning: EOF received; program status is '%c'\n", progstate);

                                }
                                continue;
                        }

                        /* figure out if it was a status line */
                        lastprintstat = printstat;
                        printstat = parsmesg(buf);
                        if (printstat == UNKNOWN || printstat == ERROR
                            || lastprintstat != printstat) {
                                /* print whatever it is that was read */
                                fprintf(stderr, buf);
                                fflush(stderr);
                                if (printstat == UNKNOWN) {
                                        printstat = lastprintstat;
                                        continue;
                                }
                        }
                        switch (printstat) {
                        case UNKNOWN:
                                continue;       /* shouldn't get here */
                        case FLUSHING:
                        case ERROR:
                                progstate = FATAL_ERROR;
                                /* ask sending process to die */
                                if (write(pipefd, Fatal_error, 1) != 1) {
                                        fprintf(stderr, "Fatal_error mesg write to pipe failed\n");
                                }
                                continue;
                        case INITIALIZING:
                        case PRINTERERROR:
                                sleep(1);
                                break;
                        case IDLE:
                                if (progstate == WAIT_FOR_IDLE) {
                                        progstate = OVER_AND_OUT;
                                        if (write(pipefd, Over_and_out, 1) != 1) {
                                                fprintf(stderr, "'fatal error' write to pipe failed\n");
                                        }
                                        continue;
                                }
                                progstate = SEND_DATA;

                                goto dowait;
                        case BUSY:
                        case WAITING:
                        default:
                                sleep(1);
dowait:
                                switch (progstate) {
                                case WAIT_FOR_IDLE:
                                case WAIT_FOR_EOJ:
                                case START:
                                        sleep(5);
                                        break;

                                case SEND_DATA:
                                        if (write(pipefd, Send_data, 1) != 1) {
                                                fprintf(stderr, "send data write to pipe failed\n");
                                                progstate = FATAL_ERROR;
                                                continue;
                                        }
                                        break;
                                default:
                                        fprintf(stderr, "unexpected program state %c\n", progstate);
                                        exit(1);
                                }
                                break;
                        }
                        n--;
                }
                if (n > 0) {
                        fprintf(stderr, "more fds selected than requested!\n");
                        exit(1);
                };
        } while ((progstate != FATAL_ERROR) && (progstate != OVER_AND_OUT));

        if (progstate == FATAL_ERROR)
                return(1);
        else
                return(0);
}

int
sendfile(int infd, int printerfd, int pipefd)
{
        unsigned char proto;
        int progstate = START;
        int i, n, nfds;
        int bytesread,  bytesent = 0;

        nfds = ((pipefd>printerfd)?pipefd:printerfd) + 1;

        if (write(printerfd, "\004", 1)!=1) {
                perror("sendfile:write:");
                progstate = FATAL_ERROR;
        }
        do {
                FD_ZERO(&readfds);      /* lets be anal */
                FD_SET(pipefd, &readfds);
                n = select(nfds, &readfds, (fd_set *)0, (fd_set *)0, &sndtimeout);
                if (debug&02) fprintf(stderr, "sendfile select returned %d\n", n);
                if (n > 0 && FD_ISSET(pipefd, &readfds)) {
                        /* protocol pipe wants to be read */
                        if (read(pipefd, &proto, 1) != 1) {
                                fprintf(stderr, "read protocol pipe failed\n");
                                return(1);
                        }
                        /* change state? */
                        if (debug&02) fprintf(stderr, "sendfile command - <%c>\n", proto);
                        switch (proto) {
                        case OVER_AND_OUT:
                        case END_OF_JOB:
                                progstate = proto;
                                break;
                        case SEND_DATA:
                                bytesread = 0;
                                do {
                                        i = read(infd, &buf[bytesread], blocksize-bytesread);
                                        if (debug&02) fprintf(stderr, "read %d bytes\n", i);
                                        if (i > 0)
                                                bytesread += i;
                                } while((i > 0) && (bytesread < blocksize));
                                if (i < 0) {
                                        fprintf(stderr, "input file read error\n");
                                        progstate = FATAL_ERROR;
                                        break;  /* from switch */
                                }
                                if (bytesread > 0) {
                                        if (debug&02) fprintf(stderr, "writing %d bytes\n", bytesread);
                                        if (write(printerfd, buf, bytesread)!=bytesread) {
                                                perror("sendfile:write:");
                                                progstate = FATAL_ERROR;
                                        } else if (write(pipefd, Sent_data, 1)!=1) {
                                                perror("sendfile:write:");
                                                progstate = FATAL_ERROR;
                                        } else {
                                                bytesent += bytesread;
                                        }
                                        fprintf(stderr, "%d sent\n", bytesent);
                                        fflush(stderr);

                                /* we have reached the end of the input file */
                                }
                                if (i == 0) {
                                        if (progstate != WAIT_FOR_EOJ) {
                                                if (write(printerfd, "\004", 1)!=1) {
                                                        perror("sendfile:write:");
                                                        progstate = FATAL_ERROR;
                                                } else if (write(pipefd, Wait_for_eoj, 1)!=1) {
                                                        perror("sendfile:write:");
                                                        progstate = FATAL_ERROR;
                                                } else {
                                                        progstate = WAIT_FOR_EOJ;
                                                }
                                        }
                                }
                                break;
                        case REQ_STAT:
                                if (write(printerfd, "\024", 1)!=1) {
                                        fprintf(stderr, "write to printer failed\n");
                                        progstate = FATAL_ERROR;
                                }
                                if (debug&02) fprintf(stderr, "^T");
                                break;
                        case FATAL_ERROR:
                                progstate = FATAL_ERROR;
                        }
                } else if (n < 0) {
                        perror("sendfile:select:");
                        progstate = FATAL_ERROR;
                } else if (n == 0) {
                        sleep(1);
                        fprintf(stderr, "sendfile timeout\n");
                        progstate = FATAL_ERROR;
                }
        } while ((progstate != FATAL_ERROR) && (progstate != OVER_AND_OUT));
        if (write(printerfd, "\004", 1)!=1) {
                perror("sendfile:write:");
                progstate = FATAL_ERROR;
        }

        if (debug&02) fprintf(stderr, "%d bytes sent\n", bytesent);
        if (progstate == FATAL_ERROR)
                return(1);
        else
                return(0);
}

void main(int argc, char *argv[]) {
        int c, usgflg=0, infd, printerfd;
        int cpid, sprv;
        int pipefd[2];
        char *dialstr;
        unsigned long rprv;

        dialstr = 0;

        while ((c = getopt(argc, argv, "b:d:")) != -1)
                switch (c) {
                case 'b':
                        blocksize = atoi(optarg)/10;
                        if (blocksize > MESGSIZE || blocksize < 1)
                                blocksize = MESGSIZE;
                        break;
                case 'd':
                        debug = atoi(optarg);
                        dial_debug = debug;
                        break;
                case '?':
                        fprintf(stderr, "unknown option %c\n", c);
                        usgflg++;
                        break;
                }
        if (optind < argc)
                dialstr = argv[optind++];
        else
                usgflg++;
        if (usgflg) {
                fprintf(stderr, "usage: %s [-b baudrate] net!host!service [infile]\n",
                        argv[0]);
                exit (2);
        }
        if (optind < argc) {
                infd = open(argv[optind], 0);
                if (infd < 0) {
                        fprintf(stderr, "cannot open %s\n", argv[optind]);
                        exit(1);
                }
                optind++;
        } else
                infd = 0;

        if (debug & 02)
                fprintf(stderr, "blocksize=%d\n", blocksize);
        if (debug)
                fprintf(stderr, "dialing address=%s\n", dialstr);
        printerfd = dial(dialstr, 0, 0, 0);
        if (printerfd < 0)
                exit(1);

        fprintf(stderr, "printer startup\n");

        if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, pipefd) < 0) {
                perror("socketpair");
                exit(1);
        }
        switch(cpid = fork()){
        case -1:
                perror("fork error");
                exit(1);
        case 0:                          /* child - to printer */
                close(pipefd[1]);
                sprv = sendfile(infd, printerfd, pipefd[0]);
                if (debug)
                        fprintf(stderr, "to remote - exiting\n");
                exit(sprv);
        default:                        /* parent - from printer */
                close(pipefd[0]);
                rprv = readprinter(printerfd, pipefd[1]);
                if (debug)
                        fprintf(stderr, "from remote - exiting\n");
                while(wait(&sprv) != cpid)
                        ;
                exit(rprv|sprv);
        }
}