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