]> git.datadissipation.net Git - ust.git/commitdiff
initial public commit master
authorgit <git@git.datadissipation.net>
Sat, 14 Feb 2026 15:13:48 +0000 (10:13 -0500)
committergit <git@git.datadissipation.net>
Sat, 14 Feb 2026 15:13:48 +0000 (10:13 -0500)
Makefile [new file with mode: 0644]
config.def.h [new file with mode: 0644]
ust.c [new file with mode: 0644]

diff --git a/Makefile b/Makefile
new file mode 100644 (file)
index 0000000..bb881e6
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,37 @@
+VERSION = 0.1.0
+
+TARGET = ust
+SRC = ust.c
+OBJ = ${SRC:.c=.o}
+
+PREFIX = /usr/local
+
+all: ${TARGET}
+
+ust.c: config.h
+
+config.h:
+       cp config.def.h config.h
+
+clean:
+       rm -f ${OBJ}
+       rm -f *.[oa] ${TARGET}
+
+install:
+       mkdir -p ${PREFIX}/bin
+       cp -f ${TARGET} ${PREFIX}/bin
+       chmod 755 ${PREFIX}/bin/${TARGET}
+
+uninstall:
+       rm -f ${PREFIX}/bin/${TARGET}
+
+
+CFLAGS = -std=c99 -Wall -pthread -O2
+LDFLAGS = 
+
+# comment out if not on linux
+CFLAGS += -D_GNU_SOURCE
+
+CC = cc
+
+.PHONY: all clean install uninstall 
diff --git a/config.def.h b/config.def.h
new file mode 100644 (file)
index 0000000..1f48d92
--- /dev/null
@@ -0,0 +1,101 @@
+/* software buffers */
+const size_t wbuffsz = 1024;
+const size_t rbuffsz = 1024;
+
+/* scratch-pads for input processing */
+const size_t scratchwsz = 256;
+const size_t scratchrsz = 256;
+
+/* `nanosleep()` values inside the read and write loops */
+const long swritedelay = 0;
+const long nswritedelay = 1000000;
+
+const long sreaddelay = 0;
+const long nsreaddelay = 1000000;
+
+/* `nanosleep()` values for the DTR pulse */
+const long spulsedelay = 0;
+const long nspulsedelay = 1000000;
+
+/* escape character for interactive use */
+const char escapechar = '~';
+
+/* print additional info to stderr */
+int verbose = 0; /*(0|1)*/
+
+/*
+ * device name:
+ * on Linux, you have `ttyS#` and `ttyUSB#`
+ * on BSD (MacOS included) systems `cuau#` and `cuaU#` (uppercase for USB TTYs) should be used
+ */
+char line[16] = "/dev/cuau0";
+
+/*
+ * speed is usually configurable to standard values only, 
+ * custom speed requires both hardware and driver support
+ */
+unsigned int ispeed = 9600;
+unsigned int ospeed = 9600;
+
+/*
+ *  data bits: can be 8,7,6 or 5.
+ *  9 is not supported
+ */
+char datab = '8';
+
+/* 
+ * stop bits: setting this to 1 enables 2 stop bits for all numbers of data bits
+ * except 5 - it's 1.5 in that case 
+ */
+int stopb = 0; /*(0|1)*/
+
+/* 
+ * local echo (half-duplex mode) and canonical mode
+ */
+int half = 0; /*(0|1)*/
+int canonical = 0; /*(0|1)*/
+
+
+/*
+ * CR and LF (NL) translation options
+ */
+
+typedef struct{
+       int crinlf;
+       int crtolf;
+       int lfincr;
+       int lftocr;
+       int nocr;
+       int nolf;
+} crlfopt;
+
+crlfopt tropts = {0};
+crlfopt itropts = {0};
+
+/*
+ * backspace: setting to one makes `^H` the backspace character, `^?` by default
+ */
+int backspace = 0; /*(0|1)*/
+
+int minchars = 1;
+int chardelay = 0;
+
+/*
+ * XON/XOFF flow control
+ * can be enabled with hardware flow control simultaneously
+ */
+int soft = 0; /*(0|1)*/
+
+/*
+ * hardware flow control
+ * 1 - RTS/CTS
+ * 2 - DTR/DSR (does not work on Linux)
+ * 3 - DCD
+ */
+int hard = 0; /*(0|1|2|3)*/ 
+
+/*
+ * 1 - even
+ * 2 - odd
+ */
+int parity = 0; /*(0|1|2)*/
diff --git a/ust.c b/ust.c
new file mode 100644 (file)
index 0000000..350fcf7
--- /dev/null
+++ b/ust.c
@@ -0,0 +1,889 @@
+#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'
+#ifndef __linux__
+ #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, 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)
+{
+        #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) {
+               #ifdef __linux__
+                fprintf(stderr, "DTR/DSR flow control is not supported on Linux\nenabling this option does nothing!\n");
+               #else
+                cntrl.c_lflag |= CDTRDSR;
+               #endif
+       } else if (hard == 3) {
+               cntrl.c_cflag &= ~CLOCAL;
+               #ifndef __linux__
+                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)
+{
+       /* turns any signed int into a signed 1
+       int toff = offset & (~(uintmax >> 1) | 1)
+       */
+       int toff = 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 + toff] = 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[VTIME] = chardelay;
+               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);
+}