1 /* see LICENSE for license details */
18 #include <asm/termbits.h>
19 #include <asm/ioctls.h>
20 #else /* needed for termios2, which is itself needed for non-standard bauds */
31 #define IXONXOFF (IXON | IXOFF)
33 #if defined(CCDTR_IFLOW) && defined(CDSR_OFLOW)
34 #define CDTRDSR (CDTR_IFLOW | CDSR_OFLOW)
38 #define TERMIOS_STRUCT termios2
40 #define TERMIOS_STRUCT termios
46 int offset
; /* can only be a 1 or a -1 */
63 /* including here to access CRLFOpt */
66 static int gettermattr(int dv
, struct TERMIOS_STRUCT
*optst
);
67 static int settermattr(int dv
, const struct TERMIOS_STRUCT
*optst
);
68 static void settermspd(unsigned int lispeed
, unsigned int lospeed
, struct TERMIOS_STRUCT
*optst
);
69 static int openport(void);
70 static unsigned int strtoui(const char *str
, unsigned int min
);
71 static int troptions(CRLFOpt
*options
, const char *str
);
72 static int setroptions(void);
73 static inline unsigned int lsbmask(unsigned int n
);
74 static inline int termchck(const struct TERMIOS_STRUCT
*term
);
75 static void interchck(void);
76 static void cechck(void);
77 static void *writeport(void *);
78 static void *readport(void *);
79 static void getcmd(int escape
);
80 static inline void replacechar(char *buff
, ssize_t size
, const Args
*args
);
81 static inline ssize_t
addchar(char *buff
, ssize_t size
, int *scratch
, const Sizes
*msizes
, const Args
*args
);
82 static inline ssize_t
rmchar(char *buff
, ssize_t sread
, int *scratch
, const Args
*args
);
83 static void sighandl(int signo
);
84 static void die(int code
, const char *msg
, ...);
86 static struct TERMIOS_STRUCT cntrl
, /* device struct */
87 origterm
= { 0 }, /* controlling terminal structs */
90 static Args bsargs
= {DEL
, DEL
, 0};
91 static const Args nocrargs
= {CR
, 0, 0},
92 nolfargs
= {LF
, '\0', 0}, /* null-terminator is for use inside `getcmd()` */
93 lfincrargs
= {CR
, LF
, 1},
94 crinlfargs
= {LF
, CR
, 0};
96 static struct timespec wts
;
97 static const unsigned int uintmax
= ~(unsigned int)0; /* unsigned -1 used to return an error */
98 static int interactive
= 0;
99 static char *writebuff
= NULL
;
100 static char *readbuff
= NULL
;
101 static int *scratchr
= NULL
;
102 static int *scratchw
= NULL
;
107 gettermattr(int dv
, struct TERMIOS_STRUCT
*optst
)
110 return ioctl(dv
, TCGETS2
, optst
);
112 return tcgetattr(dv
, optst
);
117 settermattr(int dv
, const struct TERMIOS_STRUCT
*optst
)
120 return ioctl(dv
, TCSETS2
, optst
);
122 return tcsetattr(dv
, TCSANOW
, optst
);
127 settermspd(unsigned int lispeed
, unsigned int lospeed
, struct TERMIOS_STRUCT
*optst
)
130 optst
->c_cflag
&= ~CBAUD
;
131 optst
->c_cflag
|= BOTHER
;
132 optst
->c_ispeed
= ispeed
;
133 optst
->c_ospeed
= ospeed
;
135 cfsetispeed(optst
, ispeed
);
136 cfsetospeed(optst
, ospeed
);
144 struct flock fl
= { 0 };
146 if (verbose
) fprintf(stderr
, "opening \"%s\"\n", line
);
148 fd
= open(line
, (O_RDWR
| O_NOCTTY
| O_NDELAY
));
150 perror("error opening device");
154 if (verbose
) fprintf(stderr
, "checking if \"%s\" is a TTY\n", line
);
157 die(2, "device \"%s\" is not a TTY\n", line
);
160 flags
= fcntl(fd
, F_GETFL
, 0);
161 /* opened to check with non-blocking mode, now set to blocking */
162 flags
&= ~(O_NONBLOCK
| O_NDELAY
);
164 if (fcntl(fd
, F_SETFL
, flags
) == -1) {
166 die(1, "exiting now\n");
170 if (gettermattr(fd
, &cntrl
) == -1)
171 die(1, "failed to get device attributes\n");
175 fl
.l_whence
= SEEK_SET
;
176 if (fcntl(fd
, F_SETLK
, &fl
) == -1) {
178 die(1, "failed to lock the device, exiting now\n");
185 if (verbose
) fprintf(stderr
, "setting baudrate [RX:%u | TX:%u]\n", ispeed
, ospeed
);
187 settermspd(ispeed
, ospeed
, &cntrl
);
188 if (settermattr(fd
, &cntrl
) == -1)
189 die(2,"failed to set baudrate [RX:%u | TX:%u]\n", ispeed
, ospeed
);
195 cntrl
.c_iflag
&= ~(ISTRIP
| BRKINT
);
196 cntrl
.c_cflag
&= ~(PARENB
| PARODD
);
198 if (verbose
) fprintf(stderr
, "setting parity [even: %d, odd: %d]\n",\
199 parity
& 1, (parity
& 2) >> 1);
202 cntrl
.c_cflag
|= PARENB
;
204 cntrl
.c_cflag
|= PARENB
| PARODD
;
206 if (settermattr(fd
, &cntrl
) == -1)
207 die(2, "failed to set parity [even: %d, odd: %d]\n",\
208 parity
& 1, (parity
& 2) >> 1);
214 /* it's so ugly and beautiful at the same time */
215 int t
= (((hard
& 1) << 1) & (hard
& 2)) >> 1;
216 fprintf(stderr
, "setting flow control [XON/XOFF: %d, RTS/CTS: %d, DTR/DSR: %d, DCD: %d]\n",\
217 soft
, t
^ ((hard
& 1) << 1) >> 1, t
^ (hard
& 2) >> 1, t
);
220 cntrl
.c_cflag
|= CLOCAL
;
221 cntrl
.c_iflag
&= ~IXONXOFF
;
224 cntrl
.c_iflag
|= IXONXOFF
;
227 cntrl
.c_cflag
|= CRTSCTS
;
228 } else if (hard
== 2) {
230 fprintf(stderr
, "DTR/DSR flow control is not supported on this platform\n"
231 "enabling this option does nothing!\n");
233 cntrl
.c_lflag
|= CDTRDSR
;
235 } else if (hard
== 3) {
236 cntrl
.c_cflag
&= ~CLOCAL
;
238 cntrl
.c_lflag
|= CCAR_OFLOW
;
242 if (settermattr(fd
, &cntrl
) == -1)
243 die(2, "failed to set flow control\n");
248 cntrl
.c_iflag
&= ~(ICRNL
| INLCR
);
249 cntrl
.c_oflag
&= ~(OCRNL
| ONLRET
| ONLCR
);
252 die(2, "failed to set cr-lf translation options\n");
257 if (verbose
) fprintf(stderr
, "setting data bits [%c]\n", datab
);
270 cntrl
.c_cflag
&= ~CSIZE
;
273 if (settermattr(fd
, &cntrl
) == -1)
274 die(2, "failed to set data bits [%c]\n", datab
);
279 if (verbose
) fprintf(stderr
, "setting stop bits [%d]\n", stopb
+1);
282 cntrl
.c_cflag
|= CSTOPB
;
284 cntrl
.c_cflag
&= ~CSTOPB
;
286 if (settermattr(fd
, &cntrl
) == -1)
287 die(1, "failed to set stop bits [%d]\n", stopb
+1);
289 cntrl
.c_cc
[VMIN
] = 1;
290 cntrl
.c_cc
[VTIME
] = 0;
292 /* flush both hardware buffers */
294 ioctl(fd
, TCFLSH
, TCIOFLUSH
);
296 tcflush(fd
, TCIOFLUSH
);
302 strtoui(const char *str
, unsigned int min
)
307 t
= strtol(str
, &endptr
, 10);
308 if (errno
!= 0 || *endptr
!= '\0' || t
< min
|| t
> uintmax
) {
312 * conversion like this results in 'undefined behavior' according to C spec,
313 * but almost all compilers will just truncate the value so it's OK
315 return (unsigned int)t
;
319 troptions(CRLFOpt
*options
, const char *str
)
323 if (!strcmp(str
, "lf-in-cr")) {
324 options
->lfincr
^= 1; return 0;
325 } else if (!strcmp(str
, "lf-to-cr")) {
326 options
->lftocr
^= 1; return 0;
331 if (!strcmp(str
, "cr-in-lf")) {
332 options
->crinlf
^= 1; return 0;
333 } else if (!strcmp(str
, "cr-to-lf")) {
334 options
->crtolf
^= 1; return 0;
339 if (!strcmp(str
, "no-lf")) {
340 options
->nolf
^= 1; return 0;
341 } else if (!strcmp(str
, "no-cr")) {
342 options
->nocr
^= 1; return 0;
354 cntrl
.c_iflag
^= (ICRNL
& lsbmask(itropts
.crtolf
));
355 cntrl
.c_iflag
^= (INLCR
& lsbmask(itropts
.lftocr
));
356 cntrl
.c_oflag
^= (OCRNL
& lsbmask(tropts
.crtolf
));
357 cntrl
.c_oflag
^= (ONLRET
& lsbmask(tropts
.lftocr
));
358 cntrl
.c_oflag
^= (ONLCR
& lsbmask(tropts
.crinlf
));
360 return settermattr(fd
, &cntrl
);
364 lsbmask(unsigned int n
)
366 /* if n == 1, n fills with all 1s, 0 stays the same */
367 for (int i
= (sizeof(int) * CHAR_BIT
); i
> 0; i
--)
373 termchck(const struct TERMIOS_STRUCT
*term
)
375 for (size_t i
= 0; i
< sizeof(struct TERMIOS_STRUCT
); i
++)
376 if (((char*)term
)[i
] != 0)
383 writeport(void *unused
)
388 ts
.tv_sec
= swritedelay
;
389 ts
.tv_nsec
= nswritedelay
;
391 wts
.tv_nsec
= chardelay
;
392 msizes
.sizesm
= scratchwsz
;
393 msizes
.sizebm
= wbuffsz
;
395 writebuff
= malloc(wbuffsz
* sizeof(char));
396 scratchw
= malloc(scratchwsz
* sizeof(int));
398 if (writebuff
== NULL
)
399 die(1, "buffer allocation failed\n");
400 if (scratchw
== NULL
)
401 die(1, "scratch buffer allocation failed\n");
406 ssize_t inln
= read(STDIN_FILENO
, writebuff
, wbuffsz
- 1);
412 if (writebuff
[0] == escapechar
) {
419 if (bsargs
.find
!= bsargs
.input
)
420 replacechar(writebuff
, inln
, &bsargs
);
422 inln
= rmchar(writebuff
, inln
, scratchw
, &nocrargs
);
424 inln
= rmchar(writebuff
, inln
, scratchw
, &nolfargs
);
426 inln
= addchar(writebuff
, inln
, scratchw
, &msizes
, &lfincrargs
);
429 for (int i
= 0; i
<= inln
; i
++) {
430 rwc
-= write(fd
, &writebuff
[i
], 1);
431 nanosleep(&wts
, NULL
);
435 rwc
-= write(fd
, writebuff
, 1);
438 } else if (inln
== 0 && !interactive
) {
441 nanosleep(&ts
, NULL
);
451 readport(void *unused
)
456 ts
.tv_sec
= sreaddelay
;
457 ts
.tv_nsec
= nsreaddelay
;
458 msizes
.sizesm
= scratchrsz
;
459 msizes
.sizebm
= rbuffsz
;
461 readbuff
= malloc(rbuffsz
* sizeof(char));
462 scratchr
= malloc(scratchrsz
* sizeof(int));
464 if (readbuff
== NULL
)
465 die(1, "buffer allocation failed\n");
466 if (scratchr
== NULL
)
467 die(1, "scratch buffer allocation failed\n");
471 /* disable stdout buffering */
472 setvbuf(stdout
, NULL
, _IONBF
, 0);
475 ssize_t outln
= read(fd
, readbuff
, rbuffsz
- 1);
478 outln
= rmchar(readbuff
, outln
, scratchr
, &nocrargs
);
480 outln
= rmchar(readbuff
, outln
, scratchr
, &nolfargs
);
482 outln
= addchar(readbuff
, outln
, scratchr
, &msizes
, &crinlfargs
);
484 outln
= addchar(readbuff
, outln
, scratchr
, &msizes
, &lfincrargs
);
486 rwc
+= write(STDOUT_FILENO
, readbuff
, outln
);
488 nanosleep(&ts
, NULL
);
496 replacechar(char *buff
, ssize_t size
, const Args
*args
)
498 for (int i
= 0; i
< size
; i
++)
499 if (buff
[i
] == args
->find
)
500 buff
[i
] = args
->input
;
504 addchar(char *buff
, ssize_t size
, int *scratch
, const Sizes
*sizes
, const Args
*args
)
508 /* turns negative numbers into 1, positive into 0 */
509 to
= ((args
->offset
>> ((sizeof(int) * CHAR_BIT
) - 1)) & 1);
511 for (int i
= 0; i
< size
; i
++) {
512 if (buff
[i
] == args
->find
) {
516 if (c
>= sizes
->sizesm
)
521 if (size
== 1 && c
== 1) {
522 buff
[to
^ 1] = args
->input
;
523 buff
[to
] = args
->find
;
526 if ((size
+ c
) > sizes
->sizebm
)
527 c
= sizes
->sizebm
- size
;
529 for (int i
= c
- 1; i
>= 0; i
--) {
531 memmove(&buff
[t
] + 1, &buff
[t
], (size
+ c
) - t
);
532 buff
[t
+ (to
^ 1)] = args
->input
;
538 rmchar(char *buff
, ssize_t size
, int *scratch
, const Args
*args
)
541 for (int i
= 0; i
< size
; i
++) {
542 if (buff
[i
] == args
->find
) {
549 if (size
== 1 && c
== 1)
552 for (int i
= c
; i
>= 0; i
--) {
554 memmove(&buff
[t
], &buff
[t
] + 1, (size
+ c
) - t
);
563 if (gettermattr(STDIN_FILENO
, &origterm
) < 0 )
564 die(1, "failed to get terminal attributes\n");
569 newterm
.c_lflag
&= ~ECHO
;
571 newterm
.c_lflag
&= ~ICANON
;
574 newterm
.c_iflag
|= IXONXOFF
;
576 newterm
.c_iflag
|= INLCR
;
577 newterm
.c_cc
[VMIN
] = minchars
;
578 newterm
.c_cc
[VTIME
] = 0;
579 newterm
.c_cc
[VINTR
] = newterm
.c_cc
[VSUSP
]\
580 = newterm
.c_cc
[VQUIT
] = newterm
.c_cc
[VLNEXT
]\
581 = newterm
.c_cc
[VDISCARD
] = _POSIX_VDISABLE
;
584 newterm
.c_cc
[VDSUSP
] = _POSIX_VDISABLE
;
592 bsargs
.find
= origterm
.c_cc
[VERASE
];
594 if (settermattr(STDOUT_FILENO
, &newterm
) < 0)
595 die(1, "failed to set terminal attributes\n");
603 if ((!half
|| !canonical
) && interactive
) {
604 newterm
.c_lflag
|= ECHO
;
605 newterm
.c_lflag
|= ICANON
;
606 if (settermattr(STDIN_FILENO
, &newterm
) < 0)
607 fprintf(stderr
, "failed to enable echo and/or canonical mode\n");
620 msizes
.sizesm
= scratchwsz
;
621 msizes
.sizebm
= wbuffsz
;
623 if (isatty(STDIN_FILENO
) && isatty(STDOUT_FILENO
))
629 cmdchar
= writebuff
[0];
631 cmdchar
= writebuff
[1];
634 case EOT
: /* FALLTHROUGH */
646 newterm
.c_lflag
&= ~ECHO
;
648 newterm
.c_lflag
|= ECHO
;
650 if (settermattr(STDOUT_FILENO
, &newterm
) < 0)
651 die(1, "failed to set terminal attributes\n");
655 newterm
.c_lflag
&= ~ICANON
;
657 newterm
.c_lflag
|= ICANON
;
659 if (settermattr(STDOUT_FILENO
, &newterm
) < 0)
660 die(1, "failed to set terminal attributes\n");
664 if (fgets(ttr
, sizeof(ttr
), stdin
) != NULL
) {
666 int wfd
= open(ttr
, O_RDONLY
);
667 replacechar(ttr
, 63, &nolfargs
);
669 perror("error opening file");
672 while ((frln
= read(wfd
, writebuff
, wbuffsz
- 1)) > 0) {
674 for (int i
= 0; i
<= frln
; i
++) {
676 frln
= rmchar(writebuff
, frln
, scratchw
, &nocrargs
);
678 frln
= rmchar(writebuff
, frln
, scratchw
, &nolfargs
);
680 frln
= addchar(writebuff
, frln
, scratchw
, &msizes
, &lfincrargs
);
682 rwc
-= write(fd
, &writebuff
[i
], 1);
683 nanosleep(&wts
, NULL
);
690 if (fgets(ttr
, sizeof(ttr
), stdin
) != NULL
) {
691 replacechar(ttr
, 63, &nolfargs
);
692 tspd
= strtoui(ttr
, 0);
693 if (tspd
!= uintmax
) {
694 ospeed
= ispeed
= tspd
;
695 settermspd(ispeed
, ospeed
, &cntrl
);
696 if (settermattr(fd
, &cntrl
) != 0)
697 fprintf(stderr
, "failed to set baudrate "
698 "[RX:%u | TX:%u]\n", ispeed
, ospeed
);
700 fprintf(stderr
, "invalid speed: %s\n", ttr
);
706 if (fgets(ttr
, sizeof(ttr
), stdin
) != NULL
) {
707 replacechar(ttr
, 63, &nolfargs
);
708 chardelay
= strtoui(ttr
, 0);
709 if (chardelay
!= uintmax
) {
711 wts
.tv_nsec
= chardelay
;
713 fprintf(stderr
, "invalid delay: %s\n", ttr
);
719 fprintf(stderr
, "additional output translation option: ");
720 if (fgets(ttr
, sizeof(ttr
), stdin
) != NULL
) {
721 replacechar(ttr
, 63, &nolfargs
);
722 if(troptions(&tropts
, ttr
)) {
723 fprintf(stderr
, "invalid output translation option: %s\n", optarg
);
727 fprintf(stderr
, "could not set new options\n");
732 printf("additional input translation option: ");
733 if (fgets(ttr
, sizeof(ttr
), stdin
) != NULL
) {
734 replacechar(ttr
, 63, &nolfargs
);
735 if (troptions(&itropts
, ttr
)) {
736 fprintf(stderr
, "invalid input translation option: %s\n", optarg
);
740 fprintf(stderr
, "could not set new options\n");
743 if (!half
&& interactive
)
744 newterm
.c_lflag
&= ~ECHO
;
745 if (!canonical
&& interactive
)
746 newterm
.c_lflag
&= ~ICANON
;
747 if (settermattr(STDIN_FILENO
, &newterm
) == -1)
748 fprintf(stderr
, "failed to restore echo and/or canonical mode\n");
753 ts
.tv_sec
= spulsedelay
;
754 ts
.tv_nsec
= nspulsedelay
;
756 if (ioctl(fd
, TIOCMGET
, &st
) != 0) {
757 fprintf(stderr
, "failed to get port status\n");
761 if (ioctl(fd
, TIOCMSET
, &st
) != 0) {
762 fprintf(stderr
, "failed to set DTR [assertion]\n");
765 nanosleep(&ts
, NULL
);
768 if (ioctl(fd
, TIOCMSET
, &st
) != 0)
769 fprintf(stderr
, "failed to set DTR [negation]\n");
771 case BS
: /* FALLTHROUGH */
776 fprintf(stderr
, "not a valid command [%c]\n", cmdchar
);
785 die(128 + signo
, "\nrecieved signal [%d], exiting\n", signo
);
789 die(int code
, const char *msg
, ...)
795 nanosleep(&wts
, NULL
);
811 if (termchck(&newterm
))
812 settermattr(STDIN_FILENO
, &origterm
);
815 vfprintf(stderr
, msg
, fpa
);
822 main(int argc
, char *argv
[])
828 int oind
, rxspdset
, devset
, c
;
829 oind
= rxspdset
= devset
= 0;
831 for (int i
= 1; i
< argc
; i
++) {
832 if (argv
[i
][0] == '-' && argv
[i
][1] >= '0' && argv
[i
][1] <= '9') {
833 if (asprintf(&t
, "-s%s", argv
[i
] + 1) == -1) {
834 fprintf(stderr
, "%s: cannot convert -# to -s#\n", argv
[0]);
842 static struct option longopt
[] = {
843 {"line", required_argument
, NULL
, 'l'},
844 {"speed", required_argument
, NULL
, 's'},
845 {"rx-speed", required_argument
, NULL
, 'i'},
846 {"echo", no_argument
, NULL
, 'h'},
847 {"canonical", no_argument
, NULL
, 'c'},
848 {"odd", no_argument
, NULL
, 'o'},
849 {"even", no_argument
, NULL
, 'e'},
850 {"hardware-rts-cts", no_argument
, NULL
, 'R'},
851 {"hardware-dtr-dsr", no_argument
, NULL
, 'r'},
852 {"software", no_argument
, NULL
, 'X'},
853 {"data", required_argument
, NULL
, 'D'},
854 {"delay", required_argument
, NULL
, 'd'},
855 {"min-chars", required_argument
, NULL
, 'm'},
856 {"stop-bits", no_argument
, NULL
, 'S'},
857 {"backspace", no_argument
, NULL
, 'b'},
858 {"translation", required_argument
, NULL
, 't'},
859 {"input-translation", required_argument
, NULL
, 'T'},
860 {"verbose", no_argument
, NULL
, 'v'},
864 while ((c
= getopt_long(argc
, argv
, "D:T:d:i:l:m:s:t:RXbcehorv", longopt
, &oind
)) != -1) {
869 backspace
^= 1; break;
871 canonical
^= 1; break;
879 tl
= strtol(optarg
, &endptr
, 10);
880 if (errno
!= 0 || *endptr
!= '\0' || t
< 0) {
881 fprintf(stderr
, "%s: invalid character delay: %s\n", argv
[0],optarg
);
888 if (optarg
[1] != '\0' || !(optarg
[0] >= '5' && optarg
[0] <= '8')) {
889 fprintf(stderr
, "%s: invalid number of data bits: %s\n", argv
[0], optarg
);
901 case 'i': /* FALLTHROUGH */
903 tui
= strtoui(optarg
, 1);
904 if (tui
== uintmax
) {
905 fprintf(stderr
, "%s: invalid speed: %s\n", argv
[0], optarg
);
909 ispeed
= tui
; rxspdset
= 1;
912 ispeed
= rxspdset
? ispeed
: ospeed
;
919 fprintf(stderr
, "%s: cannot specify multiple devices\n", argv
[0]);
922 if (strlen(optarg
) > 58) {
923 fprintf(stderr
, "%s: device name too long\n", argv
[0]);
926 if (strchr(optarg
, '/')) {
927 strcpy(line
, optarg
);
930 sprintf(line
, "/dev/%s", optarg
);
935 tui
= strtoui(optarg
, 1);
936 if (tui
== uintmax
) {
937 fprintf(stderr
, "%s: invalid number of characters: %s\n", argv
[0], optarg
);
943 if (troptions(&tropts
, optarg
)) {
944 fprintf(stderr
, "%s: invalid output translation option: %s\n", argv
[0], optarg
);
949 if (troptions(&itropts
, optarg
)) {
950 fprintf(stderr
, "%s: invalid input translation option: %s\n", argv
[0], optarg
);
958 die(2, "usage: %s [--line|-l line] [--speed|-s #|-#] [--rx-speed|-i #]\n"
959 " [--data-bits|-D #] [--stop-bits|-S]"
960 " [--even|-e] [--odd|-o]\n"
961 " [--hardware-rts-cts|-R]"
962 " [--hardware-dtr-dsr|-r] [--software|-X]\n"
963 " [--delay|-d #] [--min-chars|-m #]"
964 " [--canonical|-c] [--echo|-h]\n"
965 " [--translation|-t option]"
966 " [--input-translation|-T option]\n"
967 " [--verbose|-v] [--backspace|-b]\n",
973 * not the best practice, but in order for this `free()`
974 * to not get executed ust needs to be killed,
975 * which means the OS *should* free the memory
982 if (isatty(STDIN_FILENO
) && isatty(STDOUT_FILENO
))
985 signal(SIGHUP
, sighandl
);
986 signal(SIGINT
, sighandl
);
987 signal(SIGQUIT
, sighandl
);
988 signal(SIGTERM
, sighandl
);
992 pthread_t readthread
, writethread
;
993 pthread_create(&writethread
, NULL
, writeport
, NULL
);
994 pthread_create(&readthread
, NULL
, readport
, NULL
);
995 pthread_join(writethread
, NULL
);
996 pthread_join(readthread
, NULL
);