/*  -------------------------------------------------------------------
    lpr - line printer

    Used to print files on remote printers using the LPD protocol.
    Built on top of the NCSA TCP/IP package (version 2.2tn for MS-DOS).

    Paul Hilchey   May 1989

    Copyright (C) 1989  The University of British Columbia
    All rights reserved.

    history:
     8/17/89    Relax option parsing to allow spaces
     8/21/89    Expand wildcards in filenames
    10/23/89    Default title to filename with -p format
    -------------------------------------------------------------------
*/

#include "stdio.h"
#include "whatami.h"
#include "hostform.h"
#include "string.h"
#include "stdlib.h"
#include <dos.h>
#include <stdarg.h>
#include "io.h"
#include "dir.h"
#define WINMASTER
#include "windat.h"
#include "lp.h"

#define MAX_COPIES          10
#define DEFAULT_INDENT      8
#define CONTROL_FILE_SIZE   1024    /* max size in bytes */

/*  Function prototypes  */

long get_size(FILE *data_file, int is_text);


/*  global variables */

char *remote_name = NULL, /* printer name on remote system */
     *remote_host = NULL, /* address of remote host        */
     *class = NULL,       /* job classification            */
     *job = NULL,         /* job name                      */
     *title = NULL,       /* job title                     */
     filter = 'f';        /* default filter                */
int copies = 1,           /* number of copies              */
    indent = -1,          /* indent, -1 means not set      */
    width = 0,            /* width, 0 means not set        */
    noburst = 0;          /* 1 = skip burst page           */

int     connection_id;
char    control_file[CONTROL_FILE_SIZE];  /* control file to be sent after data files, built in a string */
int     cf_length = 0;      /* current length of control_file */
int     sequence_number;    /* sequence number for spooled file names */
struct config *cp;          /* configuration information */
char    username[9];        /* name of user */
int     debug = 0;          /* 1 = print debugging info; set with -D option */

int     ftppassword;        /* not used; just to avoid unresolved external */


/****************************************************************
 *  Main program.                                               *
 *     lpr  [option ...] file1 ...                              *
 ****************************************************************/
main(int argc,char *argv[])
{
    int i,first_time, temp;
    char *ptr;

    ctrlbrk(breakstop);

    /* Do session initialization.  Snetinit reads config file. */
    ptr = getenv("CONFIGTEL");
    if (ptr != NULL) Shostfile(ptr);
    if (Snetinit()) crash("network initialization failed.");

    first_time = 1;          /* reset once we've found something to print */

    /* select default printer */
    remote_name = getenv("PRINTER");
    if (remote_name == NULL) remote_name = DEFAULT_PRINTER;

    remote_host = getenv("SERVER");

    /* Files sent to the remote system need to have a name of the
       form   df<letter><sequence number><host name>  (data files)
         or   cf<letter><sequence number><host name>  (control files).
       It isn't convenient for to keep track of the sequence number
       from when lpr was last used, so we just use a random number.
       It is unlikely but possible that we could get into trouble
       doing this.                                                    */
    randomize();
    sequence_number = rand() % 1000;

    /* get info from configuration file */
    cp = (struct config *)malloc(sizeof(struct config));
    Sgetconfig(cp);

    /* check that the machine name was set in the configuration file */
    if (0 == strlen(cp->me)) crash("`myname' not set in config file.");

    /* set user name.  use first part of machine name if nothing else. */
    ptr = getenv("USER");
    if (NULL != ptr) {
        strncpy(username,ptr,8);
        username[8]='\0';
    }
    else {
        i = min(strcspn(cp->me,"."),sizeof(username)-1);
        strncpy(username,cp->me,i);
        username[i]='\0';
    }

    /* Loop through command line arguments */
    for (i=1; i<argc; ++i)

        if (argv[i][0] == '-')        /* setting an option */

            switch(argv[i][1]) {

            case '#':                   /* set number of copies */
                if (argv[i][2])
                    temp = atoi(&argv[i][2]);
                else if (i+1 < argc)
                    temp = atoi(argv[++i]);
                else
                    temp = 1;

                if (temp < 1 || temp > MAX_COPIES) {
                    fprintf(stderr,"Unreasonable number of copies requested.  Reset to 1.\n");
                    temp = 1;
                }
                copies = temp;
                break;

            case 'P':                   /* set printer name */
                if (argv[i][2])
                    remote_name = &argv[i][2];
                else if (i+1 < argc)
                    remote_name = argv[++i];
                break;

            case 'S':                   /* select server */
                if (argv[i][2])
                    remote_host = &argv[i][2];
                else if (i+1 < argc)
                    remote_host = argv[++i];
                break;

            case 'C':                   /* set job classification */
                if (argv[i][2])
                    class = &argv[i][2];
                else if (i+1 < argc)
                    class = argv[++i];
                break;

            case 'J':                   /* set job name */
                if (argv[i][2])
                    job = &argv[i][2];
                else if (i+1 < argc)
                    job = argv[++i];
                break;

            case 'T':                   /* set job title */
                if (argv[i][2])
                    title = &argv[i][2];
                else if (i+1 < argc)
                    title = argv[++i];
                break;

            case 'i':                   /* set indent */
                temp = argv[i][2] ? atoi(&argv[i][2]) : DEFAULT_INDENT;
                if (temp < -1 || temp > 255)
                    fprintf(stderr,"Unreasonable indent requested.  Indent not changed.\n");
                else
                    indent = temp;
                break;

            case 'w':                   /* set page width */
                if (argv[i][2])
                    temp = atoi(&argv[i][2]);
                else if (i+1 < argc)
                    temp = atoi(argv[++i]);
                else
                    break;

                if (temp < 0 || temp > 512)
                    fprintf(stderr,"Unreasonable page width requested.  Width not changed.\n");
                else
                    width = temp;
                break;

            case 'p':   /* pr */
            case 'v':   /* raster */
            case 'c':   /* cifplot */
            case 'g':   /* graph */
            case 'd':   /* dvi */
            case 'n':   /* ditroff */
            case 't':   /* troff */
            case 'l':   /* long */
                filter = argv[i][1];
                break;

            case 'f':                   /* fortran */
                filter = 'r';  /* f selects filter r */
                break;

            case 'h':                   /* suppress burst page */
                noburst = 1;
                break;

            case 'D':                   /* turn debugging output on */
                debug = 1;
                break;

            default:
                fprintf (stderr,"Unrecognized option: %s ignored.\n",argv[i]);
                break;
            }
        else {                          /* name of file to print */
            if (first_time) {           /* open connection on first job */
                if (NULL == remote_host) crash("server not specified.");
                if (NULL == job) job=argv[i];  /* default job name */
                start_protocol(remote_host,remote_name);
                first_time = 0;
            }
            print_file(argv[i]);
        }

    if (first_time) {
        puts("Name:  lpr - send a job to a remote printer; version 1.3");
        puts("Usage: lpr  [ options ... ]  file1 ... ");
        puts("Options are:");
        puts("  -P<printer>     -S<server>      -C<class>      -J<job>");
        puts("  -T<title>       -i<indent>      -w<width>      -#<copies>");
        puts("  -p              -l              -d             -f");
        puts("  -h");
    }
    else {
        finish_protocol();
        puts("Done.");
    }
    netshut();
    exit(EXIT_SUCCESS);
}

/*****************************************************************
 *  start_protocol                                               *
 *  Open the TCP connection and start building the control file. *
 *  Aborts with an error message if someting goes wrong.         *
 *  parameters: null terminated name or IP address of the server *
 *              name of the printer on the server                *
 *****************************************************************/
start_protocol(char *host,char *rname)
{
    char buf[30];
    int i,what,dat,ev;
    struct machinfo *server_info_record;

    server_info_record = lookup(host);
    if (0 == server_info_record) crash("domain lookup failed for %s.",host);

    netfromport(rand() % 1023);  /* source should be a privileged port */
    connection_id = open_connection(server_info_record, rand() % MAX_PRIV_PORT, PRINTER_PORT);
    if (0 > connection_id)
        crash("unable to open connection.");

    /* Tell LPD to receive a job. */
    nprintf(connection_id,"%c%s\n", LPD_PRINT_JOB, rname);

    if (debug) puts("told LPD to receive job, awaiting ack");
    check_ack(connection_id);

    cf_length = 0;
               cf_length += sprintf(control_file+cf_length,"H%s\n",cp->me);
               cf_length += sprintf(control_file+cf_length,"P%s\n",username);
    if (job)   cf_length += sprintf(control_file+cf_length,"J%s\n",job);
               cf_length += sprintf(control_file+cf_length,"C%s\n",class ? class : cp->me);
               cf_length += sprintf(control_file+cf_length,"L%s\n",username);
}

/******************************************************************
 * finish_protocol                                                *
 * Called after last data file has been sent.  Sends the control  *
 * file then closes the network connection.                       *
 ******************************************************************/
finish_protocol()
{
    nprintf(connection_id,"%c%d cfA%03d%s\n", LPD_RECEIVE_CONTROL_FILE, cf_length,
            sequence_number, cp->me);
    if (debug) puts("Told LPD we're sending control file, awaiting ack.");
    check_ack(connection_id);
    netwrite(connection_id,control_file,cf_length);
    nprintf(connection_id,"%c",LPD_END_TRANSFER);
    if (debug) puts("Sent control file, awaiting ack.");
    check_ack(connection_id);

    netclose(connection_id);
}

/**************************************************************** *
 * print_file                                                     *
 * Changes any forward slashes in the filename to backwards ones, *
 * and then expands any wildcards before calling print_one_file.  *
 ******************************************************************/
print_file(char *filename)
{
    int done;
    struct ffblk ffblk;
    char *p;
    char *start_of_name;
    char *expanded_name;

    /* change forward slashes in filenames to suit backwards MS-DOS */
    p=filename;
    while (*p != '\0') {
        if (*p == '/') *p = '\\';
        p++;
    }

    /* make a copy of the path portion of the filename with room
       to substitute any expanded wildcard filenames at the end  */
    expanded_name = (char *)malloc(strlen(filename)+13);
    strcpy(expanded_name,filename);
    start_of_name = strrchr(expanded_name,'\\');
    if (start_of_name == NULL)
       start_of_name = expanded_name;
    else
       start_of_name++;

    /* expand wildcards */
    done = findfirst(filename, &ffblk, 0);
    if (done)
        fprintf(stderr,"%s doesn't exist.\n",filename);
    else
        do {
            strcpy(start_of_name,ffblk.ff_name);
            print_one_file(expanded_name);
        } while (0 == findnext(&ffblk));

    free(expanded_name);
}


/******************************************************************
 *  Print a file                                                  *
 *  Adds records to the control file as appropriate for the       *
 *  various option settings, then sends the data file.            *
 ******************************************************************/
print_one_file(char *filename)
{
    char spool_file_name[40];
    int is_text,i;
    char *p;
    FILE *data_file;

    if (debug) printf("printing file %s\n",filename);

    /* for `non-binary' filters, we do a little bit of filtering beforehand */
    is_text = (filter == 'f' || filter == 'l' || filter == 'p' || filter == 'r');

    /* open the file in the appropriate mode (text or binary) */
    if (is_text)
        data_file = fopen(filename,"rt");
    else
        data_file = fopen(filename,"rb");

    if (data_file == NULL) {
        fprintf(stderr,"Trouble opening file %s.\n",filename);
        return;
    }
    printf("Sending %s ",filename);

    /* make up name for spooled data file */
    sequence_number = (sequence_number + 1) % 1000;
    sprintf(spool_file_name,"dfA%03d%s",sequence_number,cp->me);

    /* add records in control file describing job */
    if (width != 0)   cf_length += sprintf(control_file+cf_length,"W%d\n",width);
    if (indent != -1) cf_length += sprintf(control_file+cf_length,"I%d\n",indent);
    if (filter == 'p') cf_length += sprintf(control_file+cf_length,"T%s\n", (title != NULL) ? title : filename);
    for (i=1; i <= copies; i++)
        cf_length += sprintf(control_file+cf_length,"%c%s\n",filter,spool_file_name);
    cf_length += sprintf(control_file+cf_length,"U%s\n",spool_file_name);
    cf_length += sprintf(control_file+cf_length,"N%s\n",filename);

    send_file(connection_id, data_file, spool_file_name, is_text);
}

/****************************************************************
 * send_file                                                    *
 * Does the actual work of sending the file to the daemon.      *
 * parameters: the connection id, as returned from snetopen     *
 *             a file handle for the data file to be sent       *
 *             the name of the spool file on the remote system  *
 *             a flag indicating if this is a text file         *
 ****************************************************************/
send_file(int connection_id, FILE *data_file, char *spool_name, int is_text)
{
    char buf[1024];
    long length, step;
    char *p;
    int xp, towrite, i;
    int file_handle;

    length = get_size(data_file, is_text);

    nprintf(connection_id, "%c%ld %s\n", LPD_RECEIVE_DATA_FILE, length, spool_name);
    check_ack(connection_id);

    file_handle = fileno(data_file);
    towrite = xp = 0;
    step = max(length / 5, 1);  /* print one dot for each 1/5 of the file sent */

    while(1) {
        if (towrite <= xp) {
            towrite = read(file_handle, buf, 1024);
            if (towrite == 0) break;
            xp = 0;
        }

        i = netwrite(connection_id,&buf[xp], towrite-xp);
        if (debug && i) printf("send_file sent %d bytes\n",i);
        stask();
        checkerr();
        if (i < 0) crash("transfer failed.");

        if (i > 0) {
            int j;

            /* print out dots indicating how much sent */
            for (j=1; j<=((length+step-5)/step - (length+step-i-5)/step); j++)
                putchar('.');
            xp += i;
            length -= i;
        }
    }
    putchar('\n');
    if (length != 0)
        crash ("file length discrepancy.");

    /* send end mark */
    nprintf(connection_id, "%c", LPD_END_TRANSFER);
    check_ack(connection_id);
}

/************************************************************************
 * Return the size of a file.  For text files, we have to read through  *
 * the whole thing, as we are going to send LF as line terminator,      *
 * rather than CRLF.                                                    *
 ************************************************************************/
long get_size(FILE *data_file, int is_text)
{
    char buf[1024];
    long total;
    int handle;
    int count;

    handle = fileno(data_file);
    if (is_text) {
        total = 0;
        while ((count = read(handle, buf, 1024)) > 0)
            total += count;
        rewind(data_file);
        return(total);
    }
    else
        return(filelength(handle));
}

/*********************************************************
 *  Wait for a one character reply from LPD.  Abort with *
 *  an error message if it not an OK acknowledgement.    *
 *********************************************************/
check_ack(int connection_id)
{
    char ack_buff[80];
    int len;

    while(1) {
       len = nread(connection_id, ack_buff, 80);
       if (len <= 0)
           crash("connection closed by server.");
       switch (ack_buff[0]) {
           case LPD_OK :
               return;
           case LPD_ERROR :
               crash("error code from server.");
           case LPD_NO_SPOOL_SPACE :
               crash("server unable to accept job at this time.");
           default :   /* may be an error message */
               fprintf(stderr,"%.*s",len,ack_buff);
        }
     }
}
