small changes in interactive use
[ust.git] / ust.c
diff --git a/ust.c b/ust.c
index 350fcf75b0ff81b289affab0648cb2571a032fa9..5570aa576f70a010665a3b245988a6e909841866 100644 (file)
--- a/ust.c
+++ b/ust.c
@@ -1,19 +1,20 @@
-#include <stdlib.h>
-#include <stdio.h>
-#include <stdarg.h>
-#include <unistd.h>
-#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
 #include <getopt.h>
+#include <limits.h>
 #include <signal.h>
-#include <errno.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
 #include <pthread.h>
-#include <fcntl.h>
 #include <sys/ioctl.h>
 
 #ifdef __linux__
  #include <asm/termbits.h>
  #include <asm/ioctls.h>
-#else
+#else /* needed for termios2, which is itself needed for non-standard bauds */
  #include <termios.h>
 #endif
 
 #define LF '\n'
 #define BS '\b'
 #define DEL '\x7f'
-#ifndef __linux__
+#define EOT '\x4'
+#define ESC '\x1b'
+#if defined(CCDTR_IFLOW) && defined(CDSR_OFLOW)
  #define CDTRDSR (CDTR_IFLOW | CDSR_OFLOW)
 #endif
 #define IXONXOFF (IXON | IXOFF)
 
+typedef struct {
+       char find;
+       char input;
+       int offset; /* can only be a 1 or a -1 */
+} Args; 
+
+typedef struct {
+       ssize_t sizebm;
+       ssize_t sizesm;
+} Sizes;
+
 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 settermattr(int dv, const void *strct);
+static void settermspd(unsigned int lispeed, unsigned int lospeed, const 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 unsigned int strtoui(const char *str, const char *msg, unsigned int min);
+static int troptions(CRLFOpt *options, const char *str);
 static int setroptions();
+static inline unsigned int lsbmask(unsigned int n);
 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);
+static inline void replacechar(char *buff, ssize_t size, const Args *args);
+static inline ssize_t addchar(char *buff, ssize_t size, int *scratch, const Sizes *msizes, const Args *args);
+static inline ssize_t rmchar(char *buff, ssize_t sread, int *scratch, const Args *args);
+static void sighandl(int signo);
+static void die(int code, const char *msg, ...);
 
 #ifdef __linux__
  static struct termios2 cntrl, origterm = {0}, newterm = {0};
@@ -53,182 +68,20 @@ static void getcmd(int escape);
  static struct termios cntrl, origterm = {0}, newterm = {0};
 #endif
 
-static const unsigned int uintmax = ~(unsigned int)0;
+static Args bsargs = {DEL, DEL, 0};
+static Args nocrargs = {CR, 0, 0};
+static Args nolfargs = {LF, '\0', 0}; /* null-terminator is for use inside `getcmd()` */
+static Args lfincrargs = {CR, LF, 1};
+static Args crinlfargs = {LF, CR, -1};
+static const unsigned int uintmax = ~(unsigned int)0; /* unsigned -1 to return on error */
 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, 0, '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)
 {
@@ -242,7 +95,7 @@ gettermattr(int dv, void *strct)
 }
 
 int
-settermattr(int dv, void *strct)
+settermattr(int dv, const void *strct)
 {
        #ifdef __linux__
         struct termios2 *optst = (struct termios2*)strct;
@@ -254,7 +107,7 @@ settermattr(int dv, void *strct)
 }
 
 void
-settermspd(unsigned int lispeed, unsigned int lospeed, void *strct)
+settermspd(unsigned int lispeed, unsigned int lospeed, const void *strct)
 {
        #ifdef __linux__
         struct termios2 *optst = (struct termios2*)strct;
@@ -263,7 +116,6 @@ settermspd(unsigned int lispeed, unsigned int lospeed, void *strct)
         optst->c_cflag |= BOTHER;
         optst->c_ispeed = ispeed;
         optst->c_ospeed = ospeed;
-
        #else
         struct termios *optst = (struct termios*)strct;
         cfsetispeed(optst, ispeed);
@@ -274,37 +126,37 @@ settermspd(unsigned int lispeed, unsigned int lospeed, void *strct)
 int
 openport()
 {
+       if (verbose) fprintf(stderr, "opening \"%s\"\n", line);
+
        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 (verbose) fprintf(stderr, "checking if \"%s\" is a TTY\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)
+       int flags;
+       flags = fcntl(fd, F_GETFL, 0);
+       /* opened to check with non-blocking mode, now set to blocking */
+       flags &= ~(O_NONBLOCK | O_NDELAY); 
+       if (fcntl(fd, F_SETFL, flags) == -1)
                die(1, "failed to set the device to blocking mode\n");
                
-       if (gettermattr(fd, &cntrl) != 0)
+       if (gettermattr(fd, &cntrl) == -1)
                 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)
+       if (settermattr(fd, &cntrl) == -1)
                 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);
@@ -314,7 +166,7 @@ openport()
        if (parity == 2)
                cntrl.c_cflag |= PARENB | PARODD;
        
-       if (settermattr(fd, &cntrl) != 0)
+       if (settermattr(fd, &cntrl) == -1)
                die(2, "failed to set parity [even: %d, odd: %d]\n", parity & 1, (parity & 2) >> 1);
        
        if (verbose) {
@@ -333,21 +185,25 @@ openport()
        if (hard == 1) {
                cntrl.c_cflag |= CRTSCTS;
        } else if (hard == 2) {
-               #ifdef __linux__
-                fprintf(stderr, "DTR/DSR flow control is not supported on Linux\nenabling this option does nothing!\n");
+               #ifndef CDTRDSR
+                fprintf(stderr, "DTR/DSR flow control is not supported on this platform\n"
+                                "enabling this option does nothing!\n");
                #else
                 cntrl.c_lflag |= CDTRDSR;
                #endif
        } else if (hard == 3) {
                cntrl.c_cflag &= ~CLOCAL;
-               #ifndef __linux__
+               #ifdef CCAR_OFLOW
                 cntrl.c_lflag |= CCAR_OFLOW;
                #endif
        }
 
-       if (settermattr(fd, &cntrl) != 0)
+       if (settermattr(fd, &cntrl) == -1)
                die(2, "failed to set flow control\n");
 
+       cntrl.c_iflag &= ~(ICRNL | INLCR);
+       cntrl.c_oflag &= ~(OCRNL | ONLRET | ONLCR);
+
        if (setroptions())
                die(2, "failed to set cr-lf translation options\n");
 
@@ -367,7 +223,7 @@ openport()
        cntrl.c_cflag &= ~CSIZE;
        cntrl.c_cflag |= db;
 
-       if (settermattr(fd, &cntrl) != 0)
+       if (settermattr(fd, &cntrl) == -1)
                die(2, "failed to set data bits [%c]\n", datab);
        
        if (verbose) fprintf(stderr, "setting stop bits [%d]\n", stopb+1);
@@ -377,8 +233,8 @@ openport()
        else
                cntrl.c_cflag &= ~CSTOPB;
 
-       if (settermattr(fd, &cntrl) != 0)
-                die(1, "failed to set stop bits\n");
+       if (settermattr(fd, &cntrl) == -1)
+                die(1, "failed to set stop bits [%d]\n", stopb+1);
 
        #ifdef __linux__
         ioctl(fd, TCFLSH, TCIOFLUSH);
@@ -389,7 +245,7 @@ openport()
 }
 
 unsigned int
-strtoui(const char *str, const char *msg, const unsigned int min)
+strtoui(const char *str, const char *msg, unsigned int min)
 {
        long t;
        char *endptr;
@@ -408,7 +264,7 @@ strtoui(const char *str, const char *msg, const unsigned int min)
 }
 
 int
-troptions(crlfopt *options, const char *str)
+troptions(CRLFOpt *options, const char *str)
 {
        switch (str[0]) {
                case 'l':
@@ -416,24 +272,27 @@ troptions(crlfopt *options, const char *str)
                                options->lfincr = !options->lfincr; return 0;
                        } else if (strcmp(str, "lf-to-cr") == 0) {
                                options->lftocr = !options->lftocr; return 0;
-                       } else
+                       } 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
+                       } 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
+                       } else {
                                goto trerr;
+                       }
                        break;
                default:
                trerr:
@@ -445,34 +304,24 @@ troptions(crlfopt *options, const char *str)
 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;
+       cntrl.c_iflag ^= (ICRNL & lsbmask(itropts.crtolf));
+       cntrl.c_iflag ^= (INLCR & lsbmask(itropts.lftocr));
+       cntrl.c_oflag ^= (OCRNL & lsbmask(tropts.crtolf));
+       cntrl.c_oflag ^= (ONLRET & lsbmask(tropts.lftocr));
+       cntrl.c_oflag ^= (ONLCR & lsbmask(tropts.crinlf));
 
        return settermattr(fd, &cntrl);
 }
 
+inline unsigned int
+lsbmask(unsigned int n)
+{
+       /* if n == 1, n fills with all 1s, 0 stays the same */ 
+       for (int i = (sizeof(int) * CHAR_BIT); i > 0; i--)
+               n |= (n << 1);
+       return n;
+}
+
 inline int
 termchck(const void *term)
 {
@@ -483,21 +332,25 @@ termchck(const void *term)
         struct termios *iterm = (struct termios*)term; 
         #define TERMIOS_STRUCT termios
        #endif
-       for (size_t i = 0; i < sizeof(struct TERMIOS_STRUCT); i++) {
+       for (size_t i = 0; i < sizeof(struct TERMIOS_STRUCT); i++)
                if (((char*)iterm)[i] != 0)
                        return 1;
-       }
+
        return 0;
 }
 
 void *
 writeport(void *unused)
 {
+       Sizes msizes;
        struct timespec ts;
        ts.tv_sec = swritedelay;
        ts.tv_nsec = nswritedelay;
        wts.tv_sec = 0;
        wts.tv_nsec = chardelay;
+       msizes.sizesm = scratchwsz;
+       msizes.sizebm = wbuffsz;
+
        writebuff = malloc(wbuffsz * sizeof(char));
        scratchw = malloc(scratchwsz * sizeof(int));
        
@@ -522,40 +375,39 @@ writeport(void *unused)
                                        escape = 1; 
                        }
 
-                       if (backspc != tbackspc)
-                               replacechar(writebuff, inln, backspc, tbackspc);
-
+                       if (bsargs.find != bsargs.input)
+                               replacechar(writebuff, inln, &bsargs);
                        if (tropts.nocr)
-                               inln = rmchar(writebuff, scratchw, inln, CR);
+                               inln = rmchar(writebuff, inln, scratchw, &nocrargs);
                        if (tropts.nolf)
-                               inln = rmchar(writebuff, scratchw, inln, LF);
+                               inln = rmchar(writebuff, inln, scratchw, &nolfargs);
                        if (tropts.lfincr)
-                               inln = addchar(writebuff, scratchw, inln, wbuffsz, CR, LF, 1);
+                               inln = addchar(writebuff, inln, scratchw, &msizes, &lfincrargs);
                        
                        if (inln > 1) {
                                for (int i = 0; i <= inln; i++) {
                                        write(fd, &writebuff[i], 1);
                                        nanosleep(&wts, NULL);
                                }
-                       }
-                       else {
+                       } else {
                                write(fd, writebuff, 1);
                        }
                }
                nanosleep(&ts, NULL);
        }
-
-       free(writebuff); free(scratchw);
-       writebuff = NULL; scratchw = NULL;
        return NULL;
 }
 
 void *
 readport(void *unused)
 {
+       Sizes msizes;
        struct timespec ts;
        ts.tv_sec = sreaddelay;
        ts.tv_nsec = nsreaddelay;
+       msizes.sizesm = scratchrsz;
+       msizes.sizebm = rbuffsz;
+
        readbuff = malloc(rbuffsz * sizeof(char));
        scratchr = malloc(scratchrsz * sizeof(int));
        
@@ -566,19 +418,20 @@ readport(void *unused)
        
        interchck();
 
+       /* disable stdout buffering */
        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);
+                               outln = rmchar(readbuff, outln, scratchr, &nocrargs);
                        if (itropts.nolf)
-                               outln = rmchar(readbuff, scratchw, outln, LF);
+                               outln = rmchar(readbuff, outln, scratchr, &nolfargs);
                        if (itropts.crinlf)
-                               outln = addchar(readbuff, scratchw, outln, rbuffsz, LF, CR, -1);
+                               outln = addchar(readbuff, outln, scratchr, &msizes, &crinlfargs);
                        if (itropts.lfincr)
-                               outln = addchar(readbuff, scratchw, outln, rbuffsz, CR, LF, 1);
+                               outln = addchar(readbuff, outln, scratchr, &msizes, &lfincrargs);
 
                        write(STDOUT_FILENO, readbuff, outln);
                }
@@ -586,67 +439,70 @@ readport(void *unused)
        }
        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)
+replacechar(char *buff, ssize_t size, const Args *args)
 {
-       for (int i = 0; i < size; i++) {
-               if (str[i] == find)
-                       str[i] = repl;
-       }
+       for (int i = 0; i < size; i++)
+               if (buff[i] == args->find)
+                       buff[i] = args->input;
 }
-/* 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)
+addchar(char *buff, ssize_t size, int *scratch, const Sizes *sizes, const Args *args)
 {
-       /* turns any signed int into a signed 1
-       int toff = offset & (~(uintmax >> 1) | 1)
-       */
-       int toff = offset;
-       int c = 0;
+       int to, c;
+       c = 0;
+       /* turns negative numbers into 1, positive into 0 */
+       to = ((args->offset >> ((sizeof(int) * CHAR_BIT) - 1)) & 1);
+
        for (int i = 0; i < size; i++) {
-               if (str[i] == find) {
+               if (buff[i] == args->find) {
                        scratch[c] = i;
                        c++;
                }
+               if (c >= sizes->sizesm)
+                       break;
        }
        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;
+       if (size == 1 && c == 1 && scratch[0] == 0) {
+               buff[to ^ 1] = args->input;
+               buff[to] = args->find;
+               return 2;
        }
-       for (int i = c; i > 0; i--) {
-               for (int s = size; s >= scratch[i]; s--)
-                       str[s + toff] = str[s];
-               str[scratch[i]] = inp;
+       if ((size + c) > sizes->sizebm)
+               c = sizes->sizebm - size;
+
+       for (int i = c - 1; i >= 0; i--) {
+               int t = scratch[i];
+               memmove(&buff[t] + 1, &buff[t], (size + c) - t);
+               buff[t + (to ^ 1)] = args->input;
        }
+       printf("%s\n", buff);
        return (size + c);
 }
 
 inline ssize_t __attribute__((hot))
-rmchar(char *str, int *scratch, ssize_t size, const char find)
+rmchar(char *buff, ssize_t size, int *scratch, const Args *args)
 {
        int c = 0;
        for (int i = 0; i < size; i++) {
-               if (str[i] == find) {
+               if (buff[i] == args->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];
+       if (size == 1 && c == 1 && scratch[0] == 0)
+               return 0;
+
+       for (int i = c; i >= 0; i--) {
+               int t = scratch[i];
+               memmove(&buff[t], &buff[t] + 1, (size + c) - t);
        }
        return(size - c);
 }
@@ -670,17 +526,16 @@ interchck()
 
                newterm.c_iflag |= INLCR;
                newterm.c_cc[VMIN] = minchars;
-               newterm.c_cc[VTIME] = chardelay;
                newterm.c_cc[VINTR] = _POSIX_VDISABLE;
                newterm.c_cc[VSUSP] = _POSIX_VDISABLE;
                newterm.c_cc[VQUIT] = _POSIX_VDISABLE;
 
                if (backspace)
-                       tbackspc = BS;
+                       bsargs.input = BS;
                else
-                       tbackspc = DEL;
+                       bsargs.input = DEL;
 
-               backspc = origterm.c_cc[VERASE];
+               bsargs.find = origterm.c_cc[VERASE];
 
                if (settermattr(STDOUT_FILENO, &newterm) < 0)
                        die(1, "failed to set terminal attributes\n");
@@ -703,11 +558,15 @@ cechck()
 void
 getcmd(int escape)
 {
+       Sizes msizes;
+       msizes.sizesm = scratchwsz;
+       msizes.sizebm = wbuffsz;
+
        char cmdchar;
        char ttr[64];
        unsigned int tspd;
 
-       if (isatty(STDIN_FILENO) || isatty(STDOUT_FILENO))
+       if (isatty(STDIN_FILENO) && isatty(STDOUT_FILENO))
                interactive = 1;
 
        interchck();
@@ -718,152 +577,154 @@ getcmd(int escape)
                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);
-                                       }
-                               }
+       case EOT: /* FALLTHROUGH */
+       case '.':
+               die(0,"\n[EOT]\n");
+               break;
+       case 'b':
+               if (backspace)
+                       bsargs.input = DEL;
+               else
+                       bsargs.input = BS;
+               backspace ^= 1;
+               break;
+       case 'h':
+               if (half)
+                       newterm.c_lflag &= ~ECHO;
+               else
+                       newterm.c_lflag |= ECHO;
+               half ^= 1;
+               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 ^= 1;
+               if (settermattr(STDOUT_FILENO, &newterm) < 0)
+                       die(1, "failed to set terminal attributes\n");
+               break;
+       case 'w':
+               cechck();
+               if (fgets(ttr, sizeof(ttr), stdin) != NULL) {
+                       replacechar(ttr, 63, &nolfargs);
+                       ssize_t frln;
+                       int wfd = open(ttr, O_RDONLY);
+                       if (wfd == -1) {
+                               perror("error opening file");
+                               goto finish;
                        }
-                       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;
+                       while ((frln = read(wfd, writebuff, wbuffsz - 1)) > 0) {
+                               for (int i = 0; i <= frln; i++) {
+                                       if (tropts.nocr)
+                                               frln = rmchar(writebuff, frln, scratchw, &nocrargs);
+                                       if (tropts.nolf)
+                                               frln = rmchar(writebuff, frln, scratchw, &nolfargs);
+                                       if (tropts.lfincr)
+                                               frln = addchar(writebuff, frln, scratchw, &msizes, &lfincrargs);
+       
+                                       write(fd, &writebuff[i], 1);
+                                       nanosleep(&wts, NULL);
                                }
                        }
-                       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");
+               }
+               goto finish;
+       case 's':
+               cechck();
+               if (fgets(ttr, sizeof(ttr), stdin) != NULL) {
+                       replacechar(ttr, 63, &nolfargs);
+                       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);
                        }
-
-                       nanosleep(&ts, NULL);
-
-                       st ^= TIOCM_DTR;
-                       if (ioctl(fd, TIOCMSET, &st) != 0) {
-                               fprintf(stderr, "failed to set DTR [negation]\n");
+               }
+               goto finish;
+       case 'd':
+               cechck();
+               if (fgets(ttr, sizeof(ttr), stdin) != NULL) {
+                       replacechar(ttr, 63, &nolfargs);
+                       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, &nolfargs);
+                       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, &nolfargs);
+                       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;
+               if (settermattr(STDIN_FILENO, &newterm) == -1)
+                       fprintf(stderr, "failed to restore echo and/or canonical mode\n");
+               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;
-               case BS:
-                       break;
-               case DEL:
-                       break;
-               default:
-                       fprintf(stderr, "not a valid command [%c]\n", cmdchar);
+               }
+               st ^= TIOCM_DTR;
+               if (ioctl(fd, TIOCMSET, &st) != 0) {
+                       fprintf(stderr, "failed to set DTR [assertion]\n");
                        break;
+               }
+               nanosleep(&ts, NULL);
+               
+               st ^= TIOCM_DTR;
+               if (ioctl(fd, TIOCMSET, &st) != 0) 
+                       fprintf(stderr, "failed to set DTR [negation]\n");
+               break;
+       case BS: /* FALLTHROUGH */
+       case DEL:
+       case ESC:
+               break;
+       default:
+               fprintf(stderr, "not a valid command [%c]\n", cmdchar);
+               break;
        }
        return;
 }
 
 void
-sighandl(const int signo)
+sighandl(int signo)
 {
        die(128 + signo, "\nrecieved signal [%d], exiting\n", signo);
 }
 
 void
-die(const int code, const char *msg, ...)
+die(int code, const char *msg, ...)
 {
        va_list fpa;
        if (fd != -1)
@@ -887,3 +748,174 @@ die(const int code, const char *msg, ...)
        
        exit(code);
 }
+
+int
+main(int argc, char **argv)
+{
+       char *t = NULL;
+       /* glibc's `asprintf()` won't set the string to NULL on failure automatically */
+       for (int i = 1; i < argc; i++ ) {
+               if (argv[i][0] == '-' && argv[i][1] >= '0' && argv[i][1] <= '9') {
+                       asprintf(&t, "-s%s", argv[i] + 1);
+                       if (!t) {
+                               fprintf(stderr, "cannot convert -# to -s#\n");
+                               break;
+                       }
+                       argv[i] = t;
+               }
+       }
+
+       static struct option longopt[] = {
+               {"line", 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-rts-cts", no_argument, NULL, 'R'},
+               {"hardware-dtr-dsr", 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, rxspdset, devset, c;
+       oind = rxspdset = devset = 0;
+       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 ^= 1; break;
+               case 'c':
+                       canonical ^= 1; break;
+               case 'R':
+                       hard ^= 1; break;
+               case 'r':
+                       hard ^= 2; break;
+               case 'X':
+                       soft ^= 1; break;
+               case 'd':;
+                       char* endptr;
+                       long t;
+                       t = strtol(optarg, &endptr, 10);
+                       if (errno != 0 || *endptr != '\0' || t < 0) {
+                               fprintf(stderr, "invalid character delay\n");
+                               goto ustusage;
+                       } else {
+                               chardelay = t;
+                       }
+                       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 ^= 1; break;
+               case 'i':
+                       tui = strtoui(optarg, "invalid rx speed: %s\n", 1);
+                       if (tui == uintmax)
+                               goto ustusage;
+                       ispeed = tui;
+                       rxspdset = 1;
+                       break;
+               case 's':
+                       tui = strtoui(optarg, "invalid speed: %s\n", 1);
+                       if (tui == uintmax)
+                               goto ustusage;
+                       ospeed = tui;
+                       break;
+               case 'S':
+                       stopb ^= 1; 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, '/')) {
+                               strcpy(line, optarg);
+                               devset = 1;
+                       } else {
+                               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 ^= 1; break;
+               default:
+               ustusage:
+                       die(2, "usage: %s [--line|-l line] [--speed|-s #|-#] [--rx-speed|-i #]\n"
+                                       "           [--data-bits|-D #] [--stop-bits|-S]"
+                                       " [--even|-e] [--odd|-o]\n"
+                                       "           [--hardware-rts-cts|-R]"
+                                       " [--hardware-dtr-dsr|-r] [--software|-X]\n"
+                                       "           [--delay|-d #] [--min-chars|-m #]"
+                                       " [--canonical|-c] [--echo|-h]\n"
+                                       "           [--translation|-t option]"
+                                       " [--input-translation|-T option]\n"
+                                       "           [--verbose|-v] [--backspace|-b]\n",
+                                       argv[0]);
+                       break;
+               }
+       }
+       /* 
+        * not the best practice, but in order for this `free()`
+        * to not get executed ust needs to be killed, 
+        * which means the OS *should* free the memory
+        */
+       free(t);
+
+       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);
+}