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