#ifdef plan9

#include <u.h>
#include <libc.h>

enum {
       stderr = 2,
       RDNETIMEOUT = 30*60*1000,
       WRNETIMEOUT = RDNETIMEOUT,
};
#else

/* not for plan 9 */
#include <stdio.h>
#include <errno.h>
#include <time.h>
#include <fcntl.h>
#include <signal.h>

#define create  creat
#define seek    lseek
#define fprint  fprintf
#define sprint  sprintf
#define exits   exit

#define ORDWR   O_RDWR
#define OTRUNC  O_TRUNC
#define ORCLOSE 0

#define RDNETIMEOUT     60
#define WRNETIMEOUT     60

#endif

#define MIN(a,b)        ((a<b)?a:b)

#define ACK(a)  write(a, "", 1)
#define NAK(a)  write(a, "\001", 1)

#define LPDAEMONLOG     "/tmp/lpdaemonl"

#define LNBFSZ  4096
char lnbuf[LNBFSZ];
int dbgstate = 0;
char *dbgstrings[] = {
       "",
       "rcvack1",
       "send",
       "rcvack2",
       "response",
       "done"
};

#ifdef plan9

void
error(int level, char *s1, ...)
{
       va_list ap;
       long thetime;
       char *chartime;
       char *args[8];
       int argno = 0;

       if (level == 0) {
               time(&thetime);
               chartime = ctime(thetime);
               fprint(stderr, "%.15s ", &(chartime[4]));
       }
       va_start(ap, s1);
       while(args[argno++] = va_arg(ap, char*))
               ;
       va_end(ap);
       fprint(stderr, s1, *args);
}

int
alarmhandler(void *foo, char *note) {
       USED(foo);
       if(strcmp(note, "alarm")==0) {
               fprint(stderr, "alarm at %d - %s\n", dbgstate, dbgstrings[dbgstate]);
               return(1);
       } else return(0);
}

#else

void
error(int level, char *s1, ...)
{
       time_t thetime;
       char *chartime;

       if (level == 0) {
               time(&thetime);
               chartime = ctime(&thetime);
               fprintf(stderr, "%.15s ", &(chartime[4]));
       }
       fprintf(stderr, s1, &s1 + 1);
}

void
alarmhandler() {
       fprintf(stderr, "alarm at %d - %s\n", dbgstate, dbgstrings[dbgstate]);
}

#endif

/* get a line from inpfd using nonbuffered input.  The line is truncated if it is too
* long for the buffer.  The result is left in lnbuf and the number of characters
* read in is returned.
*/
int
readline(int inpfd)
{
       register char *ap;
       register int i;

       ap = lnbuf;
       i = 0;
       do {
               if (read(inpfd, ap, 1) != 1) {
                       error(0, "read error in readline, fd=%d\n", inpfd);
                       break;
               }
       } while ((++i < LNBFSZ - 2) && *ap++ != '\n');
       if (i == LNBFSZ - 2) {
               *ap = '\n';
               i++;
       }
       *ap = '\0';
       return(i);
}

#define RDSIZE 512
char jobbuf[RDSIZE];

int
pass(int inpfd, int outfd, int bsize)
{
       int bcnt = 0;
       int rv = 0;

       for(bcnt=bsize; bcnt > 0; bcnt -= rv) {
               alarm(WRNETIMEOUT);     /* to break hanging */
               if((rv=read(inpfd, jobbuf, MIN(bcnt,RDSIZE))) < 0) {
                       error(0, "read error during pass, %d remaining\n", bcnt);
                       break;
               } else if((write(outfd, jobbuf, rv)) != rv) {
                       error(0, "write error during pass, %d remaining\n", bcnt);
                       break;
               }
       }
       alarm(0);
       return(bcnt);
}

/* get whatever stdin has and put it into the temporary file.
* return the file size.
*/
int
prereadfile(int inpfd)
{
       int rv, bsize;

       bsize = 0;
       do {
               if((rv=read(0, jobbuf, RDSIZE))<0) {
                       error(0, "read error while making temp file\n");
                       exits("read error while making temp file");
               } else if((write(inpfd, jobbuf, rv)) != rv) {
                       error(0, "write error while making temp file\n");
                       exits("write error while making temp file");
               }
               bsize += rv;
       } while (rv!=0);
       return(bsize);
}

int
tempfile(void)
{
       static tindx = 0;
       char tmpf[20];
       int tmpfd;

       sprint(tmpf, "/tmp/lp%d.%d", getpid(), tindx++);
       if((tmpfd=create(tmpf,
#ifdef plan9
               ORDWR|OTRUNC,
#endif
           0666)) < 0) {
               error(0, "cannot create temp file %s\n", tmpf);
               exits("cannot create temp file");
       }
       close(tmpfd);
       if((tmpfd=open(tmpf, ORDWR
#ifdef plan9
               |ORCLOSE|OTRUNC
#endif
           )) < 0) {
               error(0, "cannot open temp file %s\n", tmpf);
               exits("cannot open temp file");
       }
       return(tmpfd);
}

int
recvACK(int netfd)
{
       int rv;

       *jobbuf = '\0';
       alarm(RDNETIMEOUT);
       if (read(netfd, jobbuf, 1)!=1 || *jobbuf!='\0') {
               error(0, "failed to receive ACK, ");
               if (*jobbuf == '\0')
                       error(1, "read failed\n");
               else
                       error(1, "received <0x%x> instead\n", *jobbuf);
               rv = 0;
       } else rv = 1;
       alarm(0);
       return(rv);
}

void
main(int argc, char *argv[])
{
       char *devdir;
       int i, rv, netfd, bsize, datafd;
#ifndef plan9
       void (*oldhandler)();
#endif

       /* make connection */
       if (argc != 2) {
               fprint(stderr, "usage: %s network!destination!service\n",
                       argv[0]);
               exits("usage");
       }

       /* read options line from stdin into lnbuf */
       i = readline(0);

       /* read stdin into tempfile to get size */
       datafd = tempfile();
       bsize = prereadfile(datafd);

       /* network connection is opened after data is in to avoid timeout */
       if ((netfd = dial(argv[1], 0, 0, 0)) < 0) {
               fprint(stderr, "dialing ");
               perror(argv[1]);
               exits("can't dial");
       }

       /* write out the options we read above */
       if (write(netfd, lnbuf, i) != i) {
               error(0, "write error while sending options\n");
               exits("write error sending options");
       }

       /* send the size of the file to be sent */
       sprint(lnbuf, "%d\n", bsize);
       i = strlen(lnbuf);
       if ((rv=write(netfd, lnbuf, i)) != i) {
               perror("write error while sending size");
               error(0, "write returned %d\n", rv);
               exits("write error sending size");
       }

       if (seek(datafd, 0L, 0) < 0) {
               error(0, "error seeking temp file\n");
               exits("seek error");
       }
       /* mirror performance in readfile() in lpdaemon */

#ifdef plan9
       atnotify(alarmhandler, 1);
#else
       oldhandler = signal(SIGALRM, alarmhandler);
#endif

       dbgstate = 1;
       if(!recvACK(netfd)) {
               error(0, "failed to receive ACK before sending data\n");
               exits("recv ack1 failed");
       }
       dbgstate = 2;
       if ((i=pass(datafd, netfd, bsize)) != 0) {
               NAK(netfd);
               error(0, "failed to send %d bytes\n", i);
               exits("send data failed");
       }
       ACK(netfd);
       dbgstate = 3;
       if(!recvACK(netfd)) {
               error(0, "failed to receive ACK after sending data\n");
               exits("recv ack2 failed");
       }

       /* get response, as from lp -q */
       dbgstate = 4;
       while((rv=read(netfd, jobbuf, RDSIZE)) > 0) {
               if((write(1, jobbuf, rv)) != rv) {
                       error(0, "write error while sending to stdout\n");
                       exits("write error while sending to stdout");
               }
       }
       dbgstate = 5;

#ifdef plan9
       atnotify(alarmhandler, 0);
       /* close down network connections and go away */
       exits("");
#else
       signal(SIGALRM, oldhandler);
       exit(0);
#endif
}