#include <stdlib.h>
#include <stdio.h>
#include <stdarg.h>
#include <unistd.h>
#include <string.h>
#include <getopt.h>
#include <signal.h>
#include <errno.h>
#include <pthread.h>
#include <fcntl.h>
#include <sys/ioctl.h>

#ifdef __linux__
 #include <asm/termbits.h>
 #include <asm/ioctls.h>
#else
 #include <termios.h>
#endif

#include "config.h"

#define CR '\r'
#define LF '\n'
#define BS '\b'
#define DEL '\x7f'
#if defined(CCDTR_IFLOW) && defined(CDSR_OFLOW)
 #define CDTRDSR (CDTR_IFLOW | CDSR_OFLOW)
#endif
#define IXONXOFF (IXON | IXOFF)

static int gettermattr(int dv, void *strct);
static int settermattr(int dv, void *strct);
static void settermspd(unsigned int lispeed, unsigned int lospeed, void *strct);
static int openport();
static unsigned int strtoui(const char *str, const char *msg, const unsigned int min);
static int troptions(crlfopt *options, const char *str);
static int setroptions();
static inline int termchck(const void *term);
static void interchck();
static void cechck();
static void sighandl(const int signo);
static void die(const int code, const char *msg, ...);
static void *writeport(void *unused);
static void *readport(void *unused);
static inline void replacechar(char *str, ssize_t size, const char find, const char repl);
static inline ssize_t addchar(char *str, int *scratch, ssize_t size, const size_t bsize, const char find, const char inp, const int offset);
static inline ssize_t rmchar(char *str, int *scratch, ssize_t size, const char find);
static void getcmd(int escape);

#ifdef __linux__
 static struct termios2 cntrl, origterm = {0}, newterm = {0};
#else
 static struct termios cntrl, origterm = {0}, newterm = {0};
#endif

static const unsigned int uintmax = ~(unsigned int)0;
static struct timespec wts;
static char *writebuff = NULL;
static char *readbuff = NULL;
static int *scratchr = NULL;
static int *scratchw = NULL;
static char backspc = DEL,tbackspc = DEL;
static int fd = -1;
static int interactive = 0;

int
main(int argc, char **argv)
{
	for (int i = 1; i < argc; i++ ) {
		if (argv[i][0] == '-' && argv[i][1] >= '0' && argv[i][1] <= '9') {
			char *t = NULL;
			/* glibc's `asprintf()` won't set the string to NULL on failure automatically */
			asprintf(&t, "-s%s", argv[i] + 1);
			if (!t) {
				fprintf(stderr, "cannot convert -# to -s#\n");
				break;
			}
			argv[i] = t;
			free(t);
		}
	}

	static struct option longopt[] = {
		{"device", required_argument, NULL, 'l'},
		{"speed", required_argument, NULL, 's'},
		{"rx-speed", required_argument, NULL, 'i'},
		{"echo", no_argument, NULL, 'h'},
		{"canonical", no_argument, NULL, 'c'},
		{"odd", no_argument, NULL, 'o'},
		{"even", no_argument, NULL, 'e'},
		{"hardware-rtscts", no_argument, NULL, 'R'},
		{"hardware-dsrdtr", no_argument, NULL, 'r'},
		{"software", no_argument, NULL, 'X'},
		{"data", required_argument, NULL, 'D'},
		{"delay", required_argument, NULL, 'd'},
		{"min-chars", required_argument, NULL, 'm'},
		{"stop-bits", no_argument, NULL, 'S'},
		{"backspace", no_argument, NULL, 'b'},
		{"translation", required_argument, NULL, 't'},
		{"input-translation", required_argument, NULL, 'T'},
		{"verbose", no_argument, NULL, 'v'},
		{NULL, 0, NULL, 0}
	};
	 
	unsigned int tui;
	int oind = 0, rxspdset = 0, devset = 0, c;
	while ((c = getopt_long(argc, argv, "bcd:D:ehi:l:m:oRrs:t:T:vX", longopt, &oind)) != -1) {
		switch (c) {
			case 0: 
				break;
			case 'b':
				backspace = !backspace; break;
			case 'c':
				canonical = !canonical; break;
			case 'R':
				hard ^= 1; break;
			case 'r':
				hard ^= 2; break;
			case 'X':
				soft = !soft; break;
			case 'd':
				tui = strtoui(optarg, "invalid delay: %s\n", 0);
				if (tui == uintmax)
					goto ustusage;
				chardelay = tui;
				break;
			case 'D':
				if (strlen(optarg) != 1 || !(optarg[0] >= '5' && optarg[0] <= '8')) {
					fprintf(stderr, "invalid number of data bits: %s\n", optarg);
					goto ustusage;	
				} else {
					datab = optarg[0];
				}
				break;
			case 'e':
				parity ^= 1; break;
			case 'o':
				parity ^= 2; break;
			case 'h':
				half = !half; break;
			case 'i':
				tui = strtoui(optarg, "invalid rx speed: %s\n", 1);
				if (tui == uintmax)
					goto ustusage;
				ispeed = tui;
				rxspdset = !rxspdset;
				break;
			case 's':
				tui = strtoui(optarg, "invalid speed: %s\n", 1);
				if (tui == uintmax)
					goto ustusage;
				ospeed = tui;
				break;
			case 'S':
				stopb = !stopb; break;
			case 'l':
				if (devset) {
					fprintf(stderr, "cannot specify multiple devices\n");
					goto ustusage;
				}
				if (strlen(optarg) > 10) {
					fprintf(stderr, "device name too long\n");
					goto ustusage;
				}
				if (strchr(optarg, '/')) {
					line[0] = '\0';
					strcpy(line, optarg);
					devset = 1;
				} else {
					line[0] = '\0';
					sprintf(line, "/dev/%s", optarg);
					devset = 1;
				}
				break;
			case 'm':
				tui = strtoui(optarg, "invalid number of characters: %s\n", 1);
				if (tui == uintmax)
					goto ustusage;
				minchars = tui;
				break;
			case 't':
				if (troptions(&tropts, optarg))		
					goto ustusage;
				break;
			case 'T':
				if (troptions(&itropts, optarg))
					goto ustusage;
				break;
			case 'v':
				verbose = !verbose; break;
			default:
			ustusage:
				die(2, "usage: ust [--device|-l dev] [--speed|-s #|-#] [--rx-speed|-i #]\n"
						"           [--data-bits|-D #] [--stop-bits|-S]"
						" [--even|-e] [--odd|-o]\n"
						"           [--hardware-rtscts|-R]"
						" [--hardware-dsrdtr|-r] [--software|-X]\n"
						"           [--delay|-d #] [--min-chars|-m #]"
						" [--canonical|-c] [--echo|-h]\n"
						"           [--translation|-t tropt]"
						" [--input-translation|-T tropt]\n"
						"           [--verbose|-v] [--backspace|-b]\n");
				break;
		}
	
	
	}
	if (!rxspdset)
		ispeed = ospeed;

	if (isatty(STDIN_FILENO) || isatty(STDOUT_FILENO))
		interactive = 1;

	signal(SIGHUP, sighandl);
	signal(SIGINT, sighandl);
	signal(SIGQUIT, sighandl);
	signal(SIGTERM, sighandl);

	fd = openport();
	
	pthread_t readthread, writethread;
	pthread_create(&writethread, NULL, writeport, NULL);
	pthread_create(&readthread, NULL, readport, NULL);
	
	pthread_join(writethread, NULL);
	pthread_join(readthread, NULL);
	close(fd);
	return 0;

}

int
gettermattr(int dv, void *strct)
{
	 #ifdef __linux__
	  struct termios2 *optst = (struct termios2*)strct;
	  return ioctl(dv, TCGETS2, optst);
	 #else
	  struct termios *optst = (struct termios*)strct;
	  return tcgetattr(dv, optst);
	 #endif
}

int
settermattr(int dv, void *strct)
{
	#ifdef __linux__
	 struct termios2 *optst = (struct termios2*)strct;
	 return ioctl(dv, TCSETS2, optst);
	#else
	 struct termios *optst = (struct termios*)strct;
	 return tcsetattr(dv, TCSANOW, optst);
	#endif
}

void
settermspd(unsigned int lispeed, unsigned int lospeed, void *strct)
{
	#ifdef __linux__
	 struct termios2 *optst = (struct termios2*)strct;

	 optst->c_cflag &= ~CBAUD;
	 optst->c_cflag |= BOTHER;
	 optst->c_ispeed = ispeed;
	 optst->c_ospeed = ospeed;

	#else
	 struct termios *optst = (struct termios*)strct;
	 cfsetispeed(optst, ispeed);
	 cfsetospeed(optst, ospeed);
	#endif
}

int
openport()
{
	fd = open(line, O_RDWR | O_NOCTTY | O_NDELAY);
	if (fd == -1) {
		perror("error opening device");
		exit(1);
	}

	if (verbose) fprintf(stderr, "opened \"%s\"\n", line);

	if (!isatty(fd))
		die(2, "device \"%s\" is not a TTY\n", line);
	
	if (verbose) fprintf(stderr, "\"%s\" is a TTY\n", line);

	int flags = fcntl(fd, F_GETFL, 0);
	flags &= ~(O_NONBLOCK | O_NDELAY); /* opened to check with non-blocking mode, now set to blocking */

	if (fcntl(fd, F_SETFL, flags) != 0)
		die(1, "failed to set the device to blocking mode\n");
		
	if (gettermattr(fd, &cntrl) != 0)
		 die(1, "failed to get device attributes\n");
	
	if (verbose) fprintf(stderr, "setting baudrate [RX:%u | TX:%u]\n", ispeed, ospeed);
	
	settermspd(ispeed, ospeed, &cntrl);
	if (settermattr(fd, &cntrl) != 0)
	  	 die(2,"failed to set baudrate [RX:%u | TX:%u]\n",  ispeed, ospeed);

	cntrl.c_lflag = 0;
	cntrl.c_iflag &= ~(ISTRIP | BRKINT);

	cntrl.c_cflag &= ~(PARENB | PARODD);

	if (verbose) fprintf(stderr, "setting parity [even: %d, odd: %d]\n", parity & 1, (parity & 2) >> 1);

	if (parity == 1)
		cntrl.c_cflag |= PARENB;
	if (parity == 2)
		cntrl.c_cflag |= PARENB | PARODD;
	
	if (settermattr(fd, &cntrl) != 0)
		die(2, "failed to set parity [even: %d, odd: %d]\n", parity & 1, (parity & 2) >> 1);
	
	if (verbose) {
		/* it's so ugly and beautiful at the same time */
		int t = (((hard & 1) << 1) & (hard & 2)) >> 1;
		fprintf(stderr, "setting flow control [XON/XOFF: %d, RTS/CTS: %d, DTR/DSR: %d, DCD: %d]\n",\
			soft, t ^ ((hard & 1) << 1) >> 1, t ^ (hard & 2) >> 1, t);
	}

	cntrl.c_cflag |= CLOCAL;
	cntrl.c_iflag &= ~IXONXOFF;

	if (soft)
		cntrl.c_iflag |= IXONXOFF;

	if (hard == 1) {
		cntrl.c_cflag |= CRTSCTS;
	} else if (hard == 2) {
		#ifndef CDTRDSR
		 fprintf(stderr, "DTR/DSR flow control is not supported on this platform\nenabling this option does nothing!\n");
		#else
		 cntrl.c_lflag |= CDTRDSR;
		#endif
	} else if (hard == 3) {
		cntrl.c_cflag &= ~CLOCAL;
		#ifdef CCAR_OFLOW
		 cntrl.c_lflag |= CCAR_OFLOW;
		#endif
	}

	if (settermattr(fd, &cntrl) != 0)
		die(2, "failed to set flow control\n");

	if (setroptions())
		die(2, "failed to set cr-lf translation options\n");

	if (verbose) fprintf(stderr, "setting data bits [%c]\n", datab);

	tcflag_t db = CS8;
	switch (datab) {
		case '5':
			db = CS5; break;
		case '6':
			db = CS6; break;
		case '7':
			db = CS7; break;
		default:
			break;
	}
	cntrl.c_cflag &= ~CSIZE;
	cntrl.c_cflag |= db;

	if (settermattr(fd, &cntrl) != 0)
		die(2, "failed to set data bits [%c]\n", datab);
	
	if (verbose) fprintf(stderr, "setting stop bits [%d]\n", stopb+1);

	if (stopb)
		cntrl.c_cflag |= CSTOPB;
	else
		cntrl.c_cflag &= ~CSTOPB;

	if (settermattr(fd, &cntrl) != 0)
		 die(1, "failed to set stop bits\n");

	#ifdef __linux__
	 ioctl(fd, TCFLSH, TCIOFLUSH);
	#else
	 tcflush(fd, TCIOFLUSH);
	#endif
	return(fd);
}

unsigned int
strtoui(const char *str, const char *msg, const unsigned int min)
{
	long t;
	char *endptr;

	t = strtol(str, &endptr, 10);
	if (errno != 0 || *endptr != '\0' || t < min) {
		fprintf(stderr, msg, str);
		return uintmax;
	}
	/* 
	 * conversion like this results in 'undefined behavior' according to C spec,
	 * but almost all compilers will just truncate the value so it's OK
	 */
	return (unsigned int)t;
	/* termios's `speed_t` is a typedef for `unsigned int` */
}

int
troptions(crlfopt *options, const char *str)
{
	switch (str[0]) {
		case 'l':
			if (strcmp(str, "lf-in-cr") == 0) {
				options->lfincr = !options->lfincr; return 0;
			} else if (strcmp(str, "lf-to-cr") == 0) {
				options->lftocr = !options->lftocr; return 0;
			} else
				goto trerr;
			break;
		case 'c':
			if (strcmp(str, "cr-in-lf") == 0) {
				options->crinlf = !options->crinlf; return 0;
			} else if (strcmp(str, "cr-to-lf") == 0) {
				options->crtolf = !options->crtolf; return 0;
			} else
				goto trerr;
			break;
		case 'n':
			if (strcmp(str, "no-lf") == 0) {
				options->nolf = !options->nolf; return 0;
			} else if (strcmp(str, "no-cr") == 0) {
				options->nocr = !options->nocr; return 0;
			} else
				goto trerr;
			break;
		default:
		trerr:
			fprintf(stderr, "invalid translation option: %s\n", str);
			return 1;
	}
}

int
setroptions()
{
	if (itropts.crtolf)
		cntrl.c_iflag |= ICRNL;
	else
		cntrl.c_iflag &= ~ICRNL;

	if (itropts.lftocr)
		cntrl.c_iflag |= INLCR;
	else
		cntrl.c_iflag &= ~INLCR;

	if (tropts.crtolf)
		cntrl.c_oflag |= OCRNL;
	else
		cntrl.c_oflag &= ~OCRNL;

	if (tropts.lftocr)
		cntrl.c_oflag |= ONLRET;
	else
		cntrl.c_oflag &= ~ONLRET;

	if (tropts.crinlf)
		cntrl.c_oflag |= ONLCR;
	else	
		cntrl.c_oflag &= ~ONLCR;

	return settermattr(fd, &cntrl);
}

inline int
termchck(const void *term)
{
	#ifdef __linux__
	 struct termios2 *iterm = (struct termios2*)term; 
	 #define TERMIOS_STRUCT termios2
	#else
	 struct termios *iterm = (struct termios*)term; 
	 #define TERMIOS_STRUCT termios
	#endif
	for (size_t i = 0; i < sizeof(struct TERMIOS_STRUCT); i++) {
	 	if (((char*)iterm)[i] != 0)
			return 1;
	}
	return 0;
}

void *
writeport(void *unused)
{
	struct timespec ts;
	ts.tv_sec = swritedelay;
	ts.tv_nsec = nswritedelay;
	wts.tv_sec = 0;
	wts.tv_nsec = chardelay;
	writebuff = malloc(wbuffsz * sizeof(char));
	scratchw = malloc(scratchwsz * sizeof(int));
	
	if (writebuff == NULL)
		die(1, "buffer allocation failed\n");
	if (scratchw == NULL)
		die(1, "scratch buffer allocation failed\n");
	
	int escape = 0;

	for (;;) {
		ssize_t inln = read(STDIN_FILENO, writebuff, wbuffsz - 1);
		if (inln > 0) {	
			if (escape) {
				getcmd(escape);
				escape = 0;
			}
			if (writebuff[0] == escapechar) {
				if (inln > 1)
					getcmd(escape);
				else
					escape = 1; 
			}

			if (backspc != tbackspc)
				replacechar(writebuff, inln, backspc, tbackspc);

			if (tropts.nocr)
				inln = rmchar(writebuff, scratchw, inln, CR);
			if (tropts.nolf)
				inln = rmchar(writebuff, scratchw, inln, LF);
			if (tropts.lfincr)
				inln = addchar(writebuff, scratchw, inln, wbuffsz, CR, LF, 1);
			
			if (inln > 1) {
				for (int i = 0; i <= inln; i++) {
					write(fd, &writebuff[i], 1);
					nanosleep(&wts, NULL);
				}
			}
			else {
				write(fd, writebuff, 1);
			}
		}
		nanosleep(&ts, NULL);
	}

	free(writebuff); free(scratchw);
	writebuff = NULL; scratchw = NULL;
	return NULL;
}

void *
readport(void *unused)
{
	struct timespec ts;
	ts.tv_sec = sreaddelay;
	ts.tv_nsec = nsreaddelay;
	readbuff = malloc(rbuffsz * sizeof(char));
	scratchr = malloc(scratchrsz * sizeof(int));
	
	if (readbuff == NULL)
		die(1, "buffer allocation failed\n");
	if (scratchr == NULL)
		die(1, "scratch buffer allocation failed\n");
	
	interchck();

	setvbuf(stdout, NULL, _IONBF, 0);

	for (;;) {
		ssize_t outln = read(fd, readbuff, rbuffsz - 1);
		if (outln > 0) {
			if (itropts.nocr)
				outln = rmchar(readbuff, scratchw, outln, CR);
			if (itropts.nolf)
				outln = rmchar(readbuff, scratchw, outln, LF);
			if (itropts.crinlf)
				outln = addchar(readbuff, scratchw, outln, rbuffsz, LF, CR, -1);
			if (itropts.lfincr)
				outln = addchar(readbuff, scratchw, outln, rbuffsz, CR, LF, 1);

			write(STDOUT_FILENO, readbuff, outln);
		}
		nanosleep(&ts, NULL);
	}
	if (isatty(STDIN_FILENO)) 
		settermattr(STDIN_FILENO, &origterm);

	free(readbuff); free(scratchr);
	readbuff = NULL; scratchr = NULL;
	return NULL;
}

inline void __attribute__((hot))
replacechar(char *str, ssize_t size, const char find, const char repl)
{
	for (int i = 0; i < size; i++) {
		if (str[i] == find)
			str[i] = repl;
	}
}
/* TODO: optimize the function and allow for offsets greater than 1 */
inline ssize_t __attribute__((hot))
addchar(char *str, int *scratch, ssize_t size, const size_t bsize,const char find, const char inp, const int offset)
{
	int c = 0;
	for (int i = 0; i < size; i++) {
		if (str[i] == find) {
			scratch[c] = i;
			c++;
		}
	}
	if (!c)
		return(size);
	if ((size + c) > bsize)
		c = bsize - size;
	
	if (scratch[0] == 0) {
		memmove(&str[0]+1, &str[0], size);
		scratch[0] = 1;
	}
	for (int i = c; i > 0; i--) {
		for (int s = size; s >= scratch[i]; s--)
			str[s + offset] = str[s];
		str[scratch[i]] = inp;
	}
	return (size + c);
}

inline ssize_t __attribute__((hot))
rmchar(char *str, int *scratch, ssize_t size, const char find)
{
	int c = 0;
	for (int i = 0; i < size; i++) {
		if (str[i] == find) {
			scratch[c] = i;
			c++;
		}
	}
	if (!c)
		return(size);
	for (int i = c; i > 0; i--) {
		for (int s = size; s >= scratch[i]; s--)
			str[s] = str[s + 1];
	}
	return(size - c);
}

void
interchck()
{
	if (interactive) {
		if (gettermattr(STDIN_FILENO, &origterm) < 0 )
			die(1, "failed to get terminal attributes\n");

		newterm = origterm;

		if (!canonical)
			newterm.c_lflag &= ~ECHO;
		if (!half)
			newterm.c_lflag &= ~ICANON;

		if (soft)
			newterm.c_iflag |= IXONXOFF;

		newterm.c_iflag |= INLCR;
		newterm.c_cc[VMIN] = minchars;
		newterm.c_cc[VINTR] = _POSIX_VDISABLE;
		newterm.c_cc[VSUSP] = _POSIX_VDISABLE;
		newterm.c_cc[VQUIT] = _POSIX_VDISABLE;

		if (backspace)
			tbackspc = BS;
		else
			tbackspc = DEL;

		backspc = origterm.c_cc[VERASE];

		if (settermattr(STDOUT_FILENO, &newterm) < 0)
			die(1, "failed to set terminal attributes\n");
	}
	return;
}

void
cechck()
{
	if ((!half || !canonical) && interactive) {
		newterm.c_lflag |= ECHO;
		newterm.c_lflag |= ICANON;
		if (settermattr(STDIN_FILENO, &newterm) < 0)
			fprintf(stderr, "failed to enable echo and/or canonical mode\n");
	}
	return;
}

void
getcmd(int escape)
{
	char cmdchar;
	char ttr[64];
	unsigned int tspd;

	if (isatty(STDIN_FILENO) || isatty(STDOUT_FILENO))
		interactive = 1;

	interchck();

	if (escape)
		cmdchar = writebuff[0];
	else
		cmdchar = writebuff[1];

	switch (cmdchar) {
		case '.':
			die(0,"\r\n[EOT]\r\n");
			break;
		case 'b':
			if (backspace)
				tbackspc = DEL;
			else
				tbackspc = BS;
			backspace = !backspace;
			break;
		case 'h':
			if (half)
				newterm.c_lflag &= ~ECHO;
			else
				newterm.c_lflag |= ECHO;
			half = !half;
			if (settermattr(STDOUT_FILENO, &newterm) < 0)
				die(1, "failed to set terminal attributes\n");
			break;
		case 'c':
			if (canonical)
				newterm.c_lflag &= ~ICANON;
			else
				newterm.c_lflag |= ICANON;
			canonical = !canonical;
			if (settermattr(STDOUT_FILENO, &newterm) < 0)
				die(1, "failed to set terminal attributes\n");
			break;
		case 'w':
			if (!half || !canonical) {
				newterm.c_lflag |= ECHO;
				newterm.c_lflag |= ICANON;
				settermattr(STDIN_FILENO, &newterm);
			}
			if (fgets(ttr, sizeof(ttr), stdin) != NULL) {
				replacechar(ttr, 63, LF, '\0');
				ssize_t frln;
				int wfd = open(ttr, O_RDONLY);
				if (fd == -1) {
					perror("error opening file");
					break;
				}

				while ((frln = read(wfd, writebuff, wbuffsz - 1)) > 0) {
					for (int i = 0; i <= frln; i++) {
						write(fd, &writebuff[i], 1);
						nanosleep(&wts, NULL);
					}
				}
			}
			goto finish;
		case 's':
			cechck();
			if (fgets(ttr, sizeof(ttr), stdin) != NULL) {
				replacechar(ttr, 63, LF, '\0');
				tspd = strtoui(ttr, "invalid speed\n", 0);
				if (tspd != uintmax) {
					ospeed = ispeed = tspd;
					settermspd(ispeed, ospeed, &cntrl);
					if (settermattr(fd, &cntrl) != 0) {
						fprintf(stderr, "failed to set baudrate [RX:%u | TX:%u]\n", ispeed, ospeed);
					}
				}
			}
			goto finish;
		case 'd':
			cechck();
			if (fgets(ttr, sizeof(ttr), stdin) != NULL) {
				replacechar(ttr, 63, LF, '\0');
				chardelay = strtoui(ttr, "invalid delay\n", 0);
				if (chardelay != uintmax) {
					wts.tv_sec = 0;
					wts.tv_nsec = chardelay;
				}
			}
			goto finish;
		case 't':
			cechck();
			fprintf(stderr, "additional output translation option: ");
			if (fgets(ttr, sizeof(ttr), stdin) != NULL) {
				replacechar(ttr, 63, LF, '\0');
				if(troptions(&tropts, ttr))
					goto finish;
				if (setroptions())
					fprintf(stderr, "could not set new options\n");
					
			}
			goto finish;
		case 'T':
			cechck();
			printf("additional input translation option: ");
			if (fgets(ttr, sizeof(ttr), stdin) != NULL) {
				replacechar(ttr, 63, LF, '\0');
				if (troptions(&itropts, ttr))
					goto finish;	
				if (setroptions())
					fprintf(stderr, "could not set new options\n");	
			}
			finish:
			if (!half && interactive)
				newterm.c_lflag &= ~ECHO;
			if (!canonical && interactive)
				newterm.c_lflag &= ~ICANON;
			settermattr(STDIN_FILENO, &newterm);
			break;
		case 'p':;
			int st;
			struct timespec ts;
			ts.tv_sec = spulsedelay;
			ts.tv_nsec = nspulsedelay;

			if (ioctl(fd, TIOCMGET, &st) != 0) {
				fprintf(stderr, "failed to get port status\n");
				break;
			}
			st ^= TIOCM_DTR;
			if (ioctl(fd, TIOCMSET, &st) != 0) {
				fprintf(stderr, "failed to set DTR [assertion]\n");
			}

			nanosleep(&ts, NULL);

			st ^= TIOCM_DTR;
			if (ioctl(fd, TIOCMSET, &st) != 0) {
				fprintf(stderr, "failed to set DTR [negation]\n");
			}
			break;
		case BS:
			break;
		case DEL:
			break;
		default:
			fprintf(stderr, "not a valid command [%c]\n", cmdchar);
			break;
	}
	return;
}

void
sighandl(const int signo)
{
	die(128 + signo, "\nrecieved signal [%d], exiting\n", signo);
}

void
die(const int code, const char *msg, ...)
{
	va_list fpa;
	if (fd != -1)
		close(fd);

	if (writebuff)
		free(writebuff);
	if (readbuff)
		free(readbuff);
	if (scratchw) 
		free(scratchw);
	if (scratchr)
		free(scratchr);

	if (termchck(&newterm))
		settermattr(STDIN_FILENO, &origterm);

	va_start(fpa, msg);
	vfprintf(stderr, msg, fpa);
	va_end(fpa);
	
	exit(code);
}
