From: git Date: Sat, 11 Apr 2026 15:49:52 +0000 (-0400) Subject: interactive mode improvements X-Git-Url: https://git.datadissipation.net/?a=commitdiff_plain;h=40fa18eb720897145f1d9524ec2dc74018aad2ef;p=ust.git interactive mode improvements --- diff --git a/Makefile b/Makefile index d30144b..2335159 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,6 @@ include config.mk +TARGET = ust SRC = ust.c OBJ = ${SRC:.c=.o} diff --git a/config.mk b/config.mk index 31f8619..33b391d 100644 --- a/config.mk +++ b/config.mk @@ -1,6 +1,4 @@ -VERSION = 0.2.3 - -TARGET = ust +VERSION = 0.2.4 PREFIX = /usr/local MANPREFIX = ${PREFIX}/share/man diff --git a/ust.1 b/ust.1 index 9d7e694..42a5a16 100644 --- a/ust.1 +++ b/ust.1 @@ -97,7 +97,8 @@ Toggle translation options (from the line) for CR an LF, the options are: .Sh USAGE If the first character in the input buffer is the special character (tilde by default, can be changed in .Ar config.h ) -it will be escaped and the next character may run a command. +it will be escaped and the next character may run a command. The escape character and all subsequent characters +do not get sent to the line. .Bl -tag -width ".Fl \&. No or Ar \&^D" .It Ar \&. No or Ar \&^D Drop the connection and exit. diff --git a/ust.c b/ust.c index 8be0ce7..a9e4633 100644 --- a/ust.c +++ b/ust.c @@ -17,7 +17,7 @@ #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 @@ -35,15 +35,15 @@ #endif #ifdef __linux__ - #define TERMIOS_STRUCT termios2 + typedef struct termios2 term_struct; #else - #define TERMIOS_STRUCT termios + typedef struct termios term_struct; #endif typedef struct { + int offset; /* can only be a 1 or a -1 */ char find; char input; - int offset; /* can only be a 1 or a -1 */ } Args; typedef struct { @@ -63,48 +63,56 @@ typedef struct { /* 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 gettermattr(int dv, term_struct *optst); +static int settermattr(int dv, const term_struct *optst); +static void settermspd(unsigned int lispeed, unsigned int lospeed, + term_struct *optst); static int openport(void); +static int openparent(void); static unsigned int strtoui(const char *str, unsigned int min); -static int troptions(CRLFOpt *options, const char *str); -static int setroptions(void); +static int chcktropts(CRLFOpt *options, const char *str); +static int settropts(void); static inline unsigned int lsbmask(unsigned int n); -static inline int termchck(const struct TERMIOS_STRUCT *term); -static void interchck(void); -static void cechck(void); +static inline int termvalid(const term_struct *term); +static void eninter(void); +static void enechocan(void); static void *writeport(void *); static void *readport(void *); static void getcmd(int escape); static inline void replacechar(char *buff, ssize_t size, const Args *args); -static inline ssize_t addchar(char *buff, ssize_t size, int *scratch, const Sizes *msizes, const Args *args); -static inline ssize_t rmchar(char *buff, ssize_t sread, int *scratch, const Args *args); +static 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, ...); -static struct TERMIOS_STRUCT cntrl, /* device struct */ - origterm = { 0 }, /* controlling terminal structs */ - newterm = { 0 }; +static term_struct cntrl, /* device struct */ + /* controlling terminal structs */ + origterm = { 0 }, + 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 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 struct timespec wts; -static const unsigned int uintmax = ~(unsigned int)0; /* unsigned -1 used to return an error */ +/* 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 int fd = -1; +static int pfd = -1; static long rwc = 0; int -gettermattr(int dv, struct TERMIOS_STRUCT *optst) +gettermattr(int dv, term_struct *optst) { #ifdef __linux__ return ioctl(dv, TCGETS2, optst); @@ -114,7 +122,7 @@ gettermattr(int dv, struct TERMIOS_STRUCT *optst) } int -settermattr(int dv, const struct TERMIOS_STRUCT *optst) +settermattr(int dv, const term_struct *optst) { #ifdef __linux__ return ioctl(dv, TCSETS2, optst); @@ -124,7 +132,7 @@ settermattr(int dv, const struct TERMIOS_STRUCT *optst) } void -settermspd(unsigned int lispeed, unsigned int lospeed, struct TERMIOS_STRUCT *optst) +settermspd(unsigned int lispeed, unsigned int lospeed, term_struct *optst) { #ifdef __linux__ optst->c_cflag &= ~CBAUD; @@ -144,9 +152,9 @@ openport(void) struct flock fl = { 0 }; #ifdef __OpenBSD__ - if (pledge("stdio rpath wpath tty flock", NULL) == -1) { + if (pledge("stdio rpath wpath tty flock", NULL) < 0) { perror("pledge"); - die(1, "failed to set up pledge, exiting now\n"); + die(1, "pledge failed, exiting now\n"); } #endif @@ -165,22 +173,21 @@ openport(void) 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) { + if (fcntl(fd, F_SETFL, flags) < 0) { perror("fcntl"); die(1, "exiting now\n"); } } - if (gettermattr(fd, &cntrl) == -1) + if (gettermattr(fd, &cntrl) < 0) die(1, "failed to get device attributes\n"); if (exclusive) { fl.l_type = F_WRLCK; fl.l_whence = SEEK_SET; - if (fcntl(fd, F_SETLK, &fl) == -1) { + if (fcntl(fd, F_SETLK, &fl) < 0) { perror("fcntl"); die(1, "failed to lock the device, exiting now\n"); } @@ -192,7 +199,7 @@ openport(void) if (verbose) fprintf(stderr, "setting baudrate [RX:%u | TX:%u]\n", ispeed, ospeed); settermspd(ispeed, ospeed, &cntrl); - if (settermattr(fd, &cntrl) == -1) + if (settermattr(fd, &cntrl) < 0) die(2,"failed to set baudrate [RX:%u | TX:%u]\n", ispeed, ospeed); /* @@ -210,7 +217,7 @@ openport(void) if (parity == 2) cntrl.c_cflag |= PARENB | PARODD; - if (settermattr(fd, &cntrl) == -1) + if (settermattr(fd, &cntrl) < 0) die(2, "failed to set parity [even: %d, odd: %d]\n",\ parity & 1, (parity & 2) >> 1); @@ -246,7 +253,7 @@ openport(void) #endif } - if (settermattr(fd, &cntrl) == -1) + if (settermattr(fd, &cntrl) < 0) die(2, "failed to set flow control\n"); /* @@ -255,7 +262,7 @@ openport(void) cntrl.c_iflag &= ~(ICRNL | INLCR); cntrl.c_oflag &= ~(OCRNL | ONLRET | ONLCR); - if (setroptions()) + if (settropts()) die(2, "failed to set cr-lf translation options\n"); /* @@ -277,7 +284,7 @@ openport(void) 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); /* @@ -290,10 +297,10 @@ openport(void) 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] = 1; + cntrl.c_cc[VMIN] = 0; cntrl.c_cc[VTIME] = 0; /* flush both hardware buffers */ @@ -302,7 +309,28 @@ openport(void) #else tcflush(fd, TCIOFLUSH); #endif - return(fd); + /* returning only for readability */ + return fd; +} + +int +openparent(void) +{ + if (verbose) fprintf(stderr, "opening parent terminal\n"); + + pfd = open("/dev/tty", O_RDWR); + if (pfd == -1) { + 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 @@ -315,15 +343,12 @@ strtoui(const char *str, unsigned int min) 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; } int -troptions(CRLFOpt *options, const char *str) +chcktropts(CRLFOpt *options, const char *str) { switch (str[0]) { case 'l': @@ -356,7 +381,7 @@ troptions(CRLFOpt *options, const char *str) } int -setroptions(void) +settropts(void) { cntrl.c_iflag ^= (ICRNL & lsbmask(itropts.crtolf)); cntrl.c_iflag ^= (INLCR & lsbmask(itropts.lftocr)); @@ -377,9 +402,9 @@ lsbmask(unsigned int n) } inline int -termchck(const struct TERMIOS_STRUCT *term) +termvalid(const term_struct *term) { - for (size_t i = 0; i < sizeof(struct TERMIOS_STRUCT); i++) + for (size_t i = 0; i < sizeof(term_struct); i++) if (((char*)term)[i] != 0) return 1; @@ -415,12 +440,14 @@ writeport(void *unused) if (escape) { getcmd(escape); escape = 0; + inln = 0; } if (writebuff[0] == escapechar) { if (inln > 1) - getcmd(escape); - else - escape = 1; + getcmd(escape); + else + escape = 1; + inln = 0; } if (bsargs.find != bsargs.input) @@ -439,7 +466,7 @@ writeport(void *unused) } } else { - rwc -= write(fd, writebuff, 1); + rwc -= write(fd, writebuff, inln); } } else if (inln == 0 && !interactive) { @@ -473,7 +500,7 @@ readport(void *unused) if (scratchr == NULL) die(1, "scratch buffer allocation failed\n"); - interchck(); + eninter(); /* disable stdout buffering */ setvbuf(stdout, NULL, _IONBF, 0); @@ -564,13 +591,11 @@ rmchar(char *buff, ssize_t size, int *scratch, const Args *args) } void -interchck(void) +eninter(void) { if (interactive) { - if (gettermattr(STDIN_FILENO, &origterm) < 0 ) - die(1, "failed to get terminal attributes\n"); - - newterm = origterm; + if (pfd < 0) + pfd = openparent(); if (!canonical) newterm.c_lflag &= ~ECHO; @@ -598,20 +623,20 @@ interchck(void) bsargs.find = origterm.c_cc[VERASE]; - if (settermattr(STDOUT_FILENO, &newterm) < 0) + if (settermattr(pfd, &newterm) < 0) die(1, "failed to set terminal attributes\n"); } return; } void -cechck(void) +enechocan(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 and/or canonical mode\n"); } return; } @@ -627,10 +652,10 @@ getcmd(int escape) msizes.sizesm = scratchwsz; msizes.sizebm = wbuffsz; - if (isatty(STDIN_FILENO) && isatty(STDOUT_FILENO)) + if (isatty(STDIN_FILENO)) interactive = 1; - interchck(); + eninter(); if (escape) cmdchar = writebuff[0]; @@ -654,7 +679,7 @@ getcmd(int escape) else newterm.c_lflag |= ECHO; half ^= 1; - if (settermattr(STDOUT_FILENO, &newterm) < 0) + if (settermattr(pfd, &newterm) < 0) die(1, "failed to set terminal attributes\n"); break; case 'c': @@ -663,11 +688,11 @@ getcmd(int escape) else newterm.c_lflag |= ICANON; canonical ^= 1; - if (settermattr(STDOUT_FILENO, &newterm) < 0) + if (settermattr(pfd, &newterm) < 0) die(1, "failed to set terminal attributes\n"); break; case 'w': - cechck(); + enechocan(); if (fgets(ttr, sizeof(ttr), stdin) != NULL) { ssize_t frln; int wfd = open(ttr, O_RDONLY); @@ -693,14 +718,14 @@ getcmd(int escape) } goto finish; case 's': - cechck(); + enechocan(); if (fgets(ttr, sizeof(ttr), stdin) != NULL) { replacechar(ttr, 63, &nolfargs); tspd = strtoui(ttr, 0); if (tspd != uintmax) { ospeed = ispeed = tspd; settermspd(ispeed, ospeed, &cntrl); - if (settermattr(fd, &cntrl) != 0) + if (settermattr(fd, &cntrl) < 0) fprintf(stderr, "failed to set baudrate " "[RX:%u | TX:%u]\n", ispeed, ospeed); } else { @@ -709,7 +734,7 @@ getcmd(int escape) } goto finish; case 'd': - cechck(); + enechocan(); if (fgets(ttr, sizeof(ttr), stdin) != NULL) { replacechar(ttr, 63, &nolfargs); chardelay = strtoui(ttr, 0); @@ -722,28 +747,28 @@ getcmd(int escape) } goto finish; case 't': - cechck(); + enechocan(); fprintf(stderr, "additional output translation option: "); if (fgets(ttr, sizeof(ttr), stdin) != NULL) { replacechar(ttr, 63, &nolfargs); - if(troptions(&tropts, ttr)) { + if (chcktropts(&tropts, ttr)) { fprintf(stderr, "invalid output translation option: %s\n", optarg); goto finish; } - if (setroptions()) + if (settropts()) fprintf(stderr, "could not set new options\n"); } goto finish; case 'T': - cechck(); + enechocan(); printf("additional input translation option: "); if (fgets(ttr, sizeof(ttr), stdin) != NULL) { replacechar(ttr, 63, &nolfargs); - if (troptions(&itropts, ttr)) { + if (chcktropts(&itropts, ttr)) { fprintf(stderr, "invalid input translation option: %s\n", optarg); goto finish; } - if (setroptions()) + if (settropts()) fprintf(stderr, "could not set new options\n"); } finish: @@ -751,7 +776,7 @@ getcmd(int escape) newterm.c_lflag &= ~ECHO; if (!canonical && interactive) newterm.c_lflag &= ~ICANON; - if (settermattr(STDIN_FILENO, &newterm) == -1) + if (settermattr(pfd, &newterm) < 0) fprintf(stderr, "failed to restore echo and/or canonical mode\n"); break; case 'p':; @@ -760,19 +785,19 @@ getcmd(int escape) ts.tv_sec = spulsedelay; ts.tv_nsec = nspulsedelay; - if (ioctl(fd, TIOCMGET, &st) != 0) { + if (ioctl(fd, TIOCMGET, &st) < 0) { fprintf(stderr, "failed to get port status\n"); break; } st ^= TIOCM_DTR; - if (ioctl(fd, TIOCMSET, &st) != 0) { + 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) + if (ioctl(fd, TIOCMSET, &st) < 0) fprintf(stderr, "failed to set DTR [negation]\n"); break; case BS: /* FALLTHROUGH */ @@ -803,6 +828,10 @@ die(int code, const char *msg, ...) if (t == rwc) break; } + if (termvalid(&origterm)) + settermattr(pfd, &origterm); + if (pfd != -1) + close(pfd); if (fd != -1) close(fd); @@ -815,9 +844,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); @@ -829,8 +855,8 @@ int main(int argc, char *argv[]) { long tl; - char *t; - char *endptr; + char *t = NULL; + char *endptr = NULL; unsigned int tui; int oind, rxspdset, devset, c; oind = rxspdset = devset = 0; @@ -947,13 +973,13 @@ main(int argc, char *argv[]) minchars = tui; break; case 't': - if (troptions(&tropts, optarg)) { + 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)) { + if (chcktropts(&itropts, optarg)) { fprintf(stderr, "%s: invalid input translation option: %s\n", argv[0], optarg); goto ustusage; } @@ -962,31 +988,25 @@ main(int argc, char *argv[]) verbose ^= 1; break; default: ustusage: - die(2, "usage: %s [--line|-l line] [--speed|-s #|-#] [--rx-speed|-i #]\n" - " [--data-bits|-D #] [--stop-bits|-S]" - " [--even|-e] [--odd|-o]\n" - " [--hardware-rts-cts|-R]" - " [--hardware-dtr-dsr|-r] [--software|-X]\n" - " [--delay|-d #] [--min-chars|-m #]" - " [--canonical|-c] [--echo|-h]\n" - " [--translation|-t option]" - " [--input-translation|-T option]\n" - " [--verbose|-v] [--backspace|-b]\n", + die(2, "usage: %s [--line | -l line] [--speed | -s # | -#] [--rx-speed | -i #]\n" + " [--data-bits | -D #] [--stop-bits | -S]" + " [--even | -e] [--odd | -o]\n" + " [--hardware-rts-cts | -R]" + " [--hardware-dtr-dsr | -r] [--software | -X]\n" + " [--delay | -d #] [--min-chars | -m #]" + " [--canonical | -c] [--echo | -h]\n" + " [--translation | -t option]" + " [--input-translation | -T option]\n" + " [--verbose | -v] [--backspace | -b]\n", argv[0]); break; } } - /* - * not the best practice, but in order for this `free()` - * to not get executed ust needs to be killed, - * which means the OS *should* free the memory - */ - free(t); - if (!rxspdset) - ispeed = ospeed; + if (t) + free(t); - if (isatty(STDIN_FILENO) && isatty(STDOUT_FILENO)) + if (isatty(STDIN_FILENO)) interactive = 1; signal(SIGHUP, sighandl); @@ -995,7 +1015,8 @@ main(int argc, char *argv[]) signal(SIGTERM, sighandl); fd = openport(); - + if (interactive) pfd = openparent(); + pthread_t readthread, writethread; pthread_create(&writethread, NULL, writeport, NULL); pthread_create(&readthread, NULL, readport, NULL);