/* 
 * tprint.c - written 06/01/99 by Chad Schwartz 			
 * June 1, 1999 - CWS							
 *   	-- Original Version						
 *	-- Contains support for transparent printing to a vt100		
 *		terminal, in Linux.					
 * 									
 * June 1, 1999 - CWS							
 *	-- Added support for piping data through tprint			
 *		I.E. 'cat /file/to/print | tprint' will have the same	
 *		effect as doing 'tprint /etc/termcap'.  This has	
 *		general usefulness, when the user is going to use	
 *		tprint with a print spooler.				
 *	-- Now, when tprint executes, if the device to transparent 	
 *		in which to print to, is *NOT* listed as the second	
 *		argument to tprint, we assume the tty we're ON.		
 *		I.E. If we're on ttyD0, and we execute the command	
 *		'tprint /etc/termcap', and we're doing this from	
 *		/dev/ttyD0, we will transparent print to /dev/ttyD0	
 *		instead of barfing with an error.			
 *	-- Changed version number to v1.0.1				
 *										
 * June 2, 1999 - CWS							
 *	-- Now, a user can specify a PORT to print to, at the end
 *		of a commandline.  I.E. A sample command line would be  
 *		'cat /etc/termcap | tprint /dev/ttyD0'.  This would	
 *		be useful, if the user wanted to use tprint in a 	
 *		spooler situation, where they are NOT running tprint	
 *		from their terminal directly. (But rather, are using	
 *		the tprint program from a spooler, which doesn't know	
 *		anything about the tty they're on.			
 *	-- Changed version number to v1.0.2				
 *									
 * June 2, 1999 - CWS							
 *	-- Moved the onstr/offstr values out to a defined value		
 *		instead of every time, having to use it directly	
 *		hardcoded in a write(), so that the user can easily	
 *		change what their onstring and offstring values 	
 *		really *ARE*.						
 *	-- Installed signal handler so that if a user presses CTRL-C	
 *		thus generating SIGINT, we can catch it, and send	
 *		OFFSTR to the port, thus returning control to the 	
 *		terminal, and then close off open file FD's, and 	
 *		die gracefully, rather than possibly puking in the	
 *		middle of a transmission, thus locking the terminal	
 *		in an ONSTR transaction.  We also sleep 1 second	
 *		before dying, so that any write that had been pending	
 *		can be flushed, and our OFFSTR write can be sent, 	
 *		before we die.						
 *	-- Changed version number to v1.0.3				
 *									
 * June 7, 1999 - CWS							
 *	-- General Code Changes						
 *	-- Prepared for /etc/digi/tprint.conf				
 *	-- Changed version number to v1.0.4				
 *									
 * June 7, 1999 - CWS/MAL						
 *	-- Folded in Mark's patches for _my_write(), which just		
 *		checks for escape characters, and replaces them		
 *		with the actual octal value for escape, rather than	
 *		allowing it to try to be parsed out, by write() 	
 *		and read()						
 *	-- Folded in MAL's readcfgfile() patches.  Rather than 			
 *		doing this the way _I_ was going to do, MAL 		
 *		completely rewrote the function using an array of	
 *		structs in which to store data, rather than the		
 *		original isthisit() code that I had originally 		
 *		implemented.  This should greatly improve the speed	
 *		and efficiency of the program.				
 *	-- Now, to configure a terminal/printer, one just has to edit	
 *		/etc/digi/tprint.conf, to configure each print device	
 *		separately, rather than being locked into 1 default	
 *		define for MAXCPS, ONSTR, and OFFSTR.			
 *	-- Changed version number to v1.0.5				
 *									
 *	-- Fixed HORRIBLE bug in _my_write() that used a STATIC		
 * 		BUFFER declaration.  Now, I just allocate the buffer	
 *		dynamically.  The problem would manifest itself when	
 *		setting MAXCPS over 255. (Which would overflow the	
 *		buffer, and segfault the program)			
 *	-- Changed version number to v1.0.6				
 *									
 *	-- Added a bunch of error checking on input and output file	
 *		opens - For some reason I hadn't done this earlier, and	
 *		it caused a lot of problems, in that if a user opened	
 *		an output device that didn't exist, we happily wrote	
 *		to FD -1 throughout the entire read/write process.	
 *		I really should've been checking this.			
 *	-- Changed version number to v1.0.7				
 *									
 *	-- Changed path to tprint.conf to /etc/digiepca/tprint.conf	
 *	-- Changed version number to v1.0.8				
 *									
 * June 7, 2001 - CWS/MAL						
 *	-- Per official release on sourceforge, changed path of tprint.conf to 
 *	   /etc/tprint/tprint.conf			
 *	-- Changed version number to v1.1.0 
 */


#define		VERSION		"v1.1.0"
#define 	CFGFILE		"/etc/tprint/tprint.conf"

/* BEGIN PROGRAM */

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>

int infilefd = 0, readret = 1, writeret = 0, outdevfd = 0;
int isthisit = 0, maxcpsint = 0;
FILE *cfgfd;
char *recvq;

/* 
 * cfgelnum holds the config element in the config array we're actively 
 * working on
 */
int cfgelnum;


typedef struct {
	char tty[20];
	int maxcps;
	char onstr[255];
	char offstr[255];
} configrec;

configrec *config = NULL;
int numcfgrec = 0;
char *onstrparsed, *offstrparsed;
char *ttyname(int desc);

int
_my_write(int fd, const char *buf, size_t count) {
	int i, z = 0;
	int buflen;
	int retval;
	char *str;

	buflen = strlen(buf);
	str = (char *) malloc(buflen);
	memset(str, 0, buflen);

	for (i = 0; i < count; i++) {
		if (buf[i] == '\\' && buf[i + 1] == '0' && buf[i + 2] == '3' && buf[i + 3] == '3') {
			str[z++] = 27;
			i += 3;
		} else
			str[z++] = buf[i];
	}

	retval = write(fd, str, z);
	free(str);
	return (retval);
}

int
sighandler(int x, int y) {

	if (infilefd != 0)
		close(infilefd);


	if (outdevfd != 0)
		_my_write(outdevfd, config[cfgelnum].offstr, strlen(config[cfgelnum].offstr));


	printf(" \nSIGINT Received. Flushed buffers, and sent OFFSTR so terminal won't lock.\n");
	close(infilefd);
	close(outdevfd);
	free(recvq);
	printf("Sleeping 1 second, freeing all malloc()'ed memory, then DYING.\n");
	sleep(1);
	exit(-1);
}


int
openinfile(int argc, char *argv[]) {

	if (argc == 2 && strncmp(argv[1], "/dev/", 5) == 0) {
#ifdef CWSDEBUG
		printf("We are opening STDIN for reading.\n");
#endif
		infilefd = open("/dev/stdin", O_RDONLY);
		return (0);
	 }

	if (argc == 1) {
#ifdef CWSDEBUG
		printf("We are opening STDIN for reading.\n");
#endif
		infilefd = open("/dev/stdin", O_RDONLY);
		return (0);
	} else {
#ifdef CWSDEBUG
		printf("opening %s for reading.\n", argv[1]);
#endif
		infilefd = open(argv[1], O_RDONLY);
		if (infilefd == -1) {
			 printf("Fatal Error - Couldn't Open %s\n", argv[1]);
			 exit(-1);
		}
	}
	return (0);
}

void
closeinfile(void) {
	close(infilefd);
}

int
openoutdev(int argc, char *argv[]) {

	if (argc == 3) {
#ifdef CWSDEBUG
		printf("We specified tty! it is %s!\n", argv[2]);
#endif
		outdevfd = open(argv[2], O_RDWR | O_NOCTTY);

		if (outdevfd == -1) {
			printf("Fatal Error - Couldn't open output device %s\n", argv[2]);
			exit(-1);
		}
		return (0);
	}

	if (argc == 2 && strncmp(argv[1], "/dev/", 5) == 0) {
#ifdef CWSDEBUG
		printf("Transparent printing to %s! Specified by user on CLI!\n", argv[1]);
#endif
		outdevfd = open(argv[1], O_RDWR | O_NOCTTY);

		if (outdevfd == -1) {
			printf("Fatal Error - Couldn't open output device %s\n", argv[1]);
			exit(-1);
		}
		return (0);
	 } else {
#ifdef CWSDEBUG
		printf("ttyname is %s\n", ttyname(1));
#endif
		outdevfd = open(ttyname(1), O_RDWR | O_NOCTTY);
		return (0);
	 }

	 if (outdevfd == -1) {
		printf("Cannot open %s! Program Halted.\n", argv[2]);
		exit(-1);
	 }
}

void
closeoutdev(void) {
	 close(outdevfd);
}

int
opencfgfile(void) {

	cfgfd = fopen(CFGFILE, "r");

	if (cfgfd == NULL) {
		printf("Unable to open config file %s!\n", CFGFILE);
		printf("Be sure to create %s! (For info on this, read the\n", CFGFILE);
		printf("INSTALL document that came with this package.)\n");
		exit(-1);
	}
	return (0);
}

void
closecfgfile(void) {
	fclose(cfgfd);
}


int
readcfgfile(int argc, char *argv[]) {
	char buf[255], *ptr, *x;
	int i;

	while (fgets(buf, 255, cfgfd)) {

		/* Skip comments or blank lines in file */

		if (buf[0] == '#' || buf[0] == '\n')
			continue;

		buf[strlen(buf) - 1] = 0;	/* get rid of \n on EOL */

		ptr = strtok(buf, ":");
		if (ptr == NULL)	
			/* If there aren't any :'s then it's not a valid line */
			continue;

		x = strstr(buf, "dev");

		if (x == NULL)
			continue;

		  /* At this point, assume we have a good config line and allocate a structure for this line */

		config = realloc(config, sizeof(configrec) * (numcfgrec + 1));
		memset(&config[numcfgrec], 0, sizeof(configrec));

		/* WATCH OUT!  HACK ALERT... But it works.. */

		*ptr = 0;
		sprintf(config[numcfgrec].tty, "/%s", x);	/* tty */
		*ptr = ':';

		/* END OF UGLY HACK ALERT */

		ptr = strtok(NULL, ":");	/* moves to next field */
		config[numcfgrec].maxcps = atoi(ptr);	/* maxcps */
		ptr = strtok(NULL, ":");	/* moves to next field */
		strcpy(config[numcfgrec].onstr, ptr);	/* onstr */
		ptr = strtok(NULL, ":");	/* moves to next field */
		strcpy(config[numcfgrec].offstr, ptr);	/* offstr */


		numcfgrec++;
	}

	for (i = 0; i < numcfgrec; i++) {

#ifdef CWSDEBUG
		printf("config[%d].tty = %s\n", i, config[i].tty);
		printf("config[%d].maxcps = %d\n", i, config[i].maxcps);
		printf("onstr = ``%s''\n", config[i].onstr);
		printf("offstr = ``%s''\n", config[i].offstr);
		printf("-----------------------------------------\n");
#endif

		if (argc == 1) {
			if (strcmp(ttyname(1), config[i].tty) == 0) {
#ifdef CWSDEBUG
				printf("No TTY specified! Reading from ttyname()!\n");
				printf("ttyname() = %s\n", ttyname(1));
#endif
				return (i);
			}
		}

		if (argc == 2 && strncmp(argv[1], "/dev/", 5) != 0) {
			if (strcmp(ttyname(1), config[i].tty) == 0) {
#ifdef CWSDEBUG
				printf("No TTY Specified, and argv[1] is not a device! Reading from ttyname()!\n");
				printf("ttyname() = %s\n", ttyname(1));
#endif
				return (i);
			}
		}

		if (argc == 2 && strncmp(argv[1], "/dev/", 5) == 0) {
			if (strcmp(argv[1], config[i].tty) == 0) {

#ifdef CWSDEBUG
				printf("We specified TTY on argv[1]! It is %s!\n", argv[1]);
				printf("ttystr=%s maxcpsstr=%s onstrstr=%s offstrstr=%s\n", ttystr, maxcpsstr, onstrstr, offstrstr);
#endif
				return (i);
			}
		}

		if (argc == 3 && strncmp(argv[2], "/dev/", 5) == 0) {
			if (strcmp(argv[2], config[i].tty) == 0) {
#ifdef CWSDEBUG
				printf("We specified TTY on argv[2]! It is %s!\n", argv[2]);
				printf("ttystr=%s maxcpsstr=%s onstrstr=%s offstrstr=%s\n", ttystr, maxcpsstr, onstrstr, offstrstr);
#endif
				return (i);
			}
		}
#ifdef CWSDEBUG
		printf("(%d/[%s/%s]) Nope. Sorry. Wasn't that one.\n", i, ttyname(1), config[i].tty);
#endif
	}

/* If we get here, we didn't find any matches, so lets return -1 */

	return (-1);
}

int
tprint(int argc, char *argv[]) {

#ifdef CWSDEBUG2
	int writeseq = 0;
#endif
	openoutdev(argc, argv);

	while (readret != 0) {

		memset(recvq, 0, sizeof(recvq));
		readret = read(infilefd, recvq, config[cfgelnum].maxcps);
		if (readret == 0) {
#ifdef CWSDEBUG2
			printf("readret: No more to write! Not writing anything.\n");
#endif
			closeoutdev();
			closeinfile();
			exit(0);
		}
#ifdef CWSDEBUG2
		printf("Write Sequence %d\n", writeseq);
#endif

		if (readret != config[cfgelnum].maxcps) {
#ifdef CWSDEBUG2
			printf("End of Read Loop! Only Writing %d chars!\n", readret);
#endif
			_my_write(outdevfd, config[cfgelnum].onstr, strlen(config[cfgelnum].onstr));
			_my_write(outdevfd, recvq, readret);
			_my_write(outdevfd, config[cfgelnum].offstr, strlen(config[cfgelnum].offstr));
		} else {
			_my_write(outdevfd, config[cfgelnum].onstr, strlen(config[cfgelnum].onstr));
			_my_write(outdevfd, recvq, config[cfgelnum].maxcps);
			_my_write(outdevfd, config[cfgelnum].offstr, strlen(config[cfgelnum].offstr));
		}
#ifdef CWSDEBUG2
		++writeseq;
#endif

		sleep(1);
	}

	closeoutdev();
	return (0);
}

int
main(int argc, char *argv[]) {

	int i;

	signal(SIGINT, (void *) sighandler);

	opencfgfile();
	cfgelnum = i = readcfgfile(argc, argv);

	if (i == -1)		/* No matches could be parsed given the supplied data */
		exit(0);

	closecfgfile();
	recvq = (char *) malloc(config[cfgelnum].maxcps + 1);

	openinfile(argc, argv);
	tprint(argc, argv);
	closeinfile();
	free(recvq);
	return (0);
}

