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