/*
 * "lpr" program for systems that don't have lpd but can talk to a system
 * that does using TCP or DECnet
 *
 * Copyright (C) 1990, 1991 Keith Moore

 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License, Version 1,
 * as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 * Written by Keith Moore, December 1990
 * Email: moore@cs.utk.edu (Internet)	moore@utkvx (BITNET)
 * Snail: Box 16320 / Knoxville TN  37996 / USA
 */

/* TO DO:
 * - send troff font names
 * - add support for /etc/printcap files.
 * - add options for lpq, lprm commands
 * - special hacks for Imagen and/or PostScript printers (maybe)
 * - support -i (indent) and -w (page width) options
 * - handle huge files too big to read into memory (UNIX systems only)
 * - recognize ditroff, raster, cifplot, and FORTRAN output files (maybe)
 * - instead of MAX_READ and MAX_WRITE, use MAX_{FILE,SOCKET}_{READ,WRITE}
 * - handle multiple lpd servers -- try each until we find one that's up.
 * - allow printer names of the form printer@host or host::printer,
 *   or add a -S option to specify print server on command line.
 * - add an option to specify job-id (for use when using this program
 *   as the back-end to another print spooling system -- you can keep the
 *   job-ids the same on both systems if you're lucky).
 * - add an option to wait until the job is actually printed -- this is
 *   not easy to do but is very useful when this program is being used
 *   as the back-end to another kind of print spooler.
 */

#include "config.h"
#include "patchlevel.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <ctype.h>
#include <time.h>

#define VERSION 1

char hostname[512];		/* name of this host */
char username[100];		/* name of submitting user */
char lpd_server[512];		/* name of host running lpd */
char email_address[512];	/* sender's email address */

/*
 * options...not all of which are supported
 */
char *printer;			/* name of remote print queue */
char file_type = '?';		/* kind of file to be printed */
char *title = NULL;		/* page headings for pr */
char *jobtitle = NULL;		/* job name */
char *jobclass = NULL;		/* job class */
int num_copies = 1;		/* number of copies to print */
int indent = 0;			/* # of spaces to indent */
int page_width = 72;		/* page width for pr */
char *fontnames[4];		/* names of troff fonts */
int rflag = 0;			/* if 1, remove file after spooling */
int mflag = 0;			/* if 1, send mail on completion */
int hflag = 0;			/* if 1, omit burst page */
int debug = 0;

#ifndef __STDC__
#define min(a,b) ((a) < (b) ? (a) : (b))
char *getenv ();
char *calloc ();
char *realloc ();
char *strrchr ();
#endif

/*
 * Initialize system-dependent variables
 */

#ifdef vms
#include <stat.h>

void
sysdep (void)
{
    char *p;
    /* really need to use sys$trnlog here instead of getenv */
    if (p = getenv ("SYS$NODE"))
	strcpy (hostname, p);
    else {
	fprintf (stderr, "lpr: no translation for logical SYS$NODE\n");
	exit (1);
    }
    cuserid (username);
    sprintf (email_address, "%s::%s", hostname, username);
    for (p = hostname; *p; ++p)
	if (isupper (*p))
	    *p = tolower (*p);
    if (p = getenv ("LPD_SERVER")) {
	strcpy (lpd_server, p);
	for (p = hostname; *p; ++p)
	    if (islower (*p))
		*p = toupper (*p);
    }
    else {
	fprintf (stderr, "lpr: no translation for logical name LPD_SERVER\n");
	exit (1);
    }
}
#endif

#ifdef unix
#include <sys/types.h>
#include <sys/stat.h>
#include <pwd.h>
#include <netdb.h>

void
sysdep (void)
{
    struct passwd *pwd;
    char *p;
    struct hostent *hp;

    gethostname (hostname, sizeof hostname);
    if (pwd = getpwuid (getuid ()))
	strcpy (username, pwd->pw_name);
    else {
	fprintf (stderr, "lpr: system problem: can't get your username!\n");
	exit (1);
    }
    if (p = getenv ("LPD_SERVER"))
	strcpy (lpd_server, p);
    else {
	fprintf (stderr, "lpr: LPD_SERVER environment variable is not set\n");
	exit (1);
    }
    hp = gethostbyname (hostname);
    sprintf (email_address, "%s@%s", username, hp ? hp->h_name : hostname);
}
#endif

/*
 * Open a channel to the print spooler
 * returns an fd on success
 * Print a message and return EOF on failure
 */

#if use_decnet
/*
 * Code to open a DECnet channel to an lpd gateway
 */
#define MAX_READ 512
#define MAX_WRITE 512

#ifdef vms
int
open_lpd (void)
{
    int fd;
    char buf[512];
    sprintf (buf, "%s::\"223=\"", lpd_server);
    if ((fd = open ("%s::\"223=\"", 2)) < 0)
	perror ("Cannot open remote DECnet printer daemon");
    return fd;
}
#endif

#ifdef sun
/* code for sunlink dni goes here */
#endif

#ifdef ultrix
/* code for DECnet ultrix goes here */
#endif

#endif				/* use_decnet */


#if use_tcp
#define MAX_READ 32000
#define MAX_WRITE 32000

/*
 * Code to open a TCP connection to lpd
 * This requires that this program be run set-uid to root in order
 * to be able to bind to a privileged port.
 */

#ifdef unix
#include <sys/socket.h>
#include <errno.h>
#include <netinet/in.h>

int
open_lpd (void)
{
    int fd;
    int port;
    struct hostent *hp;
    struct servent *sp;
    struct sockaddr_in s;

    if ((fd = socket (AF_INET, SOCK_STREAM, 0)) == EOF) {
	perror ("socket");
	return EOF;
    }
    for (port = IPPORT_RESERVED-1; port > IPPORT_RESERVED / 2; port--) {
	extern int errno;
	s.sin_family = AF_INET;
	s.sin_addr.s_addr = INADDR_ANY;
	s.sin_port = htons (port);
	if (bind (fd, (struct sockaddr *) &s, sizeof (s)) == 0)
	    break;
	if (errno == EACCES) {
	    fprintf (stderr, "lpr: bind: cannot bind to privileged port\n");
	    return EOF;
	}
    }
    if ((hp = gethostbyname (lpd_server)) == NULL) {
	fprintf (stderr, "lpr: can't find network address for %s\n",
		 lpd_server);
	fflush (stderr);
	return EOF;
    }
    bcopy (hp->h_addr, (char *) &s.sin_addr, hp->h_length);
    if ((sp = getservbyname ("printer", "tcp")) == NULL)
	s.sin_port = htons (515);
    else
	s.sin_port = sp->s_port;
    if (connect (fd, (struct sockaddr *)&s, sizeof s) < 0) {
	perror ("connect");
	close (fd);
	return EOF;
    }

    /* turn off set-uid privileges -- we no longer need them! */
    setuid (getuid ());

    return fd;
}
#endif
#endif				/* use_tcp */


#if use_dev_printer
#define MAX_READ 8000
#define MAX_WRITE 8000

/*
 * NB:  This code doesn't work with BSD lpd...
 *
 * I tried opening the UNIX domain socket named /dev/printer and talking
 * to the spooler that way.  This fails because of a bug in lpd ... it
 * waits for end of file on its input socket before submitting the job,
 * but then it tries to write an ack byte back to the socket before
 * printing ... which causes a SIGPIPE signal since we have closed
 * the socket.  I tried doing a shutdown(fd, 1) on the socket instead
 * from this end, but then lpd waits forever for more input.  Apparently
 * writing a single byte on a TCP socket that is closed on the other
 * end doesn't cause a SIGPIPE.  I even tried using SO_DONTLINGER to
 * see if that would keep a SIGPIPE from happening...but no luck.
 */

#include <sys/socket.h>
#include <sys/un.h>

int
open_lpd (void)
{
    int fd;
    struct sockaddr_un s;
#ifndef SO_DONTLINGER
    struct linger linger;
#endif

    fd = socket (AF_UNIX, SOCK_STREAM, 0);
    if (fd < 0) {
        perror ("Can't create socket to connect to print spooler");
        return fd;
    }
#ifdef SO_DONTLINGER
    if (setsockopt (fd, SOL_SOCKET, SO_DONTLINGER, 0, 0) < 0)
	perror ("setsockopt");
#else
    linger.l_onoff = 0;
    linger.l_linger = 0;
    if (setsockopt (fd, SOL_SOCKET, SO_LINGER, &linger, sizeof linger) < 0)
	perror ("setsockopt");
#endif
    s.sun_family = AF_UNIX;
    strcpy (s.sun_path, "/dev/printer");
    if (connect (fd, &s, sizeof s) < 0){
        perror ("Cannot connect to print spooler");
        close (fd);
        return EOF;
    }
    return fd;
}

#endif				/* use_dev_printer */

/*
 * debugging routines
 */

void
dump_buf (FILE *fp, char *prefix, char *buf, int size)
{
    fprintf (fp, "%s", prefix);
    while (size > 0){
        if (*buf >= ' ' && *buf <= '~')
            putc (*buf, fp);
        else if (*buf == '\n')
            fprintf (fp, "\\n");
        else
            fprintf (fp, "\\%03o", *buf);
        ++buf;
        --size;
    }
    fprintf (fp, "\n");
}


int
x_read (int fd, char *buf, unsigned size)
{
    int nread = read (fd, (char *) buf, size);
    if (debug)
	dump_buf (stderr, "<<<", buf, nread);
    return nread;
}

int
x_write (int fd, char *buf, unsigned size)
{
    if (debug)
	dump_buf (stderr, ">>>", buf, size);
    return write (fd, (char *) buf, size);
}

int
y_write (int fd, char *buf, unsigned size)
{
    if (debug)
	fprintf (stderr, ">>> (%d bytes)\n", size);
    return write (fd, (char *) buf, size);
}


/*
 * parse an option with an optional argument (which may be NULL)
 * return 1 if optional argument used, else 0
 * (one of these days I'll start using getopt())
 */
int
real_option (char *opt, char *arg)
{
    if (opt[1] && opt[2] == '\0') {
	/* single letter options */
	switch (opt[1]) {
	case 'd':		/* TeX .dvi file */
	case 'f':		/* ordinary text file (add page breaks) */
	case 'g':		/* UNIX plot file */
	case 'l':		/* text file with embedded control chars */
	case 'n':		/* ditroff output file */
	case 'o':		/* PostScript file */
	case 'p':		/* text file (add page headers using pr) */
	case 'r':		/* FORTRAN output file w/carraige control  */
	case 't':		/* C/A/T troff output file*/
	case 'v':		/* Versatec output file */
	    file_type = opt[1];
	    return 0;
	case 'P':		/* -P printer */
	case 'q':		/* -q queue (same thing) */
	    printer = arg;
	    return 1;
	case '#':		/* num copies */
	    if (arg && isdigit (*arg)) {
		num_copies = atoi (arg);
		return 1;
	    }
	    break;
	case 'C':		/* job class (default: local hostname) */
	    jobclass = arg;
	    return 1;
	case 'J':		/* job title (default: first file name) */
	    jobtitle = arg;
	    return 1;
	case 'i':		/* indent output (default: 8 chars) */
	    if (arg && isdigit (*arg)) {
		indent = atoi (arg);
		return 1;
	    }
	    else {
		indent = 8;
		return 0;
	    }
	case '1':		/* troff font names */
	case '2':
	case '3':
	case '4':
	    fontnames[opt[1]-'1']=arg;
	    return 1;
	case 'w':		/* cols -- page width for pr */
	    if (arg && isdigit (*arg)) {
		page_width = atoi (arg);
		return 1;
	    }
#if 0
	    /* conflicts with -r (print FORTRAN file)...which is right? */
	case 'r':		/* remove file after spooling */
	    rflag = 1;
	    return 0;
#endif
	case 'm':		/* send mail upon completion */
	    mflag = 1;
	    return 0;
	case 'h':		/* don't print the burst page */
	    hflag = 1;
	    return 0;
	case 's':		/* don't copy file -- symlink it */
	    fprintf (stderr,
		     "lpr: The -s (symlink) option is not supported\n");
	    fprintf (stderr,
		     "All files will be copied to the remote server\n");
	    return 0;
	}
    }
    if (strcmp (opt, "-debug") == 0) {
	debug = 1;
	fprintf (stderr, "standalone lpr version %d.%d",
		 VERSION, PATCHLEVEL);
	return 0;
    }
    fprintf (stderr, "lpr: warning: illegal option %s\n", opt);
    return 0;
}

int
option (char *opt, char *optarg)
{
    /*
     * This hack is used to notice whether the argument for an option
     * is appended to the option itself (e.g. "-Pprinter" rather than
     * "-P" "printer".  If this is the case, and the option accepts an
     * argument, split the arg into two args and call real_option().
     * otherwise just pass our args to real_option().
     */
  
    if (opt[2] && strchr ("P#CJTi1234w", opt[1])) {
	char temp[3];
	temp[0] = '-';
	temp[1] = opt[1];
	temp[2] = '\0';
	real_option (temp, opt + 2);
	return 0;
    }
    else
	return real_option (opt, optarg);
}

/*
 * keep up with files to be deleted (for when using -r)
 * This is so we don't delete files until we *know* that the job
 * has been successfully submitted.
 */

struct delete_list {
    char *name;
    struct delete_list *next;
} *head = NULL;

void
mark_for_delete (char *name)
{
    struct delete_list *ptr = (struct delete_list *)
	calloc (1, sizeof (struct delete_list));
    if (!ptr) {
	perror ("calloc");
	return;
    }
    ptr->next = head;
    ptr->name = name;
    head = ptr;
}

void
delete_marked_files (void)
{
    struct delete_list *ptr;
    for (ptr = head; ptr; ptr=ptr->next) {
	if (ptr->name)
	    if (unlink (ptr->name) < 0) {
		fprintf (stderr, "lpr: could not delete %s\n", ptr->name);
		perror ("unlink");
	    }
    }
}

/*
 * buffer management
 */
struct buffer {
    char *ptr;			/* points to current append point */
    int size;			/* how big is the buffer now? */
    char text[1];		/* (extensible) array of bytes in the buffer */
};

/*
 * Create an empty buffer
 */

struct buffer *
create_buffer (unsigned int initial_size)
{
    struct buffer *buf;
    if (initial_size <= 0)
	initial_size = 1000;
    if ((buf = (struct buffer *)
	 calloc (1, sizeof (struct buffer) + initial_size - 1)) == NULL)
	return NULL;
    buf->ptr = buf->text;
    buf->size = initial_size;
    return buf;
}

/*
 * Ensure there is enough room in the buffer for "more" more bytes
 */
struct buffer *
enlarge_buffer (struct buffer *buf, unsigned int more)
{
    int offset = buf->ptr - buf->text;
    int space = buf->size - offset;
    if (more > space) {
	int newsize = sizeof (struct buffer) + (buf->size * 2) - 1;
	buf = (struct buffer *) realloc ((char *) buf, newsize);
	if (buf == NULL)
	    return NULL;
	buf->ptr = buf->text + offset;
	buf->size = newsize;
    }
    return buf;
}

/*
 * Append up to max_size bytes from an open file to buffer
 */

int
read_file_into_buffer (struct buffer *buf, int fd, unsigned max_size)
{
    int real_size = 0;
    while (max_size > 0)  {
	int foo = min (max_size, MAX_READ);
	if (enlarge_buffer (buf, foo) == NULL)
	    return EOF;
	if ((foo = read (fd, buf->ptr, foo)) < 0)
	    return EOF;
	if (foo == 0)
	    break;
	buf->ptr += foo;
	max_size -= foo;
	real_size += foo;
    }
    return real_size;
}

/*
 * Append a NUL-terminated string to the buffer
 */

void
append_string_to_buffer (struct buffer *buf, char *string, int length)
{
    if (enlarge_buffer (buf, length) == NULL) {
	fprintf (stderr, "lpr: file too big to fit in memory\n");
	exit (1);
    }
    strncpy (buf->ptr, string, length);
    buf->ptr += length;
}

/*
 * Write out the entire contents of buffer to the file fd.
 */

int
send_file_from_buffer (int fd, struct buffer *buf)
{
    char *wptr = buf->text;
    while (wptr < buf->ptr) {
	int foo = min (buf->ptr - wptr, MAX_WRITE);
	if ((foo = y_write (fd, wptr, foo)) < 0)
	    return EOF;
	wptr += foo;
    }
    return 0;
}

/*
 * free up a buffer
 */

void
free_buffer (struct buffer *buf)
{
    if (buf)
	cfree (buf);
}

int
buffer_size (struct buffer *buf)
{
    if (buf)
	return (buf->ptr - buf->text);
    return EOF;
}

/*
 * Look at a file buffer and guess what kind of file it is.  This is called
 * when we aren't given an explicit file type option.
 */

char
guess_file_type (struct buffer *buf, char *fname)
{
    char *ptr = buf->text;
    if (ptr[0] == '%' && ptr[1] == '!') {
	fprintf (stderr, "lpr: %s is a PostScript (tm) file, assuming -f\n",
		 fname);
	return 'f';		/* PostScript */
    }
    if (ptr[0] == '\367' && ptr[1] == 2) {
	fprintf (stderr, "lpr: %s is a TeX .dvi file, assuming -d\n", fname);
	return 'd';		/* TeX .dvi file */
    }
    if (ptr[0] == '\100' && ptr[1] == '\357')  {
	fprintf (stderr, "lpr: %s is a C/A/T troff output file, assuming -t\n",
		 fname);
	return 't';		/* C/A/T troff file */
    }
    return 'f';
}

int
check_for_bogus_file (struct buffer *buf, char type, char *filename)
{
    char *ptr = buf->text;

    if (buf->ptr == buf->text) {
	fprintf (stderr, "lpr: skipping zero-length file %s\n", filename);
	return EOF;
    }
	
    if (ptr[0] == '\037' && ptr[1] == '\235') {
	fprintf (stderr, "lpr: %s is a compressed file -- ignoring it\n",
		 filename);
	return EOF;
    }
    if (type == 'd') {
	if (ptr[0] == '\367' && ptr[1] == '\002') {
	    /* sometimes .dvi files have trailing NULs when they
	       shouldn't have.  Remove these from the buffer and
	       check that the .dvi file ends with a \337 byte. */
	    if (buf->ptr[-1] == '\0') {
		while (buf->ptr[-1] == '\0')
		    --(buf->ptr);
	    }
	    if (buf->ptr[-1] == '\337')
		return 0;
	}
	fprintf (stderr, "lpr: %s is not a valid .dvi file", filename);
	return EOF;
    }
    if (memcmp (ptr, "!<arch>\n", 8) == 0) {
	fprintf (stderr, "lpr: %s is a UNIX library archive -- ignoring it",
		 filename);
	return EOF;
    }
    return 0;
}


/*
 * Send a command to the remote server and wait for a response.  Return
 * the first byte of the response, or EOF on error.
 */

int
send_command (int fd, char *buf, int size)
{
    char x[1];
    if (x_write (fd, buf, size) != size)
	return EOF;
    if (x_read (fd, x, 1) != 1)
	return EOF;
    return *x;
}


/*
 * structure used to keep track of print jobs
 */
struct job {
    int jobid;			/* integer job id 1-999 */
    int fd;			/* fd of lpd socket */
    int control_file_number;	/* current file number */
    int data_file_number;	/* data_file_number */
    struct buffer *cfile;	/* buffer to build control file */
};

/*
 * add a record to a control file
 */
void
control (struct job *job, char cmd, char *arg)
{
    char buf[512];
    sprintf (buf, "%c%s\n", cmd, arg);
    append_string_to_buffer (job->cfile, buf, strlen (buf));
}


/*
 * create a print job.  Return a job ptr on success or NULL on error.
 */
struct job *
open_job (char *queuename)
{
    struct job *job;
    char buf[512];
    char x;

    if ((job = (struct job *) calloc (1, sizeof (struct job))) == NULL)
	return NULL;
    /*
     * generate a job #.  Really, this should be maintained on a
     * per-(host,queue) basis, so we won't have naming conflicts.
     * for now, we just generate something pseudo-random.
     */
    job->jobid = time (0) % 1000;
    if ((job->fd = open_lpd ()) < 0) {
	free (job);
	return NULL;
    }

    sprintf (buf, "\2%s\n", queuename);
    if ((x = send_command (job->fd, buf, strlen (buf))) != 0) {
	if (isprint (x)) {
	    int foo;
	    *buf = x;
	    foo = x_read (job->fd, buf+1, sizeof(buf)-1);
	    fprintf (stderr, "lpr: %.*s", foo+1, buf);
	}
	else {
	    fprintf (stderr,
		     "lpr: server %s refused job for printer %s\n",
		     lpd_server, queuename);
	}
	close (job->fd);
	free (job);
	return NULL;
    }

    job->control_file_number = 0;
    job->data_file_number = 0;
    job->cfile = create_buffer (1000);

    control (job, 'H', hostname);
    control (job, 'P', username);
    if (hflag == 0) {
	control (job, 'J', jobtitle);
	control (job, 'C', jobclass);
    }
    control (job, 'L', username);
    if (title)
	control (job, 'T', title);
    if (mflag)
	control (job, 'M', email_address);
    return job;
}

void
concoct_file_name (char c, struct job *job, char *str)
{
    int x = c == 'd' ? job->data_file_number++ : job->control_file_number++;
    sprintf (str, "%cf%c%03d%s", c,
	     "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"[x],
	     job->jobid, hostname);
}

/*
 * Send a file to be printed.  Return 1 on success and 0 on error.
 * (i.e. # of files sent). 
 * print diagnostic messages to stderr as necessary
 * If file == NULL, print standard input
 */
int
send_print_file (struct job *job, char *file, char file_type)
{
    int estimated_size;		/* how big we think the file is */
    int max_size;		/* max size to print */
    int fd = EOF;		/* fd of file */
    struct buffer *dfile = NULL; /* buffer to read file into */
    char *reason = NULL;	/* reason file xfer failed */
    char data_file_name[512];	/* name of temporary data file */
    char buf[512];
    int x;
    int i;

    /*
     * open the file and find out how big it is
     * estimated_size is used to determine how much space to allocate.
     * max_size is used to decide how much to read in.  Under VMS
     * we don't ever want to read too much, because we might get
     * unwanted garbage at the end of a fixed-length record file.
     */
    if (file) {
	struct stat sbuf;	
	if (stat (file, &sbuf) < 0) {
	    perror (file);
	    return 0;
	}
	max_size = estimated_size = sbuf.st_size;
	if ((fd = open (file, 0)) < 0) {
	    perror (file);
	    return 0;
	}
    }
    else {
	struct stat sbuf;
	file = "standard input"; /* for page headers, error messages */
	estimated_size = 64000;
	max_size = 5*1024*1024;	/* 5 megabytes */
	fd = 0;
#ifdef unix
	/*
	 * if standard input is an ordinary file, get size estimate
	 * with fstat()
	 */
	if (fstat (fd, &sbuf) < 0) {
	    if ((sbuf.st_mode & S_IFMT) == S_IFREG)
		estimated_size = sbuf.st_size;
	}
#endif
    }

    /*
     * create a buffer and read the file into it.
     */
    concoct_file_name ('d', job, data_file_name);
    if ((dfile = create_buffer (estimated_size)) == NULL) {
        reason = "file too big to fit in memory";
	goto abort;
    }
    if (read_file_into_buffer (dfile, fd, max_size) < 0) {
	reason = "error reading file";
	goto abort;
    }

    /*
     * file transfer successful.  Add this file to the print job,
     * clean up, and return.
     */
    if (file_type == '?')
	file_type = guess_file_type (dfile, file);
    /*
     * check for bogus file formats.  Refuse to print anything that
     * is obviously bogus.
     */
    if (check_for_bogus_file (dfile, file_type, file))
	goto okay;

    /*
     * transfer the file to the server, with error checking
     */
    sprintf (buf, "\3%d %s\n", buffer_size(dfile), data_file_name);
    if ((x = send_command (job->fd, buf, strlen (buf))) != 0) {
	switch (x) {
	case 1:
	    break;
	case 2:
	    reason = "not enough disk space on server (or file is too large)";
	    break;
	case EOF:
	    reason = "connection to printer server broken";
	    break;
	default:
	    if (isprint (x)) {
		*buf = x;
		x = x_read (job->fd, buf + 1, sizeof (buf) - 1);
		buf[x+1] = '\0';
		reason = buf;
	    }
	}
	goto abort;
    }
    if (send_file_from_buffer (job->fd, dfile) != 0)
	goto abort;
    if (send_command (job->fd, "", 1) < 0)
	goto abort;

    if (file_type == 'p' && !title)
	control (job, 'P', file);
    for (i = 0; i < num_copies; ++i)
	control (job, file_type, data_file_name);
    control (job, 'U', data_file_name);
    control (job, 'N', file);
 okay:
    free_buffer (dfile);
    close (fd);
    return 1;


    /*
     * file transfer failed.  print error message and clean up
     */
 abort:
    fprintf (stderr, "lpr: unable to send file %s to print server %s\n",
	     file, lpd_server);
    if (reason)
	fprintf (stderr, "reason: %s\n", reason);
    if (dfile)
	free_buffer (dfile);
    if (fd > 0)
	close (fd);
    return 0;
}

/*
 * send the control file.  Return 0 on success, nonzero on error.
 */
send_control_file (struct job *job)
{
    char buf[512];
    char control_file_name[512];

    concoct_file_name ('c', job, control_file_name);
    sprintf (buf, "\2%d %s\n", job->cfile->ptr - job->cfile->text,
	     control_file_name);
    if (send_command (job->fd, buf, strlen (buf)) < 0)
	return 1;
    if (send_file_from_buffer (job->fd, job->cfile) != 0)
	return 1;
    if (send_command (job->fd, "", 1) < 0)
	return 1;
    return 0;
}

/*
 * close a job normally and delete its resources
 */
int
close_job (struct job *job)
{
    int x;
    if (job == NULL)
	return EOF;
    x = send_control_file (job);
    if (job->cfile)
	free_buffer (job->cfile);
    close (job->fd);
    cfree (job);
    return x;
}

/*
 * abort a job and delete its resources
 */
void
abort_job (struct job *job)
{
    if (job == NULL)
	return;
    x_write (job->fd, "\1\n", 2);
    close (job->fd);
    if (job->cfile)
	free_buffer (job->cfile);
    cfree (job);
}


main (int argc, char **argv)
{
/*    char *printer; hided a global */
    struct job *job = NULL;
    int i;
    int file_args = 0;
    int files_printed = 0;

    printer = getenv ("PRINTER");
    if (printer == NULL)
	printer = "lp";

    sysdep ();
    jobclass = hostname;
    jobtitle = NULL;
    for (i = 1; i < argc; ++i) {
	if (*argv[i] == '-')
	    i += option (argv[i], argv[i+1]);
	else {
	    if (!jobtitle) {
		char *ptr = strrchr (argv[i], '/');
		jobtitle = ptr ? ptr + 1 : argv[i];
	    }
	    if (job == NULL && (job = open_job (printer)) == NULL)
		exit (1);
	    file_args++;
	    if (send_print_file (job, argv[i], file_type)) {
		files_printed++;
		if (rflag)
		    mark_for_delete (argv[i]);
	    }
	}
    }
    if (file_args == 0) {
	if (!jobtitle)
	    jobtitle = "stdin";
	if ((job = open_job (printer)) == NULL)
	    exit (1);
	files_printed += send_print_file (job, 0, file_type);
    }
    if (files_printed > 0) {
	if (close_job (job)) {
	    delete_marked_files ();
	    exit (0);
	}
	else
	    exit (1);
    }
    else
	exit (1);
    exit (0);
}
