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