changes in define macros and switches, improved piping support
[ust.git] / ust.c
1 #include <sys/ioctl.h>
2
3 #include <errno.h>
4 #include <fcntl.h>
5 #include <limits.h>
6 #include <signal.h>
7 #include <stdarg.h>
8 #include <stdio.h>
9 #include <stdlib.h>
10 #include <string.h>
11 #include <unistd.h>
12
13 #include <getopt.h>
14 #include <pthread.h>
15
16 #ifdef __linux__
17 #include <asm/termbits.h>
18 #include <asm/ioctls.h>
19 #else /* needed for termios2, which is itself needed for non-standard bauds */
20 #include <termios.h>
21 #endif
22
23 #define CR '\r'
24 #define LF '\n'
25 #define BS '\b'
26 #define DEL '\x7f'
27 #define EOT '\x4'
28 #define ESC '\x1b'
29
30 #define IXONXOFF (IXON | IXOFF)
31
32 #ifdef __linux__
33 #define TERMIOS_STRUCT termios2
34 #else
35 #define TERMIOS_STRUCT termios
36 #endif
37
38 #if defined(CCDTR_IFLOW) && defined(CDSR_OFLOW)
39 #define CDTRDSR (CDTR_IFLOW | CDSR_OFLOW)
40 #endif
41
42 typedef struct {
43 char find;
44 char input;
45 int offset; /* can only be a 1 or a -1 */
46 } Args;
47
48 typedef struct {
49 ssize_t sizebm;
50 ssize_t sizesm;
51 } Sizes;
52
53 typedef struct{
54 int crinlf;
55 int crtolf;
56 int lfincr;
57 int lftocr;
58 int nocr;
59 int nolf;
60 } CRLFOpt;
61
62 /* including here to access CRLFOpt */
63 #include "config.h"
64
65 static int gettermattr(int dv, struct TERMIOS_STRUCT *optst);
66 static int settermattr(int dv, const struct TERMIOS_STRUCT *optst);
67 static void settermspd(unsigned int lispeed, unsigned int lospeed, struct TERMIOS_STRUCT *optst);
68 static int openport();
69 static unsigned int strtoui(const char *str, const char *msg, unsigned int min);
70 static int troptions(CRLFOpt *options, const char *str);
71 static int setroptions();
72 static inline unsigned int lsbmask(unsigned int n);
73 static inline int termchck(const void *term);
74 static void interchck();
75 static void cechck();
76 static void *writeport(void *unused);
77 static void *readport(void *unused);
78 static void getcmd(int escape);
79 static inline void replacechar(char *buff, ssize_t size, const Args *args);
80 static inline ssize_t addchar(char *buff, ssize_t size, int *scratch, const Sizes *msizes, const Args *args);
81 static inline ssize_t rmchar(char *buff, ssize_t sread, int *scratch, const Args *args);
82 static void sighandl(int signo);
83 static void die(int code, const char *msg, ...);
84
85 static struct TERMIOS_STRUCT cntrl, origterm = { 0 }, newterm = { 0 };
86
87 static Args bsargs = {DEL, DEL, 0};
88 static const Args nocrargs = {CR, 0, 0},
89 nolfargs = {LF, '\0', 0}, /* null-terminator is for use inside `getcmd()` */
90 lfincrargs = {CR, LF, 1},
91 crinlfargs = {LF, CR, -1};
92 static struct timespec wts;
93 static const unsigned int uintmax = ~(unsigned int)0; /* unsigned -1 to return on error */
94 static int interactive = 0;
95 static char *writebuff = NULL;
96 static char *readbuff = NULL;
97 static int *scratchr = NULL;
98 static int *scratchw = NULL;
99 static int fd = -1;
100
101 int
102 gettermattr(int dv, struct TERMIOS_STRUCT *optst)
103 {
104 #ifdef __linux__
105 return ioctl(dv, TCGETS2, optst);
106 #else
107 return tcgetattr(dv, optst);
108 #endif
109 }
110
111 int
112 settermattr(int dv, const struct TERMIOS_STRUCT *optst)
113 {
114 #ifdef __linux__
115 return ioctl(dv, TCSETS2, optst);
116 #else
117 return tcsetattr(dv, TCSANOW, optst);
118 #endif
119 }
120
121 void
122 settermspd(unsigned int lispeed, unsigned int lospeed, struct TERMIOS_STRUCT *optst)
123 {
124 #ifdef __linux__
125 optst->c_cflag &= ~CBAUD;
126 optst->c_cflag |= BOTHER;
127 optst->c_ispeed = ispeed;
128 optst->c_ospeed = ospeed;
129 #else
130 cfsetispeed(optst, ispeed);
131 cfsetospeed(optst, ospeed);
132 #endif
133 }
134
135 int
136 openport()
137 {
138 int flags;
139 struct flock fl = { 0 };
140
141 if (verbose) fprintf(stderr, "opening \"%s\"\n", line);
142
143 fd = open(line, (O_RDWR | O_NOCTTY | O_NDELAY));
144 if (fd == -1) {
145 perror("error opening device");
146 exit(1);
147 }
148
149 if (verbose) fprintf(stderr, "checking if \"%s\" is a TTY\n", line);
150
151 if (!isatty(fd))
152 die(2, "device \"%s\" is not a TTY\n", line);
153
154 flags = fcntl(fd, F_GETFL, 0);
155 /* opened to check with non-blocking mode, now set to blocking */
156 flags &= ~(O_NONBLOCK | O_NDELAY);
157
158 if (fcntl(fd, F_SETFL, flags) == -1) {
159 perror("fcntl");
160 die(1, "exiting now\n");
161 }
162
163 if (gettermattr(fd, &cntrl) == -1)
164 die(1, "failed to get device attributes\n");
165
166 fl.l_type = F_WRLCK;
167 fl.l_whence = SEEK_SET;
168 fl.l_start = 0;
169 fl.l_len = 0;
170
171 if (exclusive) {
172 if (fcntl(fd, F_SETLK, &fl) == -1) {
173 perror("fcntl");
174 die(1, "failed to lock the device, exiting now\n");
175 }
176 }
177 /*
178 * Baud
179 */
180 if (verbose) fprintf(stderr, "setting baudrate [RX:%u | TX:%u]\n", ispeed, ospeed);
181
182 settermspd(ispeed, ospeed, &cntrl);
183 if (settermattr(fd, &cntrl) == -1)
184 die(2,"failed to set baudrate [RX:%u | TX:%u]\n", ispeed, ospeed);
185
186 /*
187 * Parity
188 */
189 cntrl.c_lflag = 0;
190 cntrl.c_iflag &= ~(ISTRIP | BRKINT);
191 cntrl.c_cflag &= ~(PARENB | PARODD);
192
193 if (verbose) fprintf(stderr, "setting parity [even: %d, odd: %d]\n", parity & 1, (parity & 2) >> 1);
194
195 if (parity == 1)
196 cntrl.c_cflag |= PARENB;
197 if (parity == 2)
198 cntrl.c_cflag |= PARENB | PARODD;
199
200 if (settermattr(fd, &cntrl) == -1)
201 die(2, "failed to set parity [even: %d, odd: %d]\n", parity & 1, (parity & 2) >> 1);
202
203
204 /*
205 * Flow control
206 */
207 if (verbose) {
208 /* it's so ugly and beautiful at the same time */
209 int t = (((hard & 1) << 1) & (hard & 2)) >> 1;
210 fprintf(stderr, "setting flow control [XON/XOFF: %d, RTS/CTS: %d, DTR/DSR: %d, DCD: %d]\n",\
211 soft, t ^ ((hard & 1) << 1) >> 1, t ^ (hard & 2) >> 1, t);
212 }
213
214 cntrl.c_cflag |= CLOCAL;
215 cntrl.c_iflag &= ~IXONXOFF;
216
217 if (soft)
218 cntrl.c_iflag |= IXONXOFF;
219
220 if (hard == 1) {
221 cntrl.c_cflag |= CRTSCTS;
222 } else if (hard == 2) {
223 #ifndef CDTRDSR
224 fprintf(stderr, "DTR/DSR flow control is not supported on this platform\n"
225 "enabling this option does nothing!\n");
226 #else
227 cntrl.c_lflag |= CDTRDSR;
228 #endif
229 } else if (hard == 3) {
230 cntrl.c_cflag &= ~CLOCAL;
231 #ifdef CCAR_OFLOW
232 cntrl.c_lflag |= CCAR_OFLOW;
233 #endif
234 }
235
236 if (settermattr(fd, &cntrl) == -1)
237 die(2, "failed to set flow control\n");
238
239 /*
240 * CR and LF
241 */
242 cntrl.c_iflag &= ~(ICRNL | INLCR);
243 cntrl.c_oflag &= ~(OCRNL | ONLRET | ONLCR);
244
245 if (setroptions())
246 die(2, "failed to set cr-lf translation options\n");
247
248 /*
249 * Data bits
250 */
251 if (verbose) fprintf(stderr, "setting data bits [%c]\n", datab);
252
253 tcflag_t db = CS8;
254 switch (datab) {
255 case '5':
256 db = CS5; break;
257 case '6':
258 db = CS6; break;
259 case '7':
260 db = CS7; break;
261 default:
262 break;
263 }
264 cntrl.c_cflag &= ~CSIZE;
265 cntrl.c_cflag |= db;
266
267 if (settermattr(fd, &cntrl) == -1)
268 die(2, "failed to set data bits [%c]\n", datab);
269
270 /*
271 * Stop bits
272 */
273 if (verbose) fprintf(stderr, "setting stop bits [%d]\n", stopb+1);
274
275 if (stopb)
276 cntrl.c_cflag |= CSTOPB;
277 else
278 cntrl.c_cflag &= ~CSTOPB;
279
280 if (settermattr(fd, &cntrl) == -1)
281 die(1, "failed to set stop bits [%d]\n", stopb+1);
282
283 cntrl.c_cc[VMIN] = 1;
284 cntrl.c_cc[VTIME] = 0;
285
286 /* flush both hardware buffers */
287 #ifdef __linux__
288 ioctl(fd, TCFLSH, TCIOFLUSH);
289 #else
290 tcflush(fd, TCIOFLUSH);
291 #endif
292 return(fd);
293 }
294
295 unsigned int
296 strtoui(const char *str, const char *msg, unsigned int min)
297 {
298 long t;
299 char *endptr;
300
301 t = strtol(str, &endptr, 10);
302 if (errno != 0 || *endptr != '\0' || t < min || t > uintmax) {
303 fprintf(stderr, msg, str);
304 return uintmax;
305 }
306 /*
307 * conversion like this results in 'undefined behavior' according to C spec,
308 * but almost all compilers will just truncate the value so it's OK
309 */
310 return (unsigned int)t;
311 }
312
313 int
314 troptions(CRLFOpt *options, const char *str)
315 {
316 switch (str[0]) {
317 case 'l':
318 if (!strcmp(str, "lf-in-cr")) {
319 options->lfincr ^= 1; return 0;
320 } else if (!strcmp(str, "lf-to-cr")) {
321 options->lftocr ^= 1; return 0;
322 } else {
323 goto trerr;
324 }
325 break;
326 case 'c':
327 if (!strcmp(str, "cr-in-lf")) {
328 options->crinlf ^= 1; return 0;
329 } else if (!strcmp(str, "cr-to-lf")) {
330 options->crtolf ^= 1; return 0;
331 } else {
332 goto trerr;
333 }
334 break;
335 case 'n':
336 if (!strcmp(str, "no-lf")) {
337 options->nolf ^= 1; return 0;
338 } else if (!strcmp(str, "no-cr")) {
339 options->nocr ^= 1; return 0;
340 } else {
341 goto trerr;
342 }
343 break;
344 default:
345 trerr:
346 fprintf(stderr, "invalid translation option: %s\n", str);
347 return 1;
348 }
349 }
350
351 int
352 setroptions()
353 {
354 cntrl.c_iflag ^= (ICRNL & lsbmask(itropts.crtolf));
355 cntrl.c_iflag ^= (INLCR & lsbmask(itropts.lftocr));
356 cntrl.c_oflag ^= (OCRNL & lsbmask(tropts.crtolf));
357 cntrl.c_oflag ^= (ONLRET & lsbmask(tropts.lftocr));
358 cntrl.c_oflag ^= (ONLCR & lsbmask(tropts.crinlf));
359
360 return settermattr(fd, &cntrl);
361 }
362
363 inline unsigned int
364 lsbmask(unsigned int n)
365 {
366 /* if n == 1, n fills with all 1s, 0 stays the same */
367 for (int i = (sizeof(int) * CHAR_BIT); i > 0; i--)
368 n |= (n << 1);
369 return n;
370 }
371
372 inline int
373 termchck(const void *term)
374 {
375 for (size_t i = 0; i < sizeof(struct TERMIOS_STRUCT); i++)
376 if (((char*)term)[i] != 0)
377 return 1;
378
379 return 0;
380 }
381
382 void *
383 writeport(void *unused)
384 {
385 Sizes msizes;
386 struct timespec timeout, ts;
387
388 timeout.tv_sec = stimeout;
389 timeout.tv_nsec = nstimeout;
390 ts.tv_sec = swritedelay;
391 ts.tv_nsec = nswritedelay;
392 wts.tv_sec = 0;
393 wts.tv_nsec = chardelay;
394 msizes.sizesm = scratchwsz;
395 msizes.sizebm = wbuffsz;
396
397 writebuff = malloc(wbuffsz * sizeof(char));
398 scratchw = malloc(scratchwsz * sizeof(int));
399
400 if (writebuff == NULL)
401 die(1, "buffer allocation failed\n");
402 if (scratchw == NULL)
403 die(1, "scratch buffer allocation failed\n");
404
405 int escape = 0;
406
407 for (;;) {
408 ssize_t inln = read(STDIN_FILENO, writebuff, wbuffsz - 1);
409 if (inln > 0) {
410 if (escape) {
411 getcmd(escape);
412 escape = 0;
413 }
414 if (writebuff[0] == escapechar) {
415 if (inln > 1)
416 getcmd(escape);
417 else
418 escape = 1;
419 }
420
421 if (bsargs.find != bsargs.input)
422 replacechar(writebuff, inln, &bsargs);
423 if (tropts.nocr)
424 inln = rmchar(writebuff, inln, scratchw, &nocrargs);
425 if (tropts.nolf)
426 inln = rmchar(writebuff, inln, scratchw, &nolfargs);
427 if (tropts.lfincr)
428 inln = addchar(writebuff, inln, scratchw, &msizes, &lfincrargs);
429
430 if (inln > 1) {
431 for (int i = 0; i <= inln; i++) {
432 write(fd, &writebuff[i], 1);
433 nanosleep(&wts, NULL);
434 }
435 } else {
436 write(fd, writebuff, 1);
437 }
438 } else if (inln == 0 && !interactive) {
439 break;
440 }
441 nanosleep(&ts, NULL);
442 }
443 /* ust kills itself upon receiving a signal so no fancy `nanosleep()` features needed */
444 nanosleep(&timeout, NULL);
445 die(0, "\n[EOF]\n");
446 return NULL;
447 }
448
449 void *
450 readport(void *unused)
451 {
452 Sizes msizes;
453 struct timespec ts;
454 ts.tv_sec = sreaddelay;
455 ts.tv_nsec = nsreaddelay;
456 msizes.sizesm = scratchrsz;
457 msizes.sizebm = rbuffsz;
458
459 readbuff = malloc(rbuffsz * sizeof(char));
460 scratchr = malloc(scratchrsz * sizeof(int));
461
462 if (readbuff == NULL)
463 die(1, "buffer allocation failed\n");
464 if (scratchr == NULL)
465 die(1, "scratch buffer allocation failed\n");
466
467 interchck();
468
469 /* disable stdout buffering */
470 setvbuf(stdout, NULL, _IONBF, 0);
471
472 for (;;) {
473 ssize_t outln = read(fd, readbuff, rbuffsz - 1);
474 if (outln > 0) {
475 if (itropts.nocr)
476 outln = rmchar(readbuff, outln, scratchr, &nocrargs);
477 if (itropts.nolf)
478 outln = rmchar(readbuff, outln, scratchr, &nolfargs);
479 if (itropts.crinlf)
480 outln = addchar(readbuff, outln, scratchr, &msizes, &crinlfargs);
481 if (itropts.lfincr)
482 outln = addchar(readbuff, outln, scratchr, &msizes, &lfincrargs);
483
484 write(STDOUT_FILENO, readbuff, outln);
485 }
486 nanosleep(&ts, NULL);
487 }
488 if (isatty(STDIN_FILENO))
489 settermattr(STDIN_FILENO, &origterm);
490 return NULL;
491 }
492
493 inline void __attribute__((hot))
494 replacechar(char *buff, ssize_t size, const Args *args)
495 {
496 for (int i = 0; i < size; i++)
497 if (buff[i] == args->find)
498 buff[i] = args->input;
499 }
500
501 inline ssize_t __attribute__((hot))
502 addchar(char *buff, ssize_t size, int *scratch, const Sizes *sizes, const Args *args)
503 {
504 int to, c;
505 c = 0;
506 /* turns negative numbers into 1, positive into 0 */
507 to = ((args->offset >> ((sizeof(int) * CHAR_BIT) - 1)) & 1);
508
509 for (int i = 0; i < size; i++) {
510 if (buff[i] == args->find) {
511 scratch[c] = i;
512 c++;
513 }
514 if (c >= sizes->sizesm)
515 break;
516 }
517 if (!c)
518 return(size);
519 if (size == 1 && c == 1 && scratch[0] == 0) {
520 buff[to ^ 1] = args->input;
521 buff[to] = args->find;
522 return 2;
523 }
524 if ((size + c) > sizes->sizebm)
525 c = sizes->sizebm - size;
526
527 for (int i = c - 1; i >= 0; i--) {
528 int t = scratch[i];
529 memmove(&buff[t] + 1, &buff[t], (size + c) - t);
530 buff[t + (to ^ 1)] = args->input;
531 }
532 printf("%s\n", buff);
533 return (size + c);
534 }
535
536 inline ssize_t __attribute__((hot))
537 rmchar(char *buff, ssize_t size, int *scratch, const Args *args)
538 {
539 int c = 0;
540 for (int i = 0; i < size; i++) {
541 if (buff[i] == args->find) {
542 scratch[c] = i;
543 c++;
544 }
545 }
546 if (!c)
547 return(size);
548 if (size == 1 && c == 1 && scratch[0] == 0)
549 return 0;
550
551 for (int i = c; i >= 0; i--) {
552 int t = scratch[i];
553 memmove(&buff[t], &buff[t] + 1, (size + c) - t);
554 }
555 return(size - c);
556 }
557
558 void
559 interchck()
560 {
561 if (interactive) {
562 if (gettermattr(STDIN_FILENO, &origterm) < 0 )
563 die(1, "failed to get terminal attributes\n");
564
565 newterm = origterm;
566
567 if (!canonical)
568 newterm.c_lflag &= ~ECHO;
569 if (!half)
570 newterm.c_lflag &= ~ICANON;
571
572 if (soft)
573 newterm.c_iflag |= IXONXOFF;
574
575 newterm.c_iflag |= INLCR;
576 newterm.c_cc[VMIN] = minchars;
577 newterm.c_cc[VTIME] = 0;
578 newterm.c_cc[VINTR] = newterm.c_cc[VSUSP]\
579 = newterm.c_cc[VQUIT] = newterm.c_cc[VLNEXT]\
580 = newterm.c_cc[VDISCARD] = _POSIX_VDISABLE;
581
582 #ifdef VDSUSP
583 newterm.c_cc[VDSUSP] = _POSIX_VDISABLE;
584 #endif
585
586 if (backspace)
587 bsargs.input = BS;
588 else
589 bsargs.input = DEL;
590
591 bsargs.find = origterm.c_cc[VERASE];
592
593 if (settermattr(STDOUT_FILENO, &newterm) < 0)
594 die(1, "failed to set terminal attributes\n");
595 }
596 return;
597 }
598
599 void
600 cechck()
601 {
602 if ((!half || !canonical) && interactive) {
603 newterm.c_lflag |= ECHO;
604 newterm.c_lflag |= ICANON;
605 if (settermattr(STDIN_FILENO, &newterm) < 0)
606 fprintf(stderr, "failed to enable echo and/or canonical mode\n");
607 }
608 return;
609 }
610
611 void
612 getcmd(int escape)
613 {
614 Sizes msizes;
615 char cmdchar;
616 char ttr[64];
617 unsigned int tspd;
618
619 msizes.sizesm = scratchwsz;
620 msizes.sizebm = wbuffsz;
621
622 if (isatty(STDIN_FILENO) && isatty(STDOUT_FILENO))
623 interactive = 1;
624
625 interchck();
626
627 if (escape)
628 cmdchar = writebuff[0];
629 else
630 cmdchar = writebuff[1];
631
632 switch (cmdchar) {
633 case EOT: /* FALLTHROUGH */
634 case '.':
635 die(0,"\n[EOT]\n");
636 case 'b':
637 if (backspace)
638 bsargs.input = DEL;
639 else
640 bsargs.input = BS;
641 backspace ^= 1;
642 break;
643 case 'h':
644 if (half)
645 newterm.c_lflag &= ~ECHO;
646 else
647 newterm.c_lflag |= ECHO;
648 half ^= 1;
649 if (settermattr(STDOUT_FILENO, &newterm) < 0)
650 die(1, "failed to set terminal attributes\n");
651 break;
652 case 'c':
653 if (canonical)
654 newterm.c_lflag &= ~ICANON;
655 else
656 newterm.c_lflag |= ICANON;
657 canonical ^= 1;
658 if (settermattr(STDOUT_FILENO, &newterm) < 0)
659 die(1, "failed to set terminal attributes\n");
660 break;
661 case 'w':
662 cechck();
663 if (fgets(ttr, sizeof(ttr), stdin) != NULL) {
664 replacechar(ttr, 63, &nolfargs);
665 ssize_t frln;
666 int wfd = open(ttr, O_RDONLY);
667 if (wfd == -1) {
668 perror("error opening file");
669 goto finish;
670 }
671 while ((frln = read(wfd, writebuff, wbuffsz - 1)) > 0) {
672 for (int i = 0; i <= frln; i++) {
673 if (tropts.nocr)
674 frln = rmchar(writebuff, frln, scratchw, &nocrargs);
675 if (tropts.nolf)
676 frln = rmchar(writebuff, frln, scratchw, &nolfargs);
677 if (tropts.lfincr)
678 frln = addchar(writebuff, frln, scratchw, &msizes, &lfincrargs);
679
680 write(fd, &writebuff[i], 1);
681 nanosleep(&wts, NULL);
682 }
683 }
684 }
685 goto finish;
686 case 's':
687 cechck();
688 if (fgets(ttr, sizeof(ttr), stdin) != NULL) {
689 replacechar(ttr, 63, &nolfargs);
690 tspd = strtoui(ttr, "invalid speed\n", 0);
691 if (tspd != uintmax) {
692 ospeed = ispeed = tspd;
693 settermspd(ispeed, ospeed, &cntrl);
694 if (settermattr(fd, &cntrl) != 0)
695 fprintf(stderr, "failed to set baudrate "
696 "[RX:%u | TX:%u]\n", ispeed, ospeed);
697 }
698 }
699 goto finish;
700 case 'd':
701 cechck();
702 if (fgets(ttr, sizeof(ttr), stdin) != NULL) {
703 replacechar(ttr, 63, &nolfargs);
704 chardelay = strtoui(ttr, "invalid delay\n", 0);
705 if (chardelay != uintmax) {
706 wts.tv_sec = 0;
707 wts.tv_nsec = chardelay;
708 }
709 }
710 goto finish;
711 case 't':
712 cechck();
713 fprintf(stderr, "additional output translation option: ");
714 if (fgets(ttr, sizeof(ttr), stdin) != NULL) {
715 replacechar(ttr, 63, &nolfargs);
716 if(troptions(&tropts, ttr))
717 goto finish;
718 if (setroptions())
719 fprintf(stderr, "could not set new options\n");
720 }
721 goto finish;
722 case 'T':
723 cechck();
724 printf("additional input translation option: ");
725 if (fgets(ttr, sizeof(ttr), stdin) != NULL) {
726 replacechar(ttr, 63, &nolfargs);
727 if (troptions(&itropts, ttr))
728 goto finish;
729 if (setroptions())
730 fprintf(stderr, "could not set new options\n");
731 }
732 finish:
733 if (!half && interactive)
734 newterm.c_lflag &= ~ECHO;
735 if (!canonical && interactive)
736 newterm.c_lflag &= ~ICANON;
737 if (settermattr(STDIN_FILENO, &newterm) == -1)
738 fprintf(stderr, "failed to restore echo and/or canonical mode\n");
739 break;
740 case 'p':;
741 int st;
742 struct timespec ts;
743 ts.tv_sec = spulsedelay;
744 ts.tv_nsec = nspulsedelay;
745
746 if (ioctl(fd, TIOCMGET, &st) != 0) {
747 fprintf(stderr, "failed to get port status\n");
748 break;
749 }
750 st ^= TIOCM_DTR;
751 if (ioctl(fd, TIOCMSET, &st) != 0) {
752 fprintf(stderr, "failed to set DTR [assertion]\n");
753 break;
754 }
755 nanosleep(&ts, NULL);
756
757 st ^= TIOCM_DTR;
758 if (ioctl(fd, TIOCMSET, &st) != 0)
759 fprintf(stderr, "failed to set DTR [negation]\n");
760 break;
761 case BS: /* FALLTHROUGH */
762 case DEL:
763 case ESC:
764 break;
765 default:
766 fprintf(stderr, "not a valid command [%c]\n", cmdchar);
767 break;
768 }
769 return;
770 }
771
772 void
773 sighandl(int signo)
774 {
775 die(128 + signo, "\nrecieved signal [%d], exiting\n", signo);
776 }
777
778 void
779 die(int code, const char *msg, ...)
780 {
781 va_list fpa;
782 if (fd != -1)
783 close(fd);
784
785 if (writebuff)
786 free(writebuff);
787 if (readbuff)
788 free(readbuff);
789 if (scratchw)
790 free(scratchw);
791 if (scratchr)
792 free(scratchr);
793
794 if (termchck(&newterm))
795 settermattr(STDIN_FILENO, &origterm);
796
797 va_start(fpa, msg);
798 vfprintf(stderr, msg, fpa);
799 va_end(fpa);
800
801 exit(code);
802 }
803
804 int
805 main(int argc, char **argv)
806 {
807 char *t = NULL;
808 /* glibc's `asprintf()` won't set the string to NULL on failure automatically */
809 for (int i = 1; i < argc; i++ ) {
810 if (argv[i][0] == '-' && argv[i][1] >= '0' && argv[i][1] <= '9') {
811 asprintf(&t, "-s%s", argv[i] + 1);
812 if (!t) {
813 fprintf(stderr, "%s: cannot convert -# to -s#\n", argv[0]);
814 break;
815 }
816 argv[i] = t;
817 }
818 }
819
820 static struct option longopt[] = {
821 {"line", required_argument, NULL, 'l'},
822 {"speed", required_argument, NULL, 's'},
823 {"rx-speed", required_argument, NULL, 'i'},
824 {"echo", no_argument, NULL, 'h'},
825 {"canonical", no_argument, NULL, 'c'},
826 {"odd", no_argument, NULL, 'o'},
827 {"even", no_argument, NULL, 'e'},
828 {"hardware-rts-cts", no_argument, NULL, 'R'},
829 {"hardware-dtr-dsr", no_argument, NULL, 'r'},
830 {"software", no_argument, NULL, 'X'},
831 {"data", required_argument, NULL, 'D'},
832 {"delay", required_argument, NULL, 'd'},
833 {"min-chars", required_argument, NULL, 'm'},
834 {"timeout-ns", required_argument, NULL, 'f'},
835 {"timeout-s", required_argument, NULL, 'F'},
836 {"stop-bits", no_argument, NULL, 'S'},
837 {"backspace", no_argument, NULL, 'b'},
838 {"translation", required_argument, NULL, 't'},
839 {"input-translation", required_argument, NULL, 'T'},
840 {"verbose", no_argument, NULL, 'v'},
841 {NULL, 0, NULL, 0}
842 };
843
844 unsigned int tui;
845 int oind, rxspdset, devset, c;
846 oind = rxspdset = devset = 0;
847 while ((c = getopt_long(argc, argv, "D:F:T:d:f:i:l:m:s:t:RXbcehorv", longopt, &oind)) != -1) {
848 switch (c) {
849 case 0:
850 break;
851 case 'b':
852 backspace ^= 1; break;
853 case 'c':
854 canonical ^= 1; break;
855 case 'R':
856 hard ^= 1; break;
857 case 'r':
858 hard ^= 2; break;
859 case 'X':
860 soft ^= 1; break;
861 case 'd': /* FALLTHROUGH */
862 case 'f':
863 case 'F':;
864 char* endptr;
865 long t;
866 t = strtol(optarg, &endptr, 10);
867 if (errno != 0 || *endptr != '\0' || t < 0) {
868 fprintf(stderr, "%s: invalid character delay: %s\n", argv[0], optarg);
869 goto ustusage;
870 } else if (c == 'd') {
871 chardelay = t;
872 } else if (c == 'f'){
873 nstimeout = t;
874 } else {
875 stimeout = t;
876 }
877 break;
878 case 'D':
879 if (strlen(optarg) != 1 || !(optarg[0] >= '5' && optarg[0] <= '8')) {
880 fprintf(stderr, "%s: invalid number of data bits: %s\n", argv[0], optarg);
881 goto ustusage;
882 } else {
883 datab = optarg[0];
884 }
885 break;
886 case 'e':
887 parity ^= 1; break;
888 case 'o':
889 parity ^= 2; break;
890 case 'h':
891 half ^= 1; break;
892 case 'i':
893 tui = strtoui(optarg, "invalid rx speed: %s\n", 1);
894 if (tui == uintmax)
895 goto ustusage;
896 ispeed = tui;
897 rxspdset = 1;
898 break;
899 case 's':
900 tui = strtoui(optarg, "invalid speed: %s\n", 1);
901 if (tui == uintmax)
902 goto ustusage;
903 ospeed = tui;
904 break;
905 case 'S':
906 stopb ^= 1; break;
907 case 'l':
908 if (devset) {
909 fprintf(stderr, "%s: cannot specify multiple devices\n", argv[0]);
910 goto ustusage;
911 }
912 if (strlen(optarg) > 10) {
913 fprintf(stderr, "%s: device name too long\n", argv[0]);
914 goto ustusage;
915 }
916 if (strchr(optarg, '/')) {
917 strcpy(line, optarg);
918 devset = 1;
919 } else {
920 sprintf(line, "/dev/%s", optarg);
921 devset = 1;
922 }
923 break;
924 case 'm':
925 tui = strtoui(optarg, "invalid number of characters: %s\n", 1);
926 if (tui == uintmax)
927 goto ustusage;
928 minchars = tui;
929 break;
930 case 't':
931 if (troptions(&tropts, optarg))
932 goto ustusage;
933 break;
934 case 'T':
935 if (troptions(&itropts, optarg))
936 goto ustusage;
937 break;
938 case 'v':
939 verbose ^= 1; break;
940 default:
941 ustusage:
942 die(2, "usage: %s [--line|-l line] [--speed|-s #|-#] [--rx-speed|-i #]\n"
943 " [--data-bits|-D #] [--stop-bits|-S]"
944 " [--even|-e] [--odd|-o]\n"
945 " [--hardware-rts-cts|-R]"
946 " [--hardware-dtr-dsr|-r] [--software|-X]\n"
947 " [--delay|-d #] [--min-chars|-m #]"
948 " [--canonical|-c] [--echo|-h]\n"
949 " [--translation|-t option]"
950 " [--input-translation|-T option]\n"
951 " [--verbose|-v] [--backspace|-b]"
952 " [--timeout-s|-F #] [--timeout-ns|-f #]",
953 argv[0]);
954 break;
955 }
956 }
957 /*
958 * not the best practice, but in order for this `free()`
959 * to not get executed ust needs to be killed,
960 * which means the OS *should* free the memory
961 */
962 free(t);
963
964 if (!rxspdset)
965 ispeed = ospeed;
966
967 if (isatty(STDIN_FILENO) && isatty(STDOUT_FILENO))
968 interactive = 1;
969
970 signal(SIGHUP, sighandl);
971 signal(SIGINT, sighandl);
972 signal(SIGQUIT, sighandl);
973 signal(SIGTERM, sighandl);
974 signal(SIGCHLD, SIG_DFL);
975
976 fd = openport();
977
978 pthread_t readthread, writethread;
979 pthread_create(&writethread, NULL, writeport, NULL);
980 pthread_create(&readthread, NULL, readport, NULL);
981 pthread_join(writethread, NULL);
982 pthread_join(readthread, NULL);
983 }