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