#ifdef __linux__
#include <asm/termbits.h>
#include <asm/ioctls.h>
-#else /* needed for termios2, which is itself needed for non-standard bauds */
+#else /* including for termios2, which is needed for non-standard bauds */
#include <termios.h>
#endif
#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 {
/* 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);
}
int
-settermattr(int dv, const struct TERMIOS_STRUCT *optst)
+settermattr(int dv, const term_struct *optst)
{
#ifdef __linux__
return ioctl(dv, TCSETS2, 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;
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
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");
}
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);
/*
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);
#endif
}
- if (settermattr(fd, &cntrl) == -1)
+ if (settermattr(fd, &cntrl) < 0)
die(2, "failed to set flow control\n");
/*
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");
/*
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);
/*
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 */
#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
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':
}
int
-setroptions(void)
+settropts(void)
{
cntrl.c_iflag ^= (ICRNL & lsbmask(itropts.crtolf));
cntrl.c_iflag ^= (INLCR & lsbmask(itropts.lftocr));
}
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;
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)
}
} else {
- rwc -= write(fd, writebuff, 1);
+ rwc -= write(fd, writebuff, inln);
}
} else if (inln == 0 && !interactive) {
if (scratchr == NULL)
die(1, "scratch buffer allocation failed\n");
- interchck();
+ eninter();
/* disable stdout buffering */
setvbuf(stdout, NULL, _IONBF, 0);
}
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;
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;
}
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];
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':
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);
}
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 {
}
goto finish;
case 'd':
- cechck();
+ enechocan();
if (fgets(ttr, sizeof(ttr), stdin) != NULL) {
replacechar(ttr, 63, &nolfargs);
chardelay = strtoui(ttr, 0);
}
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:
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':;
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 */
if (t == rwc) break;
}
+ if (termvalid(&origterm))
+ settermattr(pfd, &origterm);
+ if (pfd != -1)
+ close(pfd);
if (fd != -1)
close(fd);
if (scratchr)
free(scratchr);
- if (termchck(&newterm))
- settermattr(STDIN_FILENO, &origterm);
-
va_start(fpa, msg);
vfprintf(stderr, msg, fpa);
va_end(fpa);
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;
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;
}
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);
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);