5570aa576f70a010665a3b245988a6e909841866
12 #include <sys/ioctl.h>
15 #include <asm/termbits.h>
16 #include <asm/ioctls.h>
17 #else /* needed for termios2, which is itself needed for non-standard bauds */
29 #if defined(CCDTR_IFLOW) && defined(CDSR_OFLOW)
30 #define CDTRDSR (CDTR_IFLOW | CDSR_OFLOW)
32 #define IXONXOFF (IXON | IXOFF)
37 int offset
; /* can only be a 1 or a -1 */
45 static int gettermattr(int dv
, void *strct
);
46 static int settermattr(int dv
, const void *strct
);
47 static void settermspd(unsigned int lispeed
, unsigned int lospeed
, const void *strct
);
48 static int openport();
49 static unsigned int strtoui(const char *str
, const char *msg
, unsigned int min
);
50 static int troptions(CRLFOpt
*options
, const char *str
);
51 static int setroptions();
52 static inline unsigned int lsbmask(unsigned int n
);
53 static inline int termchck(const void *term
);
54 static void interchck();
56 static void *writeport(void *unused
);
57 static void *readport(void *unused
);
58 static void getcmd(int escape
);
59 static inline void replacechar(char *buff
, ssize_t size
, const Args
*args
);
60 static inline ssize_t
addchar(char *buff
, ssize_t size
, int *scratch
, const Sizes
*msizes
, const Args
*args
);
61 static inline ssize_t
rmchar(char *buff
, ssize_t sread
, int *scratch
, const Args
*args
);
62 static void sighandl(int signo
);
63 static void die(int code
, const char *msg
, ...);
66 static struct termios2 cntrl
, origterm
= {0}, newterm
= {0};
68 static struct termios cntrl
, origterm
= {0}, newterm
= {0};
71 static Args bsargs
= {DEL
, DEL
, 0};
72 static Args nocrargs
= {CR
, 0, 0};
73 static Args nolfargs
= {LF
, '\0', 0}; /* null-terminator is for use inside `getcmd()` */
74 static Args lfincrargs
= {CR
, LF
, 1};
75 static Args crinlfargs
= {LF
, CR
, -1};
76 static const unsigned int uintmax
= ~(unsigned int)0; /* unsigned -1 to return on error */
77 static struct timespec wts
;
78 static char *writebuff
= NULL
;
79 static char *readbuff
= NULL
;
80 static int *scratchr
= NULL
;
81 static int *scratchw
= NULL
;
83 static int interactive
= 0;
86 gettermattr(int dv
, void *strct
)
89 struct termios2
*optst
= (struct termios2
*)strct
;
90 return ioctl(dv
, TCGETS2
, optst
);
92 struct termios
*optst
= (struct termios
*)strct
;
93 return tcgetattr(dv
, optst
);
98 settermattr(int dv
, const void *strct
)
101 struct termios2
*optst
= (struct termios2
*)strct
;
102 return ioctl(dv
, TCSETS2
, optst
);
104 struct termios
*optst
= (struct termios
*)strct
;
105 return tcsetattr(dv
, TCSANOW
, optst
);
110 settermspd(unsigned int lispeed
, unsigned int lospeed
, const void *strct
)
113 struct termios2
*optst
= (struct termios2
*)strct
;
115 optst
->c_cflag
&= ~CBAUD
;
116 optst
->c_cflag
|= BOTHER
;
117 optst
->c_ispeed
= ispeed
;
118 optst
->c_ospeed
= ospeed
;
120 struct termios
*optst
= (struct termios
*)strct
;
121 cfsetispeed(optst
, ispeed
);
122 cfsetospeed(optst
, ospeed
);
129 if (verbose
) fprintf(stderr
, "opening \"%s\"\n", line
);
131 fd
= open(line
, O_RDWR
| O_NOCTTY
| O_NDELAY
);
133 perror("error opening device");
137 if (verbose
) fprintf(stderr
, "checking if \"%s\" is a TTY\n", line
);
140 die(2, "device \"%s\" is not a TTY\n", line
);
143 flags
= fcntl(fd
, F_GETFL
, 0);
144 /* opened to check with non-blocking mode, now set to blocking */
145 flags
&= ~(O_NONBLOCK
| O_NDELAY
);
146 if (fcntl(fd
, F_SETFL
, flags
) == -1)
147 die(1, "failed to set the device to blocking mode\n");
149 if (gettermattr(fd
, &cntrl
) == -1)
150 die(1, "failed to get device attributes\n");
152 if (verbose
) fprintf(stderr
, "setting baudrate [RX:%u | TX:%u]\n", ispeed
, ospeed
);
154 settermspd(ispeed
, ospeed
, &cntrl
);
155 if (settermattr(fd
, &cntrl
) == -1)
156 die(2,"failed to set baudrate [RX:%u | TX:%u]\n", ispeed
, ospeed
);
159 cntrl
.c_iflag
&= ~(ISTRIP
| BRKINT
);
160 cntrl
.c_cflag
&= ~(PARENB
| PARODD
);
162 if (verbose
) fprintf(stderr
, "setting parity [even: %d, odd: %d]\n", parity
& 1, (parity
& 2) >> 1);
165 cntrl
.c_cflag
|= PARENB
;
167 cntrl
.c_cflag
|= PARENB
| PARODD
;
169 if (settermattr(fd
, &cntrl
) == -1)
170 die(2, "failed to set parity [even: %d, odd: %d]\n", parity
& 1, (parity
& 2) >> 1);
173 /* it's so ugly and beautiful at the same time */
174 int t
= (((hard
& 1) << 1) & (hard
& 2)) >> 1;
175 fprintf(stderr
, "setting flow control [XON/XOFF: %d, RTS/CTS: %d, DTR/DSR: %d, DCD: %d]\n",\
176 soft
, t
^ ((hard
& 1) << 1) >> 1, t
^ (hard
& 2) >> 1, t
);
179 cntrl
.c_cflag
|= CLOCAL
;
180 cntrl
.c_iflag
&= ~IXONXOFF
;
183 cntrl
.c_iflag
|= IXONXOFF
;
186 cntrl
.c_cflag
|= CRTSCTS
;
187 } else if (hard
== 2) {
189 fprintf(stderr
, "DTR/DSR flow control is not supported on this platform\n"
190 "enabling this option does nothing!\n");
192 cntrl
.c_lflag
|= CDTRDSR
;
194 } else if (hard
== 3) {
195 cntrl
.c_cflag
&= ~CLOCAL
;
197 cntrl
.c_lflag
|= CCAR_OFLOW
;
201 if (settermattr(fd
, &cntrl
) == -1)
202 die(2, "failed to set flow control\n");
204 cntrl
.c_iflag
&= ~(ICRNL
| INLCR
);
205 cntrl
.c_oflag
&= ~(OCRNL
| ONLRET
| ONLCR
);
208 die(2, "failed to set cr-lf translation options\n");
210 if (verbose
) fprintf(stderr
, "setting data bits [%c]\n", datab
);
223 cntrl
.c_cflag
&= ~CSIZE
;
226 if (settermattr(fd
, &cntrl
) == -1)
227 die(2, "failed to set data bits [%c]\n", datab
);
229 if (verbose
) fprintf(stderr
, "setting stop bits [%d]\n", stopb
+1);
232 cntrl
.c_cflag
|= CSTOPB
;
234 cntrl
.c_cflag
&= ~CSTOPB
;
236 if (settermattr(fd
, &cntrl
) == -1)
237 die(1, "failed to set stop bits [%d]\n", stopb
+1);
240 ioctl(fd
, TCFLSH
, TCIOFLUSH
);
242 tcflush(fd
, TCIOFLUSH
);
248 strtoui(const char *str
, const char *msg
, unsigned int min
)
253 t
= strtol(str
, &endptr
, 10);
254 if (errno
!= 0 || *endptr
!= '\0' || t
< min
) {
255 fprintf(stderr
, msg
, str
);
259 * conversion like this results in 'undefined behavior' according to C spec,
260 * but almost all compilers will just truncate the value so it's OK
262 return (unsigned int)t
;
263 /* termios's `speed_t` is a typedef for `unsigned int` */
267 troptions(CRLFOpt
*options
, const char *str
)
271 if (strcmp(str
, "lf-in-cr") == 0) {
272 options
->lfincr
= !options
->lfincr
; return 0;
273 } else if (strcmp(str
, "lf-to-cr") == 0) {
274 options
->lftocr
= !options
->lftocr
; return 0;
280 if (strcmp(str
, "cr-in-lf") == 0) {
281 options
->crinlf
= !options
->crinlf
; return 0;
282 } else if (strcmp(str
, "cr-to-lf") == 0) {
283 options
->crtolf
= !options
->crtolf
; return 0;
289 if (strcmp(str
, "no-lf") == 0) {
290 options
->nolf
= !options
->nolf
; return 0;
291 } else if (strcmp(str
, "no-cr") == 0) {
292 options
->nocr
= !options
->nocr
; return 0;
299 fprintf(stderr
, "invalid translation option: %s\n", str
);
307 cntrl
.c_iflag
^= (ICRNL
& lsbmask(itropts
.crtolf
));
308 cntrl
.c_iflag
^= (INLCR
& lsbmask(itropts
.lftocr
));
309 cntrl
.c_oflag
^= (OCRNL
& lsbmask(tropts
.crtolf
));
310 cntrl
.c_oflag
^= (ONLRET
& lsbmask(tropts
.lftocr
));
311 cntrl
.c_oflag
^= (ONLCR
& lsbmask(tropts
.crinlf
));
313 return settermattr(fd
, &cntrl
);
317 lsbmask(unsigned int n
)
319 /* if n == 1, n fills with all 1s, 0 stays the same */
320 for (int i
= (sizeof(int) * CHAR_BIT
); i
> 0; i
--)
326 termchck(const void *term
)
329 struct termios2
*iterm
= (struct termios2
*)term
;
330 #define TERMIOS_STRUCT termios2
332 struct termios
*iterm
= (struct termios
*)term
;
333 #define TERMIOS_STRUCT termios
335 for (size_t i
= 0; i
< sizeof(struct TERMIOS_STRUCT
); i
++)
336 if (((char*)iterm
)[i
] != 0)
343 writeport(void *unused
)
347 ts
.tv_sec
= swritedelay
;
348 ts
.tv_nsec
= nswritedelay
;
350 wts
.tv_nsec
= chardelay
;
351 msizes
.sizesm
= scratchwsz
;
352 msizes
.sizebm
= wbuffsz
;
354 writebuff
= malloc(wbuffsz
* sizeof(char));
355 scratchw
= malloc(scratchwsz
* sizeof(int));
357 if (writebuff
== NULL
)
358 die(1, "buffer allocation failed\n");
359 if (scratchw
== NULL
)
360 die(1, "scratch buffer allocation failed\n");
365 ssize_t inln
= read(STDIN_FILENO
, writebuff
, wbuffsz
- 1);
371 if (writebuff
[0] == escapechar
) {
378 if (bsargs
.find
!= bsargs
.input
)
379 replacechar(writebuff
, inln
, &bsargs
);
381 inln
= rmchar(writebuff
, inln
, scratchw
, &nocrargs
);
383 inln
= rmchar(writebuff
, inln
, scratchw
, &nolfargs
);
385 inln
= addchar(writebuff
, inln
, scratchw
, &msizes
, &lfincrargs
);
388 for (int i
= 0; i
<= inln
; i
++) {
389 write(fd
, &writebuff
[i
], 1);
390 nanosleep(&wts
, NULL
);
393 write(fd
, writebuff
, 1);
396 nanosleep(&ts
, NULL
);
402 readport(void *unused
)
406 ts
.tv_sec
= sreaddelay
;
407 ts
.tv_nsec
= nsreaddelay
;
408 msizes
.sizesm
= scratchrsz
;
409 msizes
.sizebm
= rbuffsz
;
411 readbuff
= malloc(rbuffsz
* sizeof(char));
412 scratchr
= malloc(scratchrsz
* sizeof(int));
414 if (readbuff
== NULL
)
415 die(1, "buffer allocation failed\n");
416 if (scratchr
== NULL
)
417 die(1, "scratch buffer allocation failed\n");
421 /* disable stdout buffering */
422 setvbuf(stdout
, NULL
, _IONBF
, 0);
425 ssize_t outln
= read(fd
, readbuff
, rbuffsz
- 1);
428 outln
= rmchar(readbuff
, outln
, scratchr
, &nocrargs
);
430 outln
= rmchar(readbuff
, outln
, scratchr
, &nolfargs
);
432 outln
= addchar(readbuff
, outln
, scratchr
, &msizes
, &crinlfargs
);
434 outln
= addchar(readbuff
, outln
, scratchr
, &msizes
, &lfincrargs
);
436 write(STDOUT_FILENO
, readbuff
, outln
);
438 nanosleep(&ts
, NULL
);
440 if (isatty(STDIN_FILENO
))
441 settermattr(STDIN_FILENO
, &origterm
);
445 inline void __attribute__((hot
))
446 replacechar(char *buff
, ssize_t size
, const Args
*args
)
448 for (int i
= 0; i
< size
; i
++)
449 if (buff
[i
] == args
->find
)
450 buff
[i
] = args
->input
;
453 inline ssize_t
__attribute__((hot
))
454 addchar(char *buff
, ssize_t size
, int *scratch
, const Sizes
*sizes
, const Args
*args
)
458 /* turns negative numbers into 1, positive into 0 */
459 to
= ((args
->offset
>> ((sizeof(int) * CHAR_BIT
) - 1)) & 1);
461 for (int i
= 0; i
< size
; i
++) {
462 if (buff
[i
] == args
->find
) {
466 if (c
>= sizes
->sizesm
)
471 if (size
== 1 && c
== 1 && scratch
[0] == 0) {
472 buff
[to
^ 1] = args
->input
;
473 buff
[to
] = args
->find
;
476 if ((size
+ c
) > sizes
->sizebm
)
477 c
= sizes
->sizebm
- size
;
479 for (int i
= c
- 1; i
>= 0; i
--) {
481 memmove(&buff
[t
] + 1, &buff
[t
], (size
+ c
) - t
);
482 buff
[t
+ (to
^ 1)] = args
->input
;
484 printf("%s\n", buff
);
488 inline ssize_t
__attribute__((hot
))
489 rmchar(char *buff
, ssize_t size
, int *scratch
, const Args
*args
)
492 for (int i
= 0; i
< size
; i
++) {
493 if (buff
[i
] == args
->find
) {
500 if (size
== 1 && c
== 1 && scratch
[0] == 0)
503 for (int i
= c
; i
>= 0; i
--) {
505 memmove(&buff
[t
], &buff
[t
] + 1, (size
+ c
) - t
);
514 if (gettermattr(STDIN_FILENO
, &origterm
) < 0 )
515 die(1, "failed to get terminal attributes\n");
520 newterm
.c_lflag
&= ~ECHO
;
522 newterm
.c_lflag
&= ~ICANON
;
525 newterm
.c_iflag
|= IXONXOFF
;
527 newterm
.c_iflag
|= INLCR
;
528 newterm
.c_cc
[VMIN
] = minchars
;
529 newterm
.c_cc
[VINTR
] = _POSIX_VDISABLE
;
530 newterm
.c_cc
[VSUSP
] = _POSIX_VDISABLE
;
531 newterm
.c_cc
[VQUIT
] = _POSIX_VDISABLE
;
538 bsargs
.find
= origterm
.c_cc
[VERASE
];
540 if (settermattr(STDOUT_FILENO
, &newterm
) < 0)
541 die(1, "failed to set terminal attributes\n");
549 if ((!half
|| !canonical
) && interactive
) {
550 newterm
.c_lflag
|= ECHO
;
551 newterm
.c_lflag
|= ICANON
;
552 if (settermattr(STDIN_FILENO
, &newterm
) < 0)
553 fprintf(stderr
, "failed to enable echo and/or canonical mode\n");
562 msizes
.sizesm
= scratchwsz
;
563 msizes
.sizebm
= wbuffsz
;
569 if (isatty(STDIN_FILENO
) && isatty(STDOUT_FILENO
))
575 cmdchar
= writebuff
[0];
577 cmdchar
= writebuff
[1];
580 case EOT
: /* FALLTHROUGH */
593 newterm
.c_lflag
&= ~ECHO
;
595 newterm
.c_lflag
|= ECHO
;
597 if (settermattr(STDOUT_FILENO
, &newterm
) < 0)
598 die(1, "failed to set terminal attributes\n");
602 newterm
.c_lflag
&= ~ICANON
;
604 newterm
.c_lflag
|= ICANON
;
606 if (settermattr(STDOUT_FILENO
, &newterm
) < 0)
607 die(1, "failed to set terminal attributes\n");
611 if (fgets(ttr
, sizeof(ttr
), stdin
) != NULL
) {
612 replacechar(ttr
, 63, &nolfargs
);
614 int wfd
= open(ttr
, O_RDONLY
);
616 perror("error opening file");
619 while ((frln
= read(wfd
, writebuff
, wbuffsz
- 1)) > 0) {
620 for (int i
= 0; i
<= frln
; i
++) {
622 frln
= rmchar(writebuff
, frln
, scratchw
, &nocrargs
);
624 frln
= rmchar(writebuff
, frln
, scratchw
, &nolfargs
);
626 frln
= addchar(writebuff
, frln
, scratchw
, &msizes
, &lfincrargs
);
628 write(fd
, &writebuff
[i
], 1);
629 nanosleep(&wts
, NULL
);
636 if (fgets(ttr
, sizeof(ttr
), stdin
) != NULL
) {
637 replacechar(ttr
, 63, &nolfargs
);
638 tspd
= strtoui(ttr
, "invalid speed\n", 0);
639 if (tspd
!= uintmax
) {
640 ospeed
= ispeed
= tspd
;
641 settermspd(ispeed
, ospeed
, &cntrl
);
642 if (settermattr(fd
, &cntrl
) != 0)
643 fprintf(stderr
, "failed to set baudrate "
644 "[RX:%u | TX:%u]\n", ispeed
, ospeed
);
650 if (fgets(ttr
, sizeof(ttr
), stdin
) != NULL
) {
651 replacechar(ttr
, 63, &nolfargs
);
652 chardelay
= strtoui(ttr
, "invalid delay\n", 0);
653 if (chardelay
!= uintmax
) {
655 wts
.tv_nsec
= chardelay
;
661 fprintf(stderr
, "additional output translation option: ");
662 if (fgets(ttr
, sizeof(ttr
), stdin
) != NULL
) {
663 replacechar(ttr
, 63, &nolfargs
);
664 if(troptions(&tropts
, ttr
))
667 fprintf(stderr
, "could not set new options\n");
672 printf("additional input translation option: ");
673 if (fgets(ttr
, sizeof(ttr
), stdin
) != NULL
) {
674 replacechar(ttr
, 63, &nolfargs
);
675 if (troptions(&itropts
, ttr
))
678 fprintf(stderr
, "could not set new options\n");
681 if (!half
&& interactive
)
682 newterm
.c_lflag
&= ~ECHO
;
683 if (!canonical
&& interactive
)
684 newterm
.c_lflag
&= ~ICANON
;
685 if (settermattr(STDIN_FILENO
, &newterm
) == -1)
686 fprintf(stderr
, "failed to restore echo and/or canonical mode\n");
691 ts
.tv_sec
= spulsedelay
;
692 ts
.tv_nsec
= nspulsedelay
;
694 if (ioctl(fd
, TIOCMGET
, &st
) != 0) {
695 fprintf(stderr
, "failed to get port status\n");
699 if (ioctl(fd
, TIOCMSET
, &st
) != 0) {
700 fprintf(stderr
, "failed to set DTR [assertion]\n");
703 nanosleep(&ts
, NULL
);
706 if (ioctl(fd
, TIOCMSET
, &st
) != 0)
707 fprintf(stderr
, "failed to set DTR [negation]\n");
709 case BS
: /* FALLTHROUGH */
714 fprintf(stderr
, "not a valid command [%c]\n", cmdchar
);
723 die(128 + signo
, "\nrecieved signal [%d], exiting\n", signo
);
727 die(int code
, const char *msg
, ...)
742 if (termchck(&newterm
))
743 settermattr(STDIN_FILENO
, &origterm
);
746 vfprintf(stderr
, msg
, fpa
);
753 main(int argc
, char **argv
)
756 /* glibc's `asprintf()` won't set the string to NULL on failure automatically */
757 for (int i
= 1; i
< argc
; i
++ ) {
758 if (argv
[i
][0] == '-' && argv
[i
][1] >= '0' && argv
[i
][1] <= '9') {
759 asprintf(&t
, "-s%s", argv
[i
] + 1);
761 fprintf(stderr
, "cannot convert -# to -s#\n");
768 static struct option longopt
[] = {
769 {"line", required_argument
, NULL
, 'l'},
770 {"speed", required_argument
, NULL
, 's'},
771 {"rx-speed", required_argument
, NULL
, 'i'},
772 {"echo", no_argument
, NULL
, 'h'},
773 {"canonical", no_argument
, NULL
, 'c'},
774 {"odd", no_argument
, NULL
, 'o'},
775 {"even", no_argument
, NULL
, 'e'},
776 {"hardware-rts-cts", no_argument
, NULL
, 'R'},
777 {"hardware-dtr-dsr", no_argument
, NULL
, 'r'},
778 {"software", no_argument
, NULL
, 'X'},
779 {"data", required_argument
, NULL
, 'D'},
780 {"delay", required_argument
, NULL
, 'd'},
781 {"min-chars", required_argument
, NULL
, 'm'},
782 {"stop-bits", no_argument
, NULL
, 'S'},
783 {"backspace", no_argument
, NULL
, 'b'},
784 {"translation", required_argument
, NULL
, 't'},
785 {"input-translation", required_argument
, NULL
, 'T'},
786 {"verbose", no_argument
, NULL
, 'v'},
791 int oind
, rxspdset
, devset
, c
;
792 oind
= rxspdset
= devset
= 0;
793 while ((c
= getopt_long(argc
, argv
, "bcd:D:ehi:l:m:oRrs:t:T:vX", longopt
, &oind
)) != -1) {
798 backspace
^= 1; break;
800 canonical
^= 1; break;
810 t
= strtol(optarg
, &endptr
, 10);
811 if (errno
!= 0 || *endptr
!= '\0' || t
< 0) {
812 fprintf(stderr
, "invalid character delay\n");
819 if (strlen(optarg
) != 1 || !(optarg
[0] >= '5' && optarg
[0] <= '8')) {
820 fprintf(stderr
, "invalid number of data bits: %s\n", optarg
);
833 tui
= strtoui(optarg
, "invalid rx speed: %s\n", 1);
840 tui
= strtoui(optarg
, "invalid speed: %s\n", 1);
849 fprintf(stderr
, "cannot specify multiple devices\n");
852 if (strlen(optarg
) > 10) {
853 fprintf(stderr
, "device name too long\n");
856 if (strchr(optarg
, '/')) {
857 strcpy(line
, optarg
);
860 sprintf(line
, "/dev/%s", optarg
);
865 tui
= strtoui(optarg
, "invalid number of characters: %s\n", 1);
871 if (troptions(&tropts
, optarg
))
875 if (troptions(&itropts
, optarg
))
882 die(2, "usage: %s [--line|-l line] [--speed|-s #|-#] [--rx-speed|-i #]\n"
883 " [--data-bits|-D #] [--stop-bits|-S]"
884 " [--even|-e] [--odd|-o]\n"
885 " [--hardware-rts-cts|-R]"
886 " [--hardware-dtr-dsr|-r] [--software|-X]\n"
887 " [--delay|-d #] [--min-chars|-m #]"
888 " [--canonical|-c] [--echo|-h]\n"
889 " [--translation|-t option]"
890 " [--input-translation|-T option]\n"
891 " [--verbose|-v] [--backspace|-b]\n",
897 * not the best practice, but in order for this `free()`
898 * to not get executed ust needs to be killed,
899 * which means the OS *should* free the memory
906 if (isatty(STDIN_FILENO
) && isatty(STDOUT_FILENO
))
909 signal(SIGHUP
, sighandl
);
910 signal(SIGINT
, sighandl
);
911 signal(SIGQUIT
, sighandl
);
912 signal(SIGTERM
, sighandl
);
916 pthread_t readthread
, writethread
;
917 pthread_create(&writethread
, NULL
, writeport
, NULL
);
918 pthread_create(&readthread
, NULL
, readport
, NULL
);
919 pthread_join(writethread
, NULL
);
920 pthread_join(readthread
, NULL
);