5570aa576f70a010665a3b245988a6e909841866
[ust.git] / ust.c
1 #include <errno.h>
2 #include <fcntl.h>
3 #include <getopt.h>
4 #include <limits.h>
5 #include <signal.h>
6 #include <stdarg.h>
7 #include <stdio.h>
8 #include <stdlib.h>
9 #include <string.h>
10 #include <unistd.h>
11 #include <pthread.h>
12 #include <sys/ioctl.h>
13
14 #ifdef __linux__
15 #include <asm/termbits.h>
16 #include <asm/ioctls.h>
17 #else /* needed for termios2, which is itself needed for non-standard bauds */
18 #include <termios.h>
19 #endif
20
21 #include "config.h"
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 #if defined(CCDTR_IFLOW) && defined(CDSR_OFLOW)
30 #define CDTRDSR (CDTR_IFLOW | CDSR_OFLOW)
31 #endif
32 #define IXONXOFF (IXON | IXOFF)
33
34 typedef struct {
35 char find;
36 char input;
37 int offset; /* can only be a 1 or a -1 */
38 } Args;
39
40 typedef struct {
41 ssize_t sizebm;
42 ssize_t sizesm;
43 } Sizes;
44
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();
55 static void cechck();
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, ...);
64
65 #ifdef __linux__
66 static struct termios2 cntrl, origterm = {0}, newterm = {0};
67 #else
68 static struct termios cntrl, origterm = {0}, newterm = {0};
69 #endif
70
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;
82 static int fd = -1;
83 static int interactive = 0;
84
85 int
86 gettermattr(int dv, void *strct)
87 {
88 #ifdef __linux__
89 struct termios2 *optst = (struct termios2*)strct;
90 return ioctl(dv, TCGETS2, optst);
91 #else
92 struct termios *optst = (struct termios*)strct;
93 return tcgetattr(dv, optst);
94 #endif
95 }
96
97 int
98 settermattr(int dv, const void *strct)
99 {
100 #ifdef __linux__
101 struct termios2 *optst = (struct termios2*)strct;
102 return ioctl(dv, TCSETS2, optst);
103 #else
104 struct termios *optst = (struct termios*)strct;
105 return tcsetattr(dv, TCSANOW, optst);
106 #endif
107 }
108
109 void
110 settermspd(unsigned int lispeed, unsigned int lospeed, const void *strct)
111 {
112 #ifdef __linux__
113 struct termios2 *optst = (struct termios2*)strct;
114
115 optst->c_cflag &= ~CBAUD;
116 optst->c_cflag |= BOTHER;
117 optst->c_ispeed = ispeed;
118 optst->c_ospeed = ospeed;
119 #else
120 struct termios *optst = (struct termios*)strct;
121 cfsetispeed(optst, ispeed);
122 cfsetospeed(optst, ospeed);
123 #endif
124 }
125
126 int
127 openport()
128 {
129 if (verbose) fprintf(stderr, "opening \"%s\"\n", line);
130
131 fd = open(line, O_RDWR | O_NOCTTY | O_NDELAY);
132 if (fd == -1) {
133 perror("error opening device");
134 exit(1);
135 }
136
137 if (verbose) fprintf(stderr, "checking if \"%s\" is a TTY\n", line);
138
139 if (!isatty(fd))
140 die(2, "device \"%s\" is not a TTY\n", line);
141
142 int flags;
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");
148
149 if (gettermattr(fd, &cntrl) == -1)
150 die(1, "failed to get device attributes\n");
151
152 if (verbose) fprintf(stderr, "setting baudrate [RX:%u | TX:%u]\n", ispeed, ospeed);
153
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);
157
158 cntrl.c_lflag = 0;
159 cntrl.c_iflag &= ~(ISTRIP | BRKINT);
160 cntrl.c_cflag &= ~(PARENB | PARODD);
161
162 if (verbose) fprintf(stderr, "setting parity [even: %d, odd: %d]\n", parity & 1, (parity & 2) >> 1);
163
164 if (parity == 1)
165 cntrl.c_cflag |= PARENB;
166 if (parity == 2)
167 cntrl.c_cflag |= PARENB | PARODD;
168
169 if (settermattr(fd, &cntrl) == -1)
170 die(2, "failed to set parity [even: %d, odd: %d]\n", parity & 1, (parity & 2) >> 1);
171
172 if (verbose) {
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);
177 }
178
179 cntrl.c_cflag |= CLOCAL;
180 cntrl.c_iflag &= ~IXONXOFF;
181
182 if (soft)
183 cntrl.c_iflag |= IXONXOFF;
184
185 if (hard == 1) {
186 cntrl.c_cflag |= CRTSCTS;
187 } else if (hard == 2) {
188 #ifndef CDTRDSR
189 fprintf(stderr, "DTR/DSR flow control is not supported on this platform\n"
190 "enabling this option does nothing!\n");
191 #else
192 cntrl.c_lflag |= CDTRDSR;
193 #endif
194 } else if (hard == 3) {
195 cntrl.c_cflag &= ~CLOCAL;
196 #ifdef CCAR_OFLOW
197 cntrl.c_lflag |= CCAR_OFLOW;
198 #endif
199 }
200
201 if (settermattr(fd, &cntrl) == -1)
202 die(2, "failed to set flow control\n");
203
204 cntrl.c_iflag &= ~(ICRNL | INLCR);
205 cntrl.c_oflag &= ~(OCRNL | ONLRET | ONLCR);
206
207 if (setroptions())
208 die(2, "failed to set cr-lf translation options\n");
209
210 if (verbose) fprintf(stderr, "setting data bits [%c]\n", datab);
211
212 tcflag_t db = CS8;
213 switch (datab) {
214 case '5':
215 db = CS5; break;
216 case '6':
217 db = CS6; break;
218 case '7':
219 db = CS7; break;
220 default:
221 break;
222 }
223 cntrl.c_cflag &= ~CSIZE;
224 cntrl.c_cflag |= db;
225
226 if (settermattr(fd, &cntrl) == -1)
227 die(2, "failed to set data bits [%c]\n", datab);
228
229 if (verbose) fprintf(stderr, "setting stop bits [%d]\n", stopb+1);
230
231 if (stopb)
232 cntrl.c_cflag |= CSTOPB;
233 else
234 cntrl.c_cflag &= ~CSTOPB;
235
236 if (settermattr(fd, &cntrl) == -1)
237 die(1, "failed to set stop bits [%d]\n", stopb+1);
238
239 #ifdef __linux__
240 ioctl(fd, TCFLSH, TCIOFLUSH);
241 #else
242 tcflush(fd, TCIOFLUSH);
243 #endif
244 return(fd);
245 }
246
247 unsigned int
248 strtoui(const char *str, const char *msg, unsigned int min)
249 {
250 long t;
251 char *endptr;
252
253 t = strtol(str, &endptr, 10);
254 if (errno != 0 || *endptr != '\0' || t < min) {
255 fprintf(stderr, msg, str);
256 return uintmax;
257 }
258 /*
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
261 */
262 return (unsigned int)t;
263 /* termios's `speed_t` is a typedef for `unsigned int` */
264 }
265
266 int
267 troptions(CRLFOpt *options, const char *str)
268 {
269 switch (str[0]) {
270 case 'l':
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;
275 } else {
276 goto trerr;
277 }
278 break;
279 case 'c':
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;
284 } else {
285 goto trerr;
286 }
287 break;
288 case 'n':
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;
293 } else {
294 goto trerr;
295 }
296 break;
297 default:
298 trerr:
299 fprintf(stderr, "invalid translation option: %s\n", str);
300 return 1;
301 }
302 }
303
304 int
305 setroptions()
306 {
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));
312
313 return settermattr(fd, &cntrl);
314 }
315
316 inline unsigned int
317 lsbmask(unsigned int n)
318 {
319 /* if n == 1, n fills with all 1s, 0 stays the same */
320 for (int i = (sizeof(int) * CHAR_BIT); i > 0; i--)
321 n |= (n << 1);
322 return n;
323 }
324
325 inline int
326 termchck(const void *term)
327 {
328 #ifdef __linux__
329 struct termios2 *iterm = (struct termios2*)term;
330 #define TERMIOS_STRUCT termios2
331 #else
332 struct termios *iterm = (struct termios*)term;
333 #define TERMIOS_STRUCT termios
334 #endif
335 for (size_t i = 0; i < sizeof(struct TERMIOS_STRUCT); i++)
336 if (((char*)iterm)[i] != 0)
337 return 1;
338
339 return 0;
340 }
341
342 void *
343 writeport(void *unused)
344 {
345 Sizes msizes;
346 struct timespec ts;
347 ts.tv_sec = swritedelay;
348 ts.tv_nsec = nswritedelay;
349 wts.tv_sec = 0;
350 wts.tv_nsec = chardelay;
351 msizes.sizesm = scratchwsz;
352 msizes.sizebm = wbuffsz;
353
354 writebuff = malloc(wbuffsz * sizeof(char));
355 scratchw = malloc(scratchwsz * sizeof(int));
356
357 if (writebuff == NULL)
358 die(1, "buffer allocation failed\n");
359 if (scratchw == NULL)
360 die(1, "scratch buffer allocation failed\n");
361
362 int escape = 0;
363
364 for (;;) {
365 ssize_t inln = read(STDIN_FILENO, writebuff, wbuffsz - 1);
366 if (inln > 0) {
367 if (escape) {
368 getcmd(escape);
369 escape = 0;
370 }
371 if (writebuff[0] == escapechar) {
372 if (inln > 1)
373 getcmd(escape);
374 else
375 escape = 1;
376 }
377
378 if (bsargs.find != bsargs.input)
379 replacechar(writebuff, inln, &bsargs);
380 if (tropts.nocr)
381 inln = rmchar(writebuff, inln, scratchw, &nocrargs);
382 if (tropts.nolf)
383 inln = rmchar(writebuff, inln, scratchw, &nolfargs);
384 if (tropts.lfincr)
385 inln = addchar(writebuff, inln, scratchw, &msizes, &lfincrargs);
386
387 if (inln > 1) {
388 for (int i = 0; i <= inln; i++) {
389 write(fd, &writebuff[i], 1);
390 nanosleep(&wts, NULL);
391 }
392 } else {
393 write(fd, writebuff, 1);
394 }
395 }
396 nanosleep(&ts, NULL);
397 }
398 return NULL;
399 }
400
401 void *
402 readport(void *unused)
403 {
404 Sizes msizes;
405 struct timespec ts;
406 ts.tv_sec = sreaddelay;
407 ts.tv_nsec = nsreaddelay;
408 msizes.sizesm = scratchrsz;
409 msizes.sizebm = rbuffsz;
410
411 readbuff = malloc(rbuffsz * sizeof(char));
412 scratchr = malloc(scratchrsz * sizeof(int));
413
414 if (readbuff == NULL)
415 die(1, "buffer allocation failed\n");
416 if (scratchr == NULL)
417 die(1, "scratch buffer allocation failed\n");
418
419 interchck();
420
421 /* disable stdout buffering */
422 setvbuf(stdout, NULL, _IONBF, 0);
423
424 for (;;) {
425 ssize_t outln = read(fd, readbuff, rbuffsz - 1);
426 if (outln > 0) {
427 if (itropts.nocr)
428 outln = rmchar(readbuff, outln, scratchr, &nocrargs);
429 if (itropts.nolf)
430 outln = rmchar(readbuff, outln, scratchr, &nolfargs);
431 if (itropts.crinlf)
432 outln = addchar(readbuff, outln, scratchr, &msizes, &crinlfargs);
433 if (itropts.lfincr)
434 outln = addchar(readbuff, outln, scratchr, &msizes, &lfincrargs);
435
436 write(STDOUT_FILENO, readbuff, outln);
437 }
438 nanosleep(&ts, NULL);
439 }
440 if (isatty(STDIN_FILENO))
441 settermattr(STDIN_FILENO, &origterm);
442 return NULL;
443 }
444
445 inline void __attribute__((hot))
446 replacechar(char *buff, ssize_t size, const Args *args)
447 {
448 for (int i = 0; i < size; i++)
449 if (buff[i] == args->find)
450 buff[i] = args->input;
451 }
452
453 inline ssize_t __attribute__((hot))
454 addchar(char *buff, ssize_t size, int *scratch, const Sizes *sizes, const Args *args)
455 {
456 int to, c;
457 c = 0;
458 /* turns negative numbers into 1, positive into 0 */
459 to = ((args->offset >> ((sizeof(int) * CHAR_BIT) - 1)) & 1);
460
461 for (int i = 0; i < size; i++) {
462 if (buff[i] == args->find) {
463 scratch[c] = i;
464 c++;
465 }
466 if (c >= sizes->sizesm)
467 break;
468 }
469 if (!c)
470 return(size);
471 if (size == 1 && c == 1 && scratch[0] == 0) {
472 buff[to ^ 1] = args->input;
473 buff[to] = args->find;
474 return 2;
475 }
476 if ((size + c) > sizes->sizebm)
477 c = sizes->sizebm - size;
478
479 for (int i = c - 1; i >= 0; i--) {
480 int t = scratch[i];
481 memmove(&buff[t] + 1, &buff[t], (size + c) - t);
482 buff[t + (to ^ 1)] = args->input;
483 }
484 printf("%s\n", buff);
485 return (size + c);
486 }
487
488 inline ssize_t __attribute__((hot))
489 rmchar(char *buff, ssize_t size, int *scratch, const Args *args)
490 {
491 int c = 0;
492 for (int i = 0; i < size; i++) {
493 if (buff[i] == args->find) {
494 scratch[c] = i;
495 c++;
496 }
497 }
498 if (!c)
499 return(size);
500 if (size == 1 && c == 1 && scratch[0] == 0)
501 return 0;
502
503 for (int i = c; i >= 0; i--) {
504 int t = scratch[i];
505 memmove(&buff[t], &buff[t] + 1, (size + c) - t);
506 }
507 return(size - c);
508 }
509
510 void
511 interchck()
512 {
513 if (interactive) {
514 if (gettermattr(STDIN_FILENO, &origterm) < 0 )
515 die(1, "failed to get terminal attributes\n");
516
517 newterm = origterm;
518
519 if (!canonical)
520 newterm.c_lflag &= ~ECHO;
521 if (!half)
522 newterm.c_lflag &= ~ICANON;
523
524 if (soft)
525 newterm.c_iflag |= IXONXOFF;
526
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;
532
533 if (backspace)
534 bsargs.input = BS;
535 else
536 bsargs.input = DEL;
537
538 bsargs.find = origterm.c_cc[VERASE];
539
540 if (settermattr(STDOUT_FILENO, &newterm) < 0)
541 die(1, "failed to set terminal attributes\n");
542 }
543 return;
544 }
545
546 void
547 cechck()
548 {
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");
554 }
555 return;
556 }
557
558 void
559 getcmd(int escape)
560 {
561 Sizes msizes;
562 msizes.sizesm = scratchwsz;
563 msizes.sizebm = wbuffsz;
564
565 char cmdchar;
566 char ttr[64];
567 unsigned int tspd;
568
569 if (isatty(STDIN_FILENO) && isatty(STDOUT_FILENO))
570 interactive = 1;
571
572 interchck();
573
574 if (escape)
575 cmdchar = writebuff[0];
576 else
577 cmdchar = writebuff[1];
578
579 switch (cmdchar) {
580 case EOT: /* FALLTHROUGH */
581 case '.':
582 die(0,"\n[EOT]\n");
583 break;
584 case 'b':
585 if (backspace)
586 bsargs.input = DEL;
587 else
588 bsargs.input = BS;
589 backspace ^= 1;
590 break;
591 case 'h':
592 if (half)
593 newterm.c_lflag &= ~ECHO;
594 else
595 newterm.c_lflag |= ECHO;
596 half ^= 1;
597 if (settermattr(STDOUT_FILENO, &newterm) < 0)
598 die(1, "failed to set terminal attributes\n");
599 break;
600 case 'c':
601 if (canonical)
602 newterm.c_lflag &= ~ICANON;
603 else
604 newterm.c_lflag |= ICANON;
605 canonical ^= 1;
606 if (settermattr(STDOUT_FILENO, &newterm) < 0)
607 die(1, "failed to set terminal attributes\n");
608 break;
609 case 'w':
610 cechck();
611 if (fgets(ttr, sizeof(ttr), stdin) != NULL) {
612 replacechar(ttr, 63, &nolfargs);
613 ssize_t frln;
614 int wfd = open(ttr, O_RDONLY);
615 if (wfd == -1) {
616 perror("error opening file");
617 goto finish;
618 }
619 while ((frln = read(wfd, writebuff, wbuffsz - 1)) > 0) {
620 for (int i = 0; i <= frln; i++) {
621 if (tropts.nocr)
622 frln = rmchar(writebuff, frln, scratchw, &nocrargs);
623 if (tropts.nolf)
624 frln = rmchar(writebuff, frln, scratchw, &nolfargs);
625 if (tropts.lfincr)
626 frln = addchar(writebuff, frln, scratchw, &msizes, &lfincrargs);
627
628 write(fd, &writebuff[i], 1);
629 nanosleep(&wts, NULL);
630 }
631 }
632 }
633 goto finish;
634 case 's':
635 cechck();
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);
645 }
646 }
647 goto finish;
648 case 'd':
649 cechck();
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) {
654 wts.tv_sec = 0;
655 wts.tv_nsec = chardelay;
656 }
657 }
658 goto finish;
659 case 't':
660 cechck();
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))
665 goto finish;
666 if (setroptions())
667 fprintf(stderr, "could not set new options\n");
668 }
669 goto finish;
670 case 'T':
671 cechck();
672 printf("additional input translation option: ");
673 if (fgets(ttr, sizeof(ttr), stdin) != NULL) {
674 replacechar(ttr, 63, &nolfargs);
675 if (troptions(&itropts, ttr))
676 goto finish;
677 if (setroptions())
678 fprintf(stderr, "could not set new options\n");
679 }
680 finish:
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");
687 break;
688 case 'p':;
689 int st;
690 struct timespec ts;
691 ts.tv_sec = spulsedelay;
692 ts.tv_nsec = nspulsedelay;
693
694 if (ioctl(fd, TIOCMGET, &st) != 0) {
695 fprintf(stderr, "failed to get port status\n");
696 break;
697 }
698 st ^= TIOCM_DTR;
699 if (ioctl(fd, TIOCMSET, &st) != 0) {
700 fprintf(stderr, "failed to set DTR [assertion]\n");
701 break;
702 }
703 nanosleep(&ts, NULL);
704
705 st ^= TIOCM_DTR;
706 if (ioctl(fd, TIOCMSET, &st) != 0)
707 fprintf(stderr, "failed to set DTR [negation]\n");
708 break;
709 case BS: /* FALLTHROUGH */
710 case DEL:
711 case ESC:
712 break;
713 default:
714 fprintf(stderr, "not a valid command [%c]\n", cmdchar);
715 break;
716 }
717 return;
718 }
719
720 void
721 sighandl(int signo)
722 {
723 die(128 + signo, "\nrecieved signal [%d], exiting\n", signo);
724 }
725
726 void
727 die(int code, const char *msg, ...)
728 {
729 va_list fpa;
730 if (fd != -1)
731 close(fd);
732
733 if (writebuff)
734 free(writebuff);
735 if (readbuff)
736 free(readbuff);
737 if (scratchw)
738 free(scratchw);
739 if (scratchr)
740 free(scratchr);
741
742 if (termchck(&newterm))
743 settermattr(STDIN_FILENO, &origterm);
744
745 va_start(fpa, msg);
746 vfprintf(stderr, msg, fpa);
747 va_end(fpa);
748
749 exit(code);
750 }
751
752 int
753 main(int argc, char **argv)
754 {
755 char *t = NULL;
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);
760 if (!t) {
761 fprintf(stderr, "cannot convert -# to -s#\n");
762 break;
763 }
764 argv[i] = t;
765 }
766 }
767
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'},
787 {NULL, 0, NULL, 0}
788 };
789
790 unsigned int tui;
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) {
794 switch (c) {
795 case 0:
796 break;
797 case 'b':
798 backspace ^= 1; break;
799 case 'c':
800 canonical ^= 1; break;
801 case 'R':
802 hard ^= 1; break;
803 case 'r':
804 hard ^= 2; break;
805 case 'X':
806 soft ^= 1; break;
807 case 'd':;
808 char* endptr;
809 long t;
810 t = strtol(optarg, &endptr, 10);
811 if (errno != 0 || *endptr != '\0' || t < 0) {
812 fprintf(stderr, "invalid character delay\n");
813 goto ustusage;
814 } else {
815 chardelay = t;
816 }
817 break;
818 case 'D':
819 if (strlen(optarg) != 1 || !(optarg[0] >= '5' && optarg[0] <= '8')) {
820 fprintf(stderr, "invalid number of data bits: %s\n", optarg);
821 goto ustusage;
822 } else {
823 datab = optarg[0];
824 }
825 break;
826 case 'e':
827 parity ^= 1; break;
828 case 'o':
829 parity ^= 2; break;
830 case 'h':
831 half ^= 1; break;
832 case 'i':
833 tui = strtoui(optarg, "invalid rx speed: %s\n", 1);
834 if (tui == uintmax)
835 goto ustusage;
836 ispeed = tui;
837 rxspdset = 1;
838 break;
839 case 's':
840 tui = strtoui(optarg, "invalid speed: %s\n", 1);
841 if (tui == uintmax)
842 goto ustusage;
843 ospeed = tui;
844 break;
845 case 'S':
846 stopb ^= 1; break;
847 case 'l':
848 if (devset) {
849 fprintf(stderr, "cannot specify multiple devices\n");
850 goto ustusage;
851 }
852 if (strlen(optarg) > 10) {
853 fprintf(stderr, "device name too long\n");
854 goto ustusage;
855 }
856 if (strchr(optarg, '/')) {
857 strcpy(line, optarg);
858 devset = 1;
859 } else {
860 sprintf(line, "/dev/%s", optarg);
861 devset = 1;
862 }
863 break;
864 case 'm':
865 tui = strtoui(optarg, "invalid number of characters: %s\n", 1);
866 if (tui == uintmax)
867 goto ustusage;
868 minchars = tui;
869 break;
870 case 't':
871 if (troptions(&tropts, optarg))
872 goto ustusage;
873 break;
874 case 'T':
875 if (troptions(&itropts, optarg))
876 goto ustusage;
877 break;
878 case 'v':
879 verbose ^= 1; break;
880 default:
881 ustusage:
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",
892 argv[0]);
893 break;
894 }
895 }
896 /*
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
900 */
901 free(t);
902
903 if (!rxspdset)
904 ispeed = ospeed;
905
906 if (isatty(STDIN_FILENO) && isatty(STDOUT_FILENO))
907 interactive = 1;
908
909 signal(SIGHUP, sighandl);
910 signal(SIGINT, sighandl);
911 signal(SIGQUIT, sighandl);
912 signal(SIGTERM, sighandl);
913
914 fd = openport();
915
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);
921 }