/* lp -- print files on a line printer */

#include	"lp.h"

SCCSID("@(#)lp.c	2.1	")

char work[FILEMAX];	/* Global error message string */
struct stat stbuf;	/* Global stat buffer */
char title[TITLEMAX + 1] = NULL;	/* User-supplied title for output */
int copies = 1;		/* number of copies of output */
int optlen = 0;		/* size of option string for interface  program */
char opts[OPTMAX] = NULL;	/* options for interface program */
int silent = 0;		/* don't run off at the mouth */
short mail = FALSE;	/* TRUE ==> user wants mail, FALSE ==> no mail */
short wrt = FALSE;	/* TRUE ==> user wants notification on tty via write,
			   FALSE ==> don't write */
short copy = FALSE;	/* TRUE ==> copy files, FALSE ==> don't */
char curdir[FILEMAX+1];	/* working directory at time of request */

int nfiles = 0;		/* number of files on cmd line (excluding "-") */
int fileargs = 0;	/* total number of file args */
int stdinp = 0;		/* indicates how many times to print std input
			   -1 ==> standard input empty		*/
short slocked = FALSE;	/* TRUE ==> sequence file is locked, FALSE ==> isn't */

char tname[RNAMEMAX] = NULL;	/* name of temp request file */
FILE *tfile = NULL;		/* stream for temp request file */
char rname[RNAMEMAX] = NULL;	/* name of actual request file */
char stdbase[NAMEMAX];	/* basename of copy of standard input */
char reqid[IDSIZE + 1];	/* request id to be supplied to user */

struct outq o = {	/* output request to be appended to output queue */
	NULL,	/* destination */
	NULL,	/* logname */
	0,	/* sequence # */
	0L,	/* size of request */
	NULL,	/* device where printing */
	0L,	/* date of request */
	0	/* not printing, not deleted */
};

main(argc, argv)
int argc;
char *argv[];
{
	struct qstat q;		/* acceptance status */

	startup(argv[0]);

	options(argc, argv);	/* process command line options */

	if(chdir(SPOOL) == -1)
		fatal("spool directory non-existent", 1);

	defaults();		/* establish default parameters */

	if(getqdest(&q, o.o_dest) == EOF) {	/* get acceptance status */
		sprintf(work,
		  "acceptance status of destination \"%s\" unknown", o.o_dest);
		fatal(work, 1);
	}
	endqent();
	if(! q.q_accept) {	/* accepting requests ? */
		sprintf(work,
			"can't accept requests for destination \"%s\" -\n\t%s",
			o.o_dest, q.q_reason);
		fatal(work, 1);
	}

	openreq();		/* create and init request file */

	putrent(R_TITLE, title, tfile);
	sprintf(work, "%d", copies);
	putrent(R_COPIES, work, tfile);
	putrent(R_OPTIONS, opts, tfile);

	if(stdinp > 0)
		savestd();	/* save standard input */

	files(argc, argv);	/* process command line file arguments */

	closereq();		/* complete and then close request file */

	time(&o.o_date);
	o.o_size *= copies;
	signal(SIGHUP, SIG_IGN);
	signal(SIGINT, SIG_IGN);
	signal(SIGQUIT, SIG_IGN);
	signal(SIGTERM, SIG_IGN);
	addoent(&o);		/* enter request in output queue */
	endoent();
	sprintf(work, "%s %d %s", o.o_dest, o.o_seqno, o.o_logname);
	enqueue(F_REQUEST, work);	/* notify scheduler of queued output */

	qmesg();		/* issue request id message */

	exit(0);
}
/* catch -- catch signals */

catch()
{
	int cleanup();
	signal(SIGHUP, SIG_IGN);
	signal(SIGINT, SIG_IGN);
	signal(SIGQUIT, SIG_IGN);
	signal(SIGTERM, SIG_IGN);
	cleanup();
	exit(1);
}
/* cleanup -- called by catch() after interrupts or by fatal() after errors */

cleanup()
{
	/* Unlink request and data files (if any) */

	rmreq(o.o_dest, o.o_seqno);

	/* Unlock lock files and temp lock file */

	tunlock();
	if(slocked)
		unlock(SEQLOCK);
	endoent();
	endqent();
}
/* closereq -- complete and close request file */

closereq()
{
	if(fileargs == 0)
		putrent(R_FILE, stdbase, tfile);

	if(mail)
		putrent(R_MAIL, o.o_logname, tfile);
	if(wrt)
		putrent(R_WRITE, o.o_logname, tfile);

	/* Rename temporary request file to real request file name */

	fclose(tfile);
	if(link(tname, rname) == -1) {
		sprintf(work, "can't create request file %s", rname);
		fatal(work, 1);
	}
	unlink(tname);
}
/*
 * copyfile(stream, name) -- copy stream to file "name"
 *	name is actually just the basename of a file
 *	the file is created under REQUEST/o.o_dest/
 */

copyfile(stream, name)
FILE *stream;
char *name;
{
	FILE *ostream;
	int i;
	char buf[BUFSIZ];

	sprintf(work, "%s/%s/%s", REQUEST, o.o_dest, name);
	if((ostream = fopen(work, "w")) == NULL) {
		sprintf(work, "cannot create temp file %s", name);
		fatal(work, 1);
	}

	chmod(work, 0444);
	while((i = fread(buf, sizeof(char), BUFSIZ, stream)) > 0)
		fwrite(buf, sizeof(char), (unsigned) i, ostream);

	fclose(ostream);
}
/* defaults -- establish default destination if not set on command line */

defaults()
{
	char *d, *getenv(), *c, *strcpy();
	FILE *dflt;	/* stream for reading default destination file */

	if(o.o_dest[0] == '\0') {	/* avoid optimiser bug */
		if((d = getenv(LPDEST)) != NULL && *d != '\0') {
			if(strlen(d) > DESTMAX) {
				sprintf(work,
				  "%s destination \"%s\" illegal",
				  LPDEST, d);
				fatal(work, 1);
			}
			strcpy(o.o_dest, d);
		}
		else {
			if((dflt = fopen(DEFAULT, "r")) == NULL)
				fatal("can't open default destination file",1);
			if(fgets(o.o_dest, DESTMAX+1, dflt) == NULL) {
				fatal("no system default destination", 1);
			}
			fclose(dflt);
			if(*(c = o.o_dest + strlen(o.o_dest) -1) == '\n')
				*c = '\0';
		}

		if(! isdest(o.o_dest)) {
			sprintf(work,
				"default destination \"%s\" non-existent",
				o.o_dest);
			fatal(work, 1);
		}
	}
}
/* files -- process command line file arguments */

files(argc, argv)
int argc;
char *argv[];
{
	int i;
	char *fullpath(), *file, *dname, *newname(), *full;
	FILE *f;

	for(i = 1; i < argc; i++) {
		file = argv[i];
		if(file == NULL)
			continue;
		if(strcmp(file, "-") == 0) {
			if(stdinp > 0)
				putrent(R_FILE, stdbase, tfile);
		}
		else {
			if((full = fullpath(file, curdir)) == NULL)
				fatal("can't read current directory", 1);
			dname = newname();
			if(copy) {
				if((f=fopen(full,"r")) != NULL) {
					copyfile(f, dname);
					fclose(f);
				}
				else {
					sprintf(work, "can't open file %s",
					    full);
					fatal(work, 1);
				}
				putrent(R_FILE, dname, tfile);
			}
			else {
				sprintf(work, "%s/%s/%s", REQUEST,
				    o.o_dest, dname);
				if(link(file, work) < 0)
					putrent(R_FILE, full, tfile);
				else
					putrent(R_FILE, dname, tfile);
			}
		}
	}
}
/* getseq(snum) -- get next sequence number */

getseq(snum)
int *snum;
{
	FILE *fp;

	if (trylock(SEQLOCK, LOCKTIME, LOCKTRIES, LOCKSLEEP) == -1)
		fatal("can't lock sequence number file", 1);
	slocked = TRUE;
	if ((fp = fopen(SEQFILE, "r")) != NULL) {
		/* read sequence number file */
		fscanf(fp, "%d\n", snum);
		fp = freopen(SEQFILE, "w", fp);
	}
	else {
		/* can not read file - create a new one */
		if ((fp = fopen(SEQFILE, "w")) == NULL)
			fatal("can't create new sequence number file", 1);
		*snum = 0;
	}

	chmod(SEQFILE, 0644);
	if(++(*snum) == SEQMAX)
		*snum = 1;
	fprintf(fp, "%d\n", *snum);
	fclose(fp);
	unlock(SEQLOCK);
	slocked = FALSE;
}
/* newname() -- create new name for data file
	returns the new name, which is just the base name for the file.
	The file will ultimately be created under SPOOL/REQUEST/o.o_dest/
*/

char *
newname()
{
	static int n = 0;
	static char name[NAMEMAX];

	sprintf(name, "d%d-%d", n++, o.o_seqno);
	return(name);
}
/* openreq -- open and initialize request file */

openreq()
{
	while(tfile == NULL) {
		getseq(&o.o_seqno);
		sprintf(tname, "%s/%s/t-%d", REQUEST, o.o_dest, o.o_seqno);
		sprintf(rname, "%s/%s/r-%d", REQUEST, o.o_dest, o.o_seqno);

		if(eaccess(tname, 0) == -1 && eaccess(rname, 0) == -1 &&
		   (tfile = fopen(tname, "w")) != NULL)
			chmod(tname, 0444);
	}

	sprintf(reqid, "%s-%d", o.o_dest, o.o_seqno);
}
/* options -- process command line options */

#define	OPTMSG	"too many options for interface program"

options(argc, argv)
int argc;
char *argv[];
{
	int i;
	char letter;		/* current keyletter */
	char *value;		/* value of current keyletter (or NULL) */
	char *strcat(), *strncpy(), *file;

	for(i = 1; i < argc; i++) {
		if(argv[i][0] == '-' && (letter = argv[i][1]) != '\0') {
			if(! isalpha(letter)) {
				sprintf(work, "unknown keyletter \"%c\"",
					letter);
				fatal(work, 1);
			}
			letter = tolower(letter);

			value = &argv[i][2];

			switch(letter) {

			case 'c':	/* copy files */
				copy = TRUE;
				break;
			case 'd':	/* destination */
				if(! isdest(value)) {
					sprintf(work,
					  "destination \"%s\" non-existent",
					  value);
					fatal(work, 1);
				}
				strncpy(o.o_dest, value, DESTMAX);
				o.o_dest[DESTMAX] = NULL;
				break;

			case 'm':	/* mail */
				mail = TRUE;
				break;
			case 'n':	/* # of copies */
				if(*value == '\0' || (copies=atoi(value)) <= 0)
					copies = 1;
				break;
			case 'o':	/* option for interface program */
				if(*value != '\0') {
					if(optlen == 0)
						optlen = strlen(value);
					else
						optlen = strlen(value) + 1;
					if(optlen >= OPTMAX)
						fatal(OPTMSG, 1);
					if(*opts != '\0')
						strcat(opts, " ");
					strcat(opts, value);
				}
				break;
			case 's':	/* silent */
				silent = 1;
				break;
			case 't':	/* title */
				strncpy(title, value, TITLEMAX);
				title[TITLEMAX] = '\0';
				break;
			case 'w':	/* write */
				wrt = TRUE;
				break;
			default:
				sprintf(work, "unknown keyletter \"-%c\"",
				  letter);
				fatal(work, 1);
				break;
			}

			argv[i] = NULL;
		}
		else {		/* file name or - */
			fileargs++;
			file = argv[i];
			if(strcmp(file, "-") == 0)
				stdinp++;
			else {
				if(stat(file, &stbuf) == -1) {
					sprintf(work,
					    "can't access file \"%s\"", file);
					goto badf;
				}
				else if((stbuf.st_mode & S_IFMT) == S_IFDIR) {
					sprintf(work,
					    "\"%s\" is a directory", file);
					goto badf;
				}
				else if(stbuf.st_size == 0) {
					sprintf(work, 
					    "file \"%s\" is empty", file);
					goto badf;
				}
				else if(eaccess(argv[i], ACC_R) == -1) {
					sprintf(work,
					    "can't access file \"%s\"", file);
					goto badf;
				}
				else {
					nfiles++;
					o.o_size += stbuf.st_size;
				}
			}
		}
		continue;

badf:		fatal(work, 0);
		argv[i] = NULL;
	}

	if(fileargs == 0)
		stdinp = 1;
	else if(nfiles == 0 && stdinp == 0)
		fatal("request not accepted", 1);
}
/* qmesg -- issue request id message */

qmesg()
{
	if(silent) return;	/* not so elegant but 'twill do */
	printf("request id is %s (", reqid);
	if(nfiles > 0) {
		printf("%d file", nfiles);
		if(nfiles > 1)
			printf("s");
	}
	if(stdinp > 0) {
		if(nfiles > 0)
			printf(" and ");
		printf("standard input");
	}
	printf(")\n");
}
/* savestd -- save standard input */

savestd()
{
	char *newname();

	strcpy(stdbase, newname());
	copyfile(stdin, stdbase);
	sprintf(work, "%s/%s/%s", REQUEST, o.o_dest, stdbase);
	stat(work, &stbuf);
	if(stbuf.st_size == 0) {
		fatal("standard input is empty", 0);
		unlink(work);
		if(nfiles == 0)		/* no files to queue */
			fatal("request not accepted", 1);
		else	/* inhibit printing of std input */
			stdinp = -1;
	}
	else
		o.o_size += (stdinp * stbuf.st_size);
}
/* startup -- initialization routine */

startup(name)
char *name;
{
	int catch(), cleanup();
	extern char *f_name;
	extern int (*f_clean)();

	if(signal(SIGHUP, SIG_IGN) != SIG_IGN)
		signal(SIGHUP, catch);
	if(signal(SIGINT, SIG_IGN) != SIG_IGN)
		signal(SIGINT, catch);
	if(signal(SIGQUIT, SIG_IGN) != SIG_IGN)
		signal(SIGQUIT, catch);
	if(signal(SIGTERM, SIG_IGN) != SIG_IGN)
		signal(SIGTERM, catch);

	umask(0000);
	f_name = name;
	f_clean = cleanup;
	gwd(curdir);			/* get current directory */

	strcpy(o.o_logname, getname());
	strcpy(o.o_dev, "-");
}
