From 65e318f51547062b338cb51f84a4ef5c5135ad15 Mon Sep 17 00:00:00 2001 From: git Date: Sat, 14 Feb 2026 10:13:48 -0500 Subject: [PATCH 1/2] initial public commit --- Makefile | 37 +++ config.def.h | 101 ++++++ ust.c | 889 +++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 1027 insertions(+) create mode 100644 Makefile create mode 100644 config.def.h create mode 100644 ust.c diff --git a/Makefile b/Makefile new file mode 100644 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 index 0000000..1f48d92 --- /dev/null +++ b/config.def.h @@ -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 index 0000000..350fcf7 --- /dev/null +++ b/ust.c @@ -0,0 +1,889 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef __linux__ + #include + #include +#else + #include +#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); +} -- 2.39.5 From 1570cacd7e50aa4eef973e2658b45ce6e7cf159d Mon Sep 17 00:00:00 2001 From: git Date: Sat, 14 Feb 2026 14:25:22 -0500 Subject: [PATCH 2/2] DTR/DSR, DCD macro switches are now platform-agnostic. Fixed "declaration after label" warning/error --- ust.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/ust.c b/ust.c index 350fcf7..b9cee49 100644 --- a/ust.c +++ b/ust.c @@ -23,7 +23,7 @@ #define LF '\n' #define BS '\b' #define DEL '\x7f' -#ifndef __linux__ +#if defined(CCDTR_IFLOW) && defined(CDSR_OFLOW) #define CDTRDSR (CDTR_IFLOW | CDSR_OFLOW) #endif #define IXONXOFF (IXON | IXOFF) @@ -333,14 +333,14 @@ openport() if (hard == 1) { cntrl.c_cflag |= CRTSCTS; } else if (hard == 2) { - #ifdef __linux__ - fprintf(stderr, "DTR/DSR flow control is not supported on Linux\nenabling this option does nothing!\n"); + #ifndef CDTRDSR + fprintf(stderr, "DTR/DSR flow control is not supported on this platform\nenabling this option does nothing!\n"); #else cntrl.c_lflag |= CDTRDSR; #endif } else if (hard == 3) { cntrl.c_cflag &= ~CLOCAL; - #ifndef __linux__ + #ifdef CCAR_OFLOW cntrl.c_lflag |= CCAR_OFLOW; #endif } @@ -823,7 +823,7 @@ getcmd(int escape) newterm.c_lflag &= ~ICANON; settermattr(STDIN_FILENO, &newterm); break; - case 'p': + case 'p':; int st; struct timespec ts; ts.tv_sec = spulsedelay; -- 2.39.5