add non-blocking i/o option
[ust.git] / ust.c
diff --git a/ust.c b/ust.c
index 9c2c57f63cc6a318ef76830e2ca8d0458a7b91c1..e88bdccc104a2479dd8571bfeb9c0ab885a4d9c5 100644 (file)
--- a/ust.c
+++ b/ust.c
@@ -1,14 +1,18 @@
+/* see LICENSE for license details */
+#include <sys/ioctl.h>
+
 #include <errno.h>
 #include <fcntl.h>
-#include <getopt.h>
+#include <limits.h>
 #include <signal.h>
 #include <stdarg.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
 #include <unistd.h>
+
+#include <getopt.h>
 #include <pthread.h>
-#include <sys/ioctl.h>
 
 #ifdef __linux__
  #include <asm/termbits.h>
  #include <termios.h>
 #endif
 
-#include "config.h"
-
 #define CR '\r'
 #define LF '\n'
 #define BS '\b'
 #define DEL '\x7f'
+#define EOT '\x4'
 #define ESC '\x1b'
+
+#define IXONXOFF (IXON | IXOFF)
+
 #if defined(CCDTR_IFLOW) && defined(CDSR_OFLOW)
  #define CDTRDSR (CDTR_IFLOW | CDSR_OFLOW)
 #endif
-#define IXONXOFF (IXON | IXOFF)
+
+#ifdef __linux__
+ #define TERMIOS_STRUCT termios2
+#else
+ #define TERMIOS_STRUCT termios
+#endif
 
 typedef struct {
-       ssize_t sizem;
        char find;
        char input;
-       int offset;
+       int offset; /* can only be a 1 or a -1 */
 } Args; 
 
-static int gettermattr(int dv, void *strct);
-static int settermattr(int dv, const void *strct);
-static void settermspd(unsigned int lispeed, unsigned int lospeed, const void *strct);
+typedef struct {
+       ssize_t sizebm;
+       ssize_t sizesm;
+} Sizes;
+
+typedef struct {
+       int crinlf;
+       int crtolf;
+       int lfincr;
+       int lftocr;
+       int nocr;
+       int nolf;
+} CRLFOpt;
+
+/* including here to access CRLFOpt */
+#include "config.h"
+
+static int gettermattr(int dv, struct TERMIOS_STRUCT *optst);
+static int settermattr(int dv, const struct TERMIOS_STRUCT *optst);
+static void settermspd(unsigned int lispeed, unsigned int lospeed, struct TERMIOS_STRUCT *optst);
 static int openport();
 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 int termchck(const void *term);
+static inline unsigned int lsbmask(unsigned int n);
+static inline int termchck(const struct TERMIOS_STRUCT *term);
 static void interchck();
 static void cechck();
 static void *writeport(void *unused);
 static void *readport(void *unused);
 static void getcmd(int escape);
-static inline void replacechar(char *buff, ssize_t sread, const Args *args);
-static inline ssize_t addchar(char *buff, ssize_t sread, int *scratch, const Args *args);
+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};
-#else
- static struct termios cntrl, origterm = {0}, newterm = {0};
-#endif
+static struct TERMIOS_STRUCT cntrl, /* device struct */
+                             origterm = { 0 }, /* controlling terminal structs */
+                             newterm = { 0 };
+
+static Args bsargs = {DEL, DEL, 0};
+static const Args nocrargs = {CR, 0, 0},
+                  nolfargs = {LF, '\0', 0}, /* null-terminator is for use inside `getcmd()` */
+                  lfincrargs = {CR, LF, 1},
+                  crinlfargs = {LF, CR, 0};
 
-static const unsigned int uintmax = ~(unsigned int)0; /* unsigned -1 to return on error */
 static struct timespec wts;
+static const unsigned int uintmax = ~(unsigned int)0; /* unsigned -1 used to return an error */
+static int interactive = 0;
 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
-gettermattr(int dv, void *strct)
+gettermattr(int dv, struct TERMIOS_STRUCT *optst)
 {
         #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, const void *strct)
+settermattr(int dv, const struct TERMIOS_STRUCT *optst)
 {
        #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, const void *strct)
+settermspd(unsigned int lispeed, unsigned int lospeed, struct TERMIOS_STRUCT *optst)
 {
        #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
@@ -115,9 +139,12 @@ settermspd(unsigned int lispeed, unsigned int lospeed, const void *strct)
 int
 openport()
 {
+       int flags;
+       struct flock fl = { 0 };
+
        if (verbose) fprintf(stderr, "opening \"%s\"\n", line);
 
-       fd = open(line, O_RDWR | O_NOCTTY | O_NDELAY);
+       fd = open(line, (O_RDWR | O_NOCTTY | O_NDELAY));
        if (fd == -1) {
                perror("error opening device");
                exit(1);
@@ -128,36 +155,63 @@ openport()
        if (!isatty(fd))
                die(2, "device \"%s\" is not a TTY\n", line);
 
-       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 (blocking) {
+               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) {
+                       perror("fcntl");
+                       die(1, "exiting now\n");
+               }
+       }
+
        if (gettermattr(fd, &cntrl) == -1)
                 die(1, "failed to get device attributes\n");
-       
+
+       fl.l_type = F_WRLCK;
+       fl.l_whence = SEEK_SET;
+       fl.l_start = 0;
+       fl.l_len = 0;
+
+       if (exclusive) {
+               if (fcntl(fd, F_SETLK, &fl) == -1) {
+                       perror("fcntl");
+                       die(1, "failed to lock the device, exiting now\n");
+               }
+       }
+
+       /*
+        * Baud
+        */
        if (verbose) fprintf(stderr, "setting baudrate [RX:%u | TX:%u]\n", ispeed, ospeed);
-       
+
        settermspd(ispeed, ospeed, &cntrl);
        if (settermattr(fd, &cntrl) == -1)
                 die(2,"failed to set baudrate [RX:%u | TX:%u]\n",  ispeed, ospeed);
-
+       
+       /*
+        * Parity
+        */
        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 (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) == -1)
-               die(2, "failed to set parity [even: %d, odd: %d]\n", parity & 1, (parity & 2) >> 1);
-       
+               die(2, "failed to set parity [even: %d, odd: %d]\n",\
+                               parity & 1, (parity & 2) >> 1);
+
+       /*
+        * Flow control
+        */
        if (verbose) {
                /* it's so ugly and beautiful at the same time */
                int t = (((hard & 1) << 1) & (hard & 2)) >> 1;
@@ -190,9 +244,18 @@ openport()
        if (settermattr(fd, &cntrl) == -1)
                die(2, "failed to set flow control\n");
 
+       /*
+        * CR and LF
+        */
+       cntrl.c_iflag &= ~(ICRNL | INLCR);
+       cntrl.c_oflag &= ~(OCRNL | ONLRET | ONLCR);
+
        if (setroptions())
                die(2, "failed to set cr-lf translation options\n");
 
+       /*
+        * Data bits
+        */
        if (verbose) fprintf(stderr, "setting data bits [%c]\n", datab);
 
        tcflag_t db = CS8;
@@ -212,6 +275,9 @@ openport()
        if (settermattr(fd, &cntrl) == -1)
                die(2, "failed to set data bits [%c]\n", datab);
        
+       /*
+        * Stop bits
+        */
        if (verbose) fprintf(stderr, "setting stop bits [%d]\n", stopb+1);
 
        if (stopb)
@@ -222,6 +288,10 @@ openport()
        if (settermattr(fd, &cntrl) == -1)
                 die(1, "failed to set stop bits [%d]\n", stopb+1);
 
+       cntrl.c_cc[VMIN] = 1;
+       cntrl.c_cc[VTIME] = 0;
+       
+       /* flush both hardware buffers */
        #ifdef __linux__
         ioctl(fd, TCFLSH, TCIOFLUSH);
        #else
@@ -237,7 +307,7 @@ strtoui(const char *str, const char *msg, unsigned int min)
        char *endptr;
 
        t = strtol(str, &endptr, 10);
-       if (errno != 0 || *endptr != '\0' || t < min) {
+       if (errno != 0 || *endptr != '\0' || t < min || t > uintmax) {
                fprintf(stderr, msg, str);
                return uintmax;
        }
@@ -246,90 +316,72 @@ strtoui(const char *str, const char *msg, unsigned int min)
         * 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;
+       case 'l':
+               if (!strcmp(str, "lf-in-cr")) {
+                       options->lfincr ^= 1; return 0;
+               } else if (!strcmp(str, "lf-to-cr")) {
+                       options->lftocr ^= 1; return 0;
+               } else {
+                       goto trerr;
+               }
+               break;
+       case 'c':
+               if (!strcmp(str, "cr-in-lf")) {
+                       options->crinlf ^= 1; return 0;
+               } else if (!strcmp(str, "cr-to-lf")) {
+                       options->crtolf ^= 1; return 0;
+               } else {
+                       goto trerr;
+               }
+               break;
+       case 'n':
+               if (!strcmp(str, "no-lf")) {
+                       options->nolf ^= 1; return 0;
+               } else if (!strcmp(str, "no-cr")) {
+                       options->nocr ^= 1; 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;
+       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)
+termchck(const struct TERMIOS_STRUCT *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)
+               if (((char*)term)[i] != 0)
                        return 1;
 
        return 0;
@@ -338,11 +390,18 @@ termchck(const void *term)
 void *
 writeport(void *unused)
 {
-       struct timespec ts;
+       Sizes msizes;
+       struct timespec timeout, ts;
+
+       timeout.tv_sec = stimeout;
+       timeout.tv_nsec = nstimeout;
        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));
        
@@ -353,17 +412,6 @@ writeport(void *unused)
        
        int escape = 0;
 
-       Args bsargs, nocrargs, nolfargs, lfincrargs;
-       bsargs.sizem = wbuffsz;
-       nocrargs = nolfargs  = lfincrargs = bsargs;
-
-       bsargs.find=backspc; bsargs.input=tbackspc;
-
-       nocrargs.find = CR;
-       nolfargs.find = LF;
-
-       lfincrargs.find = CR; lfincrargs.input = LF; lfincrargs.offset = 1;
-
        for (;;) {
                ssize_t inln = read(STDIN_FILENO, writebuff, wbuffsz - 1);
                if (inln > 0) { 
@@ -378,15 +426,14 @@ writeport(void *unused)
                                        escape = 1; 
                        }
 
-                       if (backspc != tbackspc)
+                       if (bsargs.find != bsargs.input)
                                replacechar(writebuff, inln, &bsargs);
-
                        if (tropts.nocr)
                                inln = rmchar(writebuff, inln, scratchw, &nocrargs);
                        if (tropts.nolf)
                                inln = rmchar(writebuff, inln, scratchw, &nolfargs);
                        if (tropts.lfincr)
-                               inln = addchar(writebuff, inln, scratchw, &lfincrargs);
+                               inln = addchar(writebuff, inln, scratchw, &msizes, &lfincrargs);
                        
                        if (inln > 1) {
                                for (int i = 0; i <= inln; i++) {
@@ -396,18 +443,29 @@ writeport(void *unused)
                        } else {
                                write(fd, writebuff, 1);
                        }
+               } else if (inln == 0 && !interactive) {
+                       break;
                }
                nanosleep(&ts, NULL);
        }
+       /* ust kills itself upon receiving a signal so no fancy `nanosleep()` features needed */
+       nanosleep(&timeout, NULL);
+       die(0, "[EOF]\n");
+
+       /* unreachable */
        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));
        
@@ -418,16 +476,6 @@ readport(void *unused)
        
        interchck();
 
-       Args nocrargs, nolfargs, crinlfargs, lfincrargs;
-       nocrargs.sizem = rbuffsz;
-       nolfargs  = crinlfargs = lfincrargs = nocrargs;
-
-       nocrargs.find = CR;
-       nolfargs.find = LF;
-
-       lfincrargs.find = CR; lfincrargs.input = LF; lfincrargs.offset = 1;
-       crinlfargs.find = LF; crinlfargs.input = CR; crinlfargs.offset = -1;
-
        /* disable stdout buffering */
        setvbuf(stdout, NULL, _IONBF, 0);
 
@@ -439,16 +487,16 @@ readport(void *unused)
                        if (itropts.nolf)
                                outln = rmchar(readbuff, outln, scratchr, &nolfargs);
                        if (itropts.crinlf)
-                               outln = addchar(readbuff, outln, scratchr, &crinlfargs);
+                               outln = addchar(readbuff, outln, scratchr, &msizes, &crinlfargs);
                        if (itropts.lfincr)
-                               outln = addchar(readbuff, outln, scratchr, &lfincrargs);
+                               outln = addchar(readbuff, outln, scratchr, &msizes, &lfincrargs);
 
                        write(STDOUT_FILENO, readbuff, outln);
                }
                nanosleep(&ts, NULL);
        }
-       if (isatty(STDIN_FILENO)) 
-               settermattr(STDIN_FILENO, &origterm);
+
+       /* unreachable */
        return NULL;
 }
 
@@ -460,33 +508,38 @@ replacechar(char *buff, ssize_t size, const Args *args)
                        buff[i] = args->input;
 }
 
-/* TODO: optimize the function and allow for offsets greater than 1 */
 inline ssize_t __attribute__((hot))
-addchar(char *buff, ssize_t size, int *scratch, const Args *args)
+addchar(char *buff, ssize_t size, int *scratch, const Sizes *sizes, const Args *args)
 {
-       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 (buff[i] == args->find) {
                        scratch[c] = i;
                        c++;
                }
+               if (c >= sizes->sizesm)
+                       break;
        }
        if (!c)
                return(size);
-       if ((size + c) > args->sizem)
-               c = args->sizem - size;
-       
-       if (scratch[0] == 0 && args->offset < 0) {
-               memmove(&buff[0]+1, &buff[0], size);
-               scratch[0] = 1;
+       if (size == 1 && c == 1)  {
+               buff[to ^ 1] = args->input;
+               buff[to] = args->find;
+               return 2;
        }
-       for (int i = c; i > 0; i--) {
+       if ((size + c) > sizes->sizebm)
+               c = sizes->sizebm - size;
+
+       for (int i = c - 1; i >= 0; i--) {
                int t = scratch[i];
-               for (int s = size; s >= t;s--)
-                       buff[s + args->offset] = buff[s];
-               buff[t] = args->input;
+               memmove(&buff[t] + 1, &buff[t], (size + c) - t);
+               buff[t + (to ^ 1)] = args->input;
        }
+       printf("%s\n", buff);
        return (size + c);
 }
 
@@ -494,7 +547,6 @@ inline ssize_t __attribute__((hot))
 rmchar(char *buff, ssize_t size, int *scratch, const Args *args)
 {
        int c = 0;
-
        for (int i = 0; i < size; i++) {
                if (buff[i] == args->find) {
                        scratch[c] = i;
@@ -503,11 +555,13 @@ rmchar(char *buff, ssize_t size, int *scratch, const Args *args)
        }
        if (!c)
                return(size);
+       if (size == 1 && c == 1)
+               return 0;
 
-       for (int i = c; i > 0; i--)
-               for (int s = size; s >= scratch[i]; s--)
-                       buff[s] = buff[s + 1];
-
+       for (int i = c; i >= 0; i--) {
+               int t = scratch[i];
+               memmove(&buff[t], &buff[t] + 1, (size + c) - t);
+       }
        return(size - c);
 }
 
@@ -530,17 +584,22 @@ interchck()
 
                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;
+               newterm.c_cc[VTIME] = 0;
+               newterm.c_cc[VINTR] = newterm.c_cc[VSUSP]\
+                       = newterm.c_cc[VQUIT] = newterm.c_cc[VLNEXT]\
+                       = newterm.c_cc[VDISCARD] = _POSIX_VDISABLE;
+                               
+               #ifdef VDSUSP
+                newterm.c_cc[VDSUSP] = _POSIX_VDISABLE;
+               #endif
 
                if (backspace)
-                       tbackspc = BS;
+                       bsargs.input = BS;
                else
-                       tbackspc = DEL;
-
-               backspc = origterm.c_cc[VERASE];
+                       bsargs.input = DEL;
 
+               bsargs.find = origterm.c_cc[VERASE];
+               
                if (settermattr(STDOUT_FILENO, &newterm) < 0)
                        die(1, "failed to set terminal attributes\n");
        }
@@ -562,14 +621,15 @@ cechck()
 void
 getcmd(int escape)
 {
-       Args rmlf;
+       Sizes msizes;
        char cmdchar;
        char ttr[64];
        unsigned int tspd;
 
-       rmlf.find = LF; rmlf.input = '\0';
+       msizes.sizesm = scratchwsz;
+       msizes.sizebm = wbuffsz;
 
-       if (isatty(STDIN_FILENO) || isatty(STDOUT_FILENO))
+       if (isatty(STDIN_FILENO) && isatty(STDOUT_FILENO))
                interactive = 1;
 
        interchck();
@@ -580,22 +640,22 @@ getcmd(int escape)
                cmdchar = writebuff[1];
 
        switch (cmdchar) {
+       case EOT: /* FALLTHROUGH */
        case '.':
                die(0,"\n[EOT]\n");
-               break;
        case 'b':
                if (backspace)
-                       tbackspc = DEL;
+                       bsargs.input = DEL;
                else
-                       tbackspc = BS;
-               backspace = !backspace;
+                       bsargs.input = BS;
+               backspace ^= 1;
                break;
        case 'h':
                if (half)
                        newterm.c_lflag &= ~ECHO;
                else
                        newterm.c_lflag |= ECHO;
-               half = !half;
+               half ^= 1;
                if (settermattr(STDOUT_FILENO, &newterm) < 0)
                        die(1, "failed to set terminal attributes\n");
                break;
@@ -604,22 +664,29 @@ getcmd(int escape)
                        newterm.c_lflag &= ~ICANON;
                else
                        newterm.c_lflag |= ICANON;
-               canonical = !canonical;
+               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, &rmlf);
+                       replacechar(ttr, 63, &nolfargs);
                        ssize_t frln;
                        int wfd = open(ttr, O_RDONLY);
-                       if (fd == -1) {
+                       if (wfd == -1) {
                                perror("error opening file");
-                               break;
+                               goto finish;
                        }
                        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);
                                }
@@ -629,7 +696,7 @@ getcmd(int escape)
        case 's':
                cechck();
                if (fgets(ttr, sizeof(ttr), stdin) != NULL) {
-                       replacechar(ttr, 63, &rmlf);
+                       replacechar(ttr, 63, &nolfargs);
                        tspd = strtoui(ttr, "invalid speed\n", 0);
                        if (tspd != uintmax) {
                                ospeed = ispeed = tspd;
@@ -643,7 +710,7 @@ getcmd(int escape)
        case 'd':
                cechck();
                if (fgets(ttr, sizeof(ttr), stdin) != NULL) {
-                       replacechar(ttr, 63, &rmlf);
+                       replacechar(ttr, 63, &nolfargs);
                        chardelay = strtoui(ttr, "invalid delay\n", 0);
                        if (chardelay != uintmax) {
                                wts.tv_sec = 0;
@@ -655,7 +722,7 @@ getcmd(int escape)
                cechck();
                fprintf(stderr, "additional output translation option: ");
                if (fgets(ttr, sizeof(ttr), stdin) != NULL) {
-                       replacechar(ttr, 63, &rmlf);
+                       replacechar(ttr, 63, &nolfargs);
                        if(troptions(&tropts, ttr))
                                goto finish;
                        if (setroptions())
@@ -666,7 +733,7 @@ getcmd(int escape)
                cechck();
                printf("additional input translation option: ");
                if (fgets(ttr, sizeof(ttr), stdin) != NULL) {
-                       replacechar(ttr, 63, &rmlf);
+                       replacechar(ttr, 63, &nolfargs);
                        if (troptions(&itropts, ttr))
                                goto finish;    
                        if (setroptions())
@@ -753,7 +820,7 @@ main(int argc, char **argv)
                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");
+                               fprintf(stderr, "%s: cannot convert -# to -s#\n", argv[0]);
                                break;
                        }
                        argv[i] = t;
@@ -774,6 +841,8 @@ main(int argc, char **argv)
                {"data", required_argument, NULL, 'D'},
                {"delay", required_argument, NULL, 'd'},
                {"min-chars", required_argument, NULL, 'm'},
+               {"timeout-ns", required_argument, NULL, 'f'},
+               {"timeout-s", required_argument, NULL, 'F'},
                {"stop-bits", no_argument, NULL, 'S'},
                {"backspace", no_argument, NULL, 'b'},
                {"translation", required_argument, NULL, 't'},
@@ -785,7 +854,7 @@ main(int argc, char **argv)
        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) {
+       while ((c = getopt_long(argc, argv, "D:F:T:d:f:i:l:m:s:t:RXbcehorv", longopt, &oind)) != -1) {
                switch (c) {
                case 0: 
                        break;
@@ -799,20 +868,26 @@ main(int argc, char **argv)
                        hard ^= 2; break;
                case 'X':
                        soft ^= 1; break;
-               case 'd':;
+               case 'd': /* FALLTHROUGH */
+               case 'f':
+               case 'F':;
                        char* endptr;
                        long t;
                        t = strtol(optarg, &endptr, 10);
                        if (errno != 0 || *endptr != '\0' || t < 0) {
-                               fprintf(stderr, "invalid character delay\n");
+                               fprintf(stderr, "%s: invalid character delay: %s\n", argv[0], optarg);
                                goto ustusage;
-                       } else {
+                       } else if (c == 'd') {
                                chardelay = t;
+                       } else if (c == 'f'){
+                               nstimeout = t;
+                       } else {
+                               stimeout = t;
                        }
                        break;
                case 'D':
                        if (strlen(optarg) != 1 || !(optarg[0] >= '5' && optarg[0] <= '8')) {
-                               fprintf(stderr, "invalid number of data bits: %s\n", optarg);
+                               fprintf(stderr, "%s: invalid number of data bits: %s\n", argv[0], optarg);
                                goto ustusage;  
                        } else {
                                datab = optarg[0];
@@ -841,11 +916,11 @@ main(int argc, char **argv)
                        stopb ^= 1; break;
                case 'l':
                        if (devset) {
-                               fprintf(stderr, "cannot specify multiple devices\n");
+                               fprintf(stderr, "%s: cannot specify multiple devices\n", argv[0]);
                                goto ustusage;
                        }
                        if (strlen(optarg) > 10) {
-                               fprintf(stderr, "device name too long\n");
+                               fprintf(stderr, "%s: device name too long\n", argv[0]);
                                goto ustusage;
                        }
                        if (strchr(optarg, '/')) {
@@ -881,9 +956,10 @@ main(int argc, char **argv)
                                        " [--hardware-dtr-dsr|-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",
+                                       "           [--translation|-t option]"
+                                       " [--input-translation|-T option]\n"
+                                       "           [--verbose|-v] [--backspace|-b]"
+                                       " [--timeout-s|-F #] [--timeout-ns|-f #]",
                                        argv[0]);
                        break;
                }
@@ -898,13 +974,14 @@ main(int argc, char **argv)
        if (!rxspdset)
                ispeed = ospeed;
 
-       if (isatty(STDIN_FILENO) || isatty(STDOUT_FILENO))
+       if (isatty(STDIN_FILENO) && isatty(STDOUT_FILENO))
                interactive = 1;
 
        signal(SIGHUP, sighandl);
        signal(SIGINT, sighandl);
        signal(SIGQUIT, sighandl);
        signal(SIGTERM, sighandl);
+       signal(SIGCHLD, SIG_DFL);
 
        fd = openport();
        
@@ -913,4 +990,7 @@ main(int argc, char **argv)
        pthread_create(&readthread, NULL, readport, NULL);
        pthread_join(writethread, NULL);
        pthread_join(readthread, NULL);
+
+       /* unreachable */
+       return 0;
 }