X-Git-Url: https://git.datadissipation.net/ust/blobdiff_plain/5975dfc0d7eddde025db78dc38525eb158386faa..HEAD:/ust.c diff --git a/ust.c b/ust.c index 9c2c57f..4f00a4c 100644 --- a/ust.c +++ b/ust.c @@ -1,124 +1,160 @@ +/* see LICENSE for license details */ +#include + #include -#include -#include +#include #include #include #include #include #include #include + +#include +#include #include -#include #ifdef __linux__ #include #include -#else /* needed for termios2, which is itself needed for non-standard bauds */ +#else /* including for termios2, which is needed for non-standard bauds */ #include #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__ + typedef struct termios2 TermStruct; +#else + typedef struct termios TermStruct; +#endif typedef struct { - ssize_t sizem; + int offset; /* can only be a 1 or a -1 */ char find; char input; - int offset; } 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); -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 void interchck(); -static void cechck(); -static void *writeport(void *unused); -static void *readport(void *unused); +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, TermStruct *optst); +static int settermattr(int dv, const TermStruct *optst); +static void settermspd(unsigned int lispeed, unsigned int lospeed, + TermStruct *optst); +static int openport(void); +static int openparent(void); +static unsigned int strtoui(const char *str, unsigned int min); +static int chcktropts(CRLFOpt *options, const char *str); +static int settropts(void); +static inline unsigned int lsbmask(unsigned int n); +static inline int termvalid(const TermStruct *term); +static void eninter(void); +static void enusrecho(void); +static void *writeport(void *); +static void *readport(void *); 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 ssize_t rmchar(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 TermStruct cntrl, /* device struct */ + /* controlling terminal structs */ + origterm = { 0 }, + newterm = { 0 }; + +static Args bsargs = {0, DEL, DEL}; +static const Args nocrargs = {0, CR, 0}, + /* null-terminator is for use inside `getcmd()` */ + nolfargs = {0, LF, '\0'}, + lfincrargs = {1, CR, LF}, + crinlfargs = {0, LF, CR}; -static const unsigned int uintmax = ~(unsigned int)0; /* unsigned -1 to return on error */ static struct timespec wts; +/* unsigned -1 used to return an error */ +static const unsigned int uintmax = ~(unsigned int)0; +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; +static int pfd = -1; +static long rwc = 0; int -gettermattr(int dv, void *strct) +gettermattr(int dv, TermStruct *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 TermStruct *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, TermStruct *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; + optst->c_ispeed = lispeed; + optst->c_ospeed = lospeed; #else - struct termios *optst = (struct termios*)strct; - cfsetispeed(optst, ispeed); - cfsetospeed(optst, ospeed); + (void)cfsetispeed(optst, lispeed); + (void)cfsetospeed(optst, lospeed); #endif } int -openport() +openport(void) { + int flags; + struct flock fl = { 0 }; + if (verbose) fprintf(stderr, "opening \"%s\"\n", line); - fd = open(line, O_RDWR | O_NOCTTY | O_NDELAY); - if (fd == -1) { + fd = open(line, (O_RDWR | O_NOCTTY | O_NONBLOCK | O_NDELAY)); + if (fd < 0) { perror("error opening device"); exit(1); } @@ -128,41 +164,68 @@ 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 (gettermattr(fd, &cntrl) == -1) + if (blocking) { + flags = fcntl(fd, F_GETFL, 0); + flags &= ~(O_NONBLOCK | O_NDELAY); + + if (fcntl(fd, F_SETFL, flags) < 0) { + perror("fcntl"); + die(1, "exiting now\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) == -1) - die(2,"failed to set baudrate [RX:%u | TX:%u]\n", ispeed, ospeed); + if (exclusive) { + fl.l_type = F_WRLCK; + fl.l_whence = SEEK_SET; + if (fcntl(fd, F_SETLK, &fl) < 0) { + 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) < 0) + 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); - + + if (settermattr(fd, &cntrl) < 0) + 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; - 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); + 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; @@ -175,8 +238,9 @@ openport() cntrl.c_cflag |= CRTSCTS; } else if (hard == 2) { #ifndef CDTRDSR - fprintf(stderr, "DTR/DSR flow control is not supported on this platform\n" - "enabling this option does nothing!\n"); + 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 @@ -187,12 +251,21 @@ openport() #endif } - if (settermattr(fd, &cntrl) == -1) + if (settermattr(fd, &cntrl) < 0) die(2, "failed to set flow control\n"); - if (setroptions()) + /* + * CR and LF + */ + cntrl.c_iflag &= ~(ICRNL | INLCR); + cntrl.c_oflag &= ~(OCRNL | ONLRET | ONLCR); + + if (settropts()) 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; @@ -209,9 +282,12 @@ openport() cntrl.c_cflag &= ~CSIZE; cntrl.c_cflag |= db; - if (settermattr(fd, &cntrl) == -1) + if (settermattr(fd, &cntrl) < 0) 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) @@ -219,118 +295,116 @@ openport() else cntrl.c_cflag &= ~CSTOPB; - if (settermattr(fd, &cntrl) == -1) + if (settermattr(fd, &cntrl) < 0) die(1, "failed to set stop bits [%d]\n", stopb+1); + cntrl.c_cc[VMIN] = 0; + cntrl.c_cc[VTIME] = 0; + + /* flush both hardware buffers */ #ifdef __linux__ ioctl(fd, TCFLSH, TCIOFLUSH); #else tcflush(fd, TCIOFLUSH); #endif - return(fd); + /* returning only for readability */ + return fd; } -unsigned int -strtoui(const char *str, const char *msg, unsigned int min) +int +openparent(void) { + if (verbose) fprintf(stderr, "opening parent terminal\n"); + + pfd = open("/dev/tty", O_RDWR); + if (pfd < 0) { + perror("error opening parent terminal fd"); + die(1, "exiting now\n"); + } + + if (gettermattr(pfd, &origterm) < 0 ) + die(1, "failed to get parent terminal attributes\n"); + + newterm = origterm; + + /* returning only for readability */ + return pfd; +} + +unsigned int +strtoui(const char *str, unsigned int min) +{ long t; char *endptr; t = strtol(str, &endptr, 10); - if (errno != 0 || *endptr != '\0' || t < min) { - fprintf(stderr, msg, str); + if (errno != 0 || *endptr != '\0' || t < min || t > uintmax) { 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) +chcktropts(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 { + return 1; + } + 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 { + return 1; + } + case 'n': + if (!strcmp(str, "no-lf")) { + options->nolf ^= 1; return 0; + } else if (!strcmp(str, "no-cr")) { + options->nocr ^= 1; return 0; + } else { + return 1; + } + default: + return 1; } } int -setroptions() +settropts(void) { - 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) +termvalid(const TermStruct *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; + for (size_t i = 0; i < sizeof(TermStruct); i++) + if (((char*)term)[i] != 0) + return 1; return 0; } @@ -338,11 +412,17 @@ termchck(const void *term) void * writeport(void *unused) { + Sizes msizes; struct timespec ts; + int escape = 0; + 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)); @@ -350,64 +430,66 @@ writeport(void *unused) die(1, "buffer allocation failed\n"); if (scratchw == NULL) die(1, "scratch buffer allocation failed\n"); - - 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) { - if (escape) { - getcmd(escape); - escape = 0; - } - if (writebuff[0] == escapechar) { - if (inln > 1) - getcmd(escape); - else - escape = 1; - } - - if (backspc != tbackspc) - 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); - - if (inln > 1) { - for (int i = 0; i <= inln; i++) { - write(fd, &writebuff[i], 1); - nanosleep(&wts, NULL); - } - } else { - write(fd, writebuff, 1); - } + if (inln > 0) { + if (escape) { + getcmd(escape); + escape = 0; + inln = 0; + } + if (writebuff[0] == escapechar) { + if (inln > 1) + getcmd(escape); + else + escape = 1; + inln = 0; + } + + 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, &msizes, + &lfincrargs); + + if (inln > 1) { + for (int i = 0; i <= inln; i++) { + rwc -= write(fd, &writebuff[i], 1); + nanosleep(&wts, NULL); + } + + } else { + rwc -= write(fd, writebuff, inln); + } + + } else if (inln == 0 && !interactive) { + break; } nanosleep(&ts, 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)); @@ -416,17 +498,7 @@ readport(void *unused) if (scratchr == NULL) die(1, "scratch buffer allocation failed\n"); - 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; + eninter(); /* disable stdout buffering */ setvbuf(stdout, NULL, _IONBF, 0); @@ -434,127 +506,136 @@ readport(void *unused) for (;;) { ssize_t outln = read(fd, readbuff, rbuffsz - 1); if (outln > 0) { - if (itropts.nocr) - outln = rmchar(readbuff, outln, scratchr, &nocrargs); - if (itropts.nolf) - outln = rmchar(readbuff, outln, scratchr, &nolfargs); - if (itropts.crinlf) - outln = addchar(readbuff, outln, scratchr, &crinlfargs); - if (itropts.lfincr) - outln = addchar(readbuff, outln, scratchr, &lfincrargs); - - write(STDOUT_FILENO, readbuff, outln); + if (itropts.nocr) + outln = rmchar(readbuff, outln, scratchr, &nocrargs); + if (itropts.nolf) + outln = rmchar(readbuff, outln, scratchr, &nolfargs); + if (itropts.crinlf) + outln = addchar(readbuff, outln, scratchr, &msizes, + &crinlfargs); + if (itropts.lfincr) + outln = addchar(readbuff, outln, scratchr, &msizes, + &lfincrargs); + + rwc += write(STDOUT_FILENO, readbuff, outln); } nanosleep(&ts, NULL); } - if (isatty(STDIN_FILENO)) - settermattr(STDIN_FILENO, &origterm); + + /* unreachable */ return NULL; } -inline void __attribute__((hot)) +inline void replacechar(char *buff, ssize_t size, const Args *args) { for (int i = 0; i < size; i++) if (buff[i] == args->find) - buff[i] = args->input; + 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) +inline ssize_t +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++; + 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; } return (size + c); } -inline ssize_t __attribute__((hot)) +inline ssize_t 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; - c++; + scratch[c] = i; + c++; } } 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); } void -interchck() +eninter(void) { if (interactive) { - if (gettermattr(STDIN_FILENO, &origterm) < 0 ) - die(1, "failed to get terminal attributes\n"); - - newterm = origterm; - if (!canonical) - newterm.c_lflag &= ~ECHO; + newterm.c_lflag &= ~ECHO; if (!half) - newterm.c_lflag &= ~ICANON; + newterm.c_lflag &= ~ICANON; if (soft) - newterm.c_iflag |= IXONXOFF; + newterm.c_iflag |= IXONXOFF; newterm.c_iflag |= INLCR; newterm.c_cc[VMIN] = minchars; - newterm.c_cc[VINTR] = _POSIX_VDISABLE; - newterm.c_cc[VSUSP] = _POSIX_VDISABLE; - newterm.c_cc[VQUIT] = _POSIX_VDISABLE; + 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; + bsargs.input = DEL; - backspc = origterm.c_cc[VERASE]; - - if (settermattr(STDOUT_FILENO, &newterm) < 0) - die(1, "failed to set terminal attributes\n"); + bsargs.find = origterm.c_cc[VERASE]; + + if (settermattr(pfd, &newterm) < 0) + die(1, "failed to set terminal attributes\n"); } return; } void -cechck() +enusrecho(void) { 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"); + if (settermattr(pfd, &newterm) < 0) + fprintf(stderr, + "failed to enable local echo for user input\n"); } return; } @@ -562,17 +643,14 @@ cechck() void getcmd(int escape) { - Args rmlf; + Sizes msizes; + CRLFOpt *toptptr; char cmdchar; char ttr[64]; unsigned int tspd; - rmlf.find = LF; rmlf.input = '\0'; - - if (isatty(STDIN_FILENO) || isatty(STDOUT_FILENO)) - interactive = 1; - - interchck(); + msizes.sizesm = scratchwsz; + msizes.sizebm = wbuffsz; if (escape) cmdchar = writebuff[0]; @@ -580,105 +658,119 @@ 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; + newterm.c_lflag &= ~ECHO; + else + newterm.c_lflag |= ECHO; + if (settermattr(pfd, &newterm) < 0) + die(1, "failed to set terminal attributes\n"); else - newterm.c_lflag |= ECHO; - half = !half; - if (settermattr(STDOUT_FILENO, &newterm) < 0) - die(1, "failed to set terminal attributes\n"); + half ^= 1; break; case 'c': if (canonical) - newterm.c_lflag &= ~ICANON; + newterm.c_lflag &= ~ICANON; + else + newterm.c_lflag |= ICANON; + if (settermattr(pfd, &newterm) < 0) + die(1, "failed to set terminal attributes\n"); else - newterm.c_lflag |= ICANON; - canonical = !canonical; - if (settermattr(STDOUT_FILENO, &newterm) < 0) - die(1, "failed to set terminal attributes\n"); + canonical ^= 1; break; case 'w': - cechck(); + enusrecho(); if (fgets(ttr, sizeof(ttr), stdin) != NULL) { - replacechar(ttr, 63, &rmlf); - 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); - } - } + ssize_t frln; + int wfd = open(ttr, O_RDONLY); + replacechar(ttr, 63, &nolfargs); + if (wfd < -1) { + perror("error opening file"); + goto finish; + } + while ((frln = read(wfd, writebuff, wbuffsz - 1)) > 0) { + rwc += frln; + 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); + + rwc -= write(fd, &writebuff[i], 1); + nanosleep(&wts, NULL); + } + } } goto finish; case 's': - cechck(); + enusrecho(); if (fgets(ttr, sizeof(ttr), stdin) != NULL) { - replacechar(ttr, 63, &rmlf); - 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); - } + replacechar(ttr, 63, &nolfargs); + tspd = strtoui(ttr, 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); + } else { + fprintf(stderr, "invalid speed: %s\n", ttr); + } } goto finish; case 'd': - cechck(); - if (fgets(ttr, sizeof(ttr), stdin) != NULL) { - replacechar(ttr, 63, &rmlf); - 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: "); + enusrecho(); if (fgets(ttr, sizeof(ttr), stdin) != NULL) { - replacechar(ttr, 63, &rmlf); - if(troptions(&tropts, ttr)) - goto finish; - if (setroptions()) - fprintf(stderr, "could not set new options\n"); + replacechar(ttr, 63, &nolfargs); + chardelay = strtoui(ttr, 0); + if (chardelay != uintmax) { + wts.tv_sec = 0; + wts.tv_nsec = chardelay; + } else { + fprintf(stderr, "invalid delay: %s\n", ttr); + } } goto finish; + case 't': /* FALLTHROUGH */ case 'T': - cechck(); - printf("additional input translation option: "); + enusrecho(); + toptptr = (cmdchar == 't') ? &tropts : &itropts; + fprintf(stderr, "additional translation option: "); if (fgets(ttr, sizeof(ttr), stdin) != NULL) { - replacechar(ttr, 63, &rmlf); - if (troptions(&itropts, ttr)) - goto finish; - if (setroptions()) - fprintf(stderr, "could not set new options\n"); + replacechar(ttr, 63, &nolfargs); + if (chcktropts(toptptr, ttr)) { + fprintf(stderr, + "invalid translation option: %s\n", + optarg); + + goto finish; + } + if (settropts()) + fprintf(stderr, "could not set new options\n"); } finish: if (!half && interactive) - newterm.c_lflag &= ~ECHO; + 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"); + newterm.c_lflag &= ~ICANON; + if (settermattr(pfd, &newterm) < 0) + fprintf(stderr, + "failed to restore echo and/or canonical mode\n"); break; case 'p':; int st; @@ -686,20 +778,20 @@ getcmd(int escape) ts.tv_sec = spulsedelay; ts.tv_nsec = nspulsedelay; - if (ioctl(fd, TIOCMGET, &st) != 0) { - fprintf(stderr, "failed to get port status\n"); - break; + 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"); - break; + 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"); + if (ioctl(fd, TIOCMSET, &st) < 0) + fprintf(stderr, "failed to set DTR [negation]\n"); break; case BS: /* FALLTHROUGH */ case DEL: @@ -722,7 +814,19 @@ void die(int code, const char *msg, ...) { va_list fpa; - if (fd != -1) + + if (termvalid(&origterm)) + settermattr(pfd, &origterm); + + while (rwc > 0) { + int t = rwc; + nanosleep(&wts, NULL); + if (t == rwc) break; + } + + if (pfd < 0) + close(pfd); + if (fd < 0) close(fd); if (writebuff) @@ -734,9 +838,6 @@ die(int code, const char *msg, ...) if (scratchr) free(scratchr); - if (termchck(&newterm)) - settermattr(STDIN_FILENO, &origterm); - va_start(fpa, msg); vfprintf(stderr, msg, fpa); va_end(fpa); @@ -745,18 +846,27 @@ die(int code, const char *msg, ...) } int -main(int argc, char **argv) +main(int argc, char *argv[]) { + long tl; 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; + char *endptr = NULL; + unsigned int tui; + int oind, rxspdset, devset, c; + oind = rxspdset = devset = 0; + + for (int i = 1; i < argc; i++) { + if (argv[i][0] == '-' && argv[i][1] >= '0' && + argv[i][1] <= '9') { + if (asprintf(&t, "-s%s", argv[i] + 1) == -1) { + fprintf(stderr, + "%s: cannot convert -# to -s#\n", + argv[0]); + + break; + } else { + argv[i] = t; + } } } @@ -782,12 +892,10 @@ main(int argc, char **argv) {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) { + while ((c = getopt_long(argc, argv, + "D:T:d:i:l:m:s:t:RXbcehorv", longopt, &oind)) >= 0) { switch (c) { - case 0: + case 0: break; case 'b': backspace ^= 1; break; @@ -799,23 +907,28 @@ main(int argc, char **argv) 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; + case 'd': + tl = strtol(optarg, &endptr, 10); + if (errno != 0 || *endptr != '\0' || tl < 0) { + fprintf(stderr, + "%s: invalid character delay: %s\n", + argv[0], optarg); + + goto ustusage; } else { - chardelay = t; + chardelay = tl; } 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; + if (optarg[1] != '\0' || + !(optarg[0] >= '5' && optarg[0] <= '8')) { + fprintf(stderr, + "%s: invalid number of data bits: %s\n", + argv[0], optarg); + + goto ustusage; } else { - datab = optarg[0]; + datab = optarg[0]; } break; case 'e': @@ -824,81 +937,98 @@ main(int argc, char **argv) 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 'i': /* FALLTHROUGH */ case 's': - tui = strtoui(optarg, "invalid speed: %s\n", 1); - if (tui == uintmax) - goto ustusage; - ospeed = tui; + tui = strtoui(optarg, 1); + if (tui == uintmax) { + fprintf(stderr, + "%s: invalid speed: %s\n", + argv[0], optarg); + + goto ustusage; + } + if (c == 'i') { + ispeed = tui; rxspdset = 1; + } else { + ospeed = tui; + ispeed = rxspdset ? ispeed : ospeed; + } break; case 'S': stopb ^= 1; break; case 'l': if (devset) { - fprintf(stderr, "cannot specify multiple devices\n"); - goto ustusage; + fprintf(stderr, + "%s: cannot specify multiple devices\n", + argv[0]); + + goto ustusage; } - if (strlen(optarg) > 10) { - fprintf(stderr, "device name too long\n"); - goto ustusage; + if (strlen(optarg) > 58) { + fprintf(stderr, "%s: device name too long\n", argv[0]); + goto ustusage; } if (strchr(optarg, '/')) { - strcpy(line, optarg); - devset = 1; + strcpy(line, optarg); + devset = 1; } else { - sprintf(line, "/dev/%s", optarg); - devset = 1; + 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; + tui = strtoui(optarg, 1); + if (tui == uintmax) { + fprintf(stderr, + "%s: invalid number of characters: %s\n", + argv[0], optarg); + + goto ustusage; + } minchars = tui; break; case 't': - if (troptions(&tropts, optarg)) - goto ustusage; + if (chcktropts(&tropts, optarg)) { + fprintf(stderr, + "%s: invalid output translation option: %s\n", + argv[0], optarg); + + goto ustusage; + } break; case 'T': - if (troptions(&itropts, optarg)) - goto ustusage; + if (chcktropts(&itropts, optarg)) { + fprintf(stderr, + "%s: invalid input translation option: %s\n", + argv[0], 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 tropt]" - " [--input-translation|-T tropt]\n" - " [--verbose|-v] [--backspace|-b]\n", - argv[0]); + 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" + " [--min-chars | -m #] [--echo | -h] " + "[--canonical | -c] [--delay | -d #]\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 (t) + free(t); - if (isatty(STDIN_FILENO) || isatty(STDOUT_FILENO)) + if (isatty(STDIN_FILENO)) interactive = 1; signal(SIGHUP, sighandl); @@ -907,10 +1037,21 @@ main(int argc, char **argv) signal(SIGTERM, sighandl); fd = openport(); - + if (interactive) pfd = openparent(); + + #ifdef __OpenBSD__ + if (pledge("stdio rpath tty", NULL) < 0) { + perror("pledge"); + die(1, "pledge failed, exiting now\n"); + } + #endif + pthread_t readthread, writethread; pthread_create(&writethread, NULL, writeport, NULL); pthread_create(&readthread, NULL, readport, NULL); pthread_join(writethread, NULL); pthread_join(readthread, NULL); + + /* unreachable */ + return 0; }