diff --git a/CMakeLists.txt b/CMakeLists.txt index d88565a..b7ddeb8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,19 +1,18 @@ cmake_minimum_required(VERSION 3.0) set(PROJ tty_term) -set(MINOR_VERSION "1") -set(MID_VERSION "0") +set(MINOR_VERSION "0") +set(MID_VERSION "1") set(MAJOR_VERSION "0") set(VERSION "${MAJOR_VERSION}.${MID_VERSION}.${MINOR_VERSION}") -project(${PROJ} VERSION ${PROJ_VERSION} LANGUAGES C) -#enable_language(C) +project(${PROJ} VERSION ${VERSION} LANGUAGES C) message("VER: ${VERSION}") # default flags set(CMAKE_C_FLAGS_RELEASE "") set(CMAKE_C_FLAGS_DEBUG "") -set(CMAKE_C_FLAGS "-O2 -std=gnu99") +set(CMAKE_C_FLAGS "-O3 -std=gnu99") set(CMAKE_COLOR_MAKEFILE ON) diff --git a/cmdlnopts.c b/cmdlnopts.c index 9e13a09..770dbbd 100644 --- a/cmdlnopts.c +++ b/cmdlnopts.c @@ -20,8 +20,9 @@ #include // assert #include // printf #include // memcpy -#include + #include "cmdlnopts.h" +#include "dbg.h" /* * here are global parameters initialisation @@ -33,7 +34,6 @@ static glob_pars G; // default global parameters glob_pars const Gdefault = { .speed = 9600, - .ttyname = "/dev/ttyUSB0", .eol = "n", .tmoutms = 100, }; @@ -46,9 +46,12 @@ static myoption cmdlnopts[] = { // set 1 to param despite of its repeating number: {"help", NO_ARGS, NULL, 'h', arg_int, APTR(&help), _("show this help")}, {"speed", NEED_ARG, NULL, 's', arg_int, APTR(&G.speed), _("baudrate (default: 9600)")}, - {"devname", NEED_ARG, NULL, 'd', arg_string, APTR(&G.ttyname), _("serial device name")}, + {"name", NEED_ARG, NULL, 'n', arg_string, APTR(&G.ttyname), _("serial device path or server name/IP")}, {"eol", NEED_ARG, NULL, 'e', arg_string, APTR(&G.eol), _("end of line: n (default), r, nr or rn")}, {"timeout", NEED_ARG, NULL, 't', arg_int, APTR(&G.tmoutms), _("timeout for select() in ms (default: 100)")}, + {"port", NEED_ARG, NULL, 'p', arg_string, APTR(&G.port), _("socket port (none for UNIX)")}, + {"socket", NO_ARGS, NULL, 'S', arg_int, APTR(&G.socket), _("open socket")}, + {"dumpfile",NEED_ARG, NULL, 'd', arg_string, APTR(&G.dumpfile), _("dump data to this file")}, end_option }; diff --git a/cmdlnopts.h b/cmdlnopts.h index ee617fa..83b60c0 100644 --- a/cmdlnopts.h +++ b/cmdlnopts.h @@ -26,8 +26,11 @@ typedef struct{ int speed; // baudrate int tmoutms; // timeout for select() in ms + int socket; // open socket + char *dumpfile; // file to save dump char *ttyname; // device name char *eol; // end of line: \r (CR), \rn (CR+LF) or \n (LF): "r", "rn", "n" + char *port; // socket port } glob_pars; diff --git a/dbg.h b/dbg.h new file mode 100644 index 0000000..5df7693 --- /dev/null +++ b/dbg.h @@ -0,0 +1,41 @@ +/* + * This file is part of the ttyterm project. + * Copyright 2022 Edward V. Emelianov . + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once +#ifndef DBG_H__ +#define DBG_H__ + +#include + +#ifdef EBUG +#undef DBG +#undef FNAME +#undef ERR +#undef ERRX +//#undef WARN +#undef WARNX +#define FNAME() do{LOGDBG("%s (%s, line %d)", __func__, __FILE__, __LINE__);}while(0) +#define DBG(...) do{LOGDBG("%s (%s, line %d):", __func__, __FILE__, __LINE__); \ + LOGDBGADD(__VA_ARGS__);} while(0) +#define ERR(...) do{LOGERR(__VA_ARGS__); signals(9);}while(0) +#define ERRX(...) do{LOGERR(__VA_ARGS__); signals(9);}while(0) +//#define WARN(...) do{LOGWARN(__VA_ARGS__);}while(0) +#define WARNX(...) do{LOGWARN(__VA_ARGS__);}while(0) +#endif + +#endif // DBG_H__ diff --git a/main.c b/main.c index 8fb4529..e0c2575 100644 --- a/main.c +++ b/main.c @@ -19,43 +19,31 @@ #include #include #include // strcmp -#include #include "cmdlnopts.h" #include "ncurses_and_readline.h" -#include "tty.h" +#include "ttysocket.h" -#define BUFLEN 4096 +#include "dbg.h" -static ttyd dtty = {.dev = NULL, .mutex = PTHREAD_MUTEX_INITIALIZER}; - -//FILE *fd; +static chardevice conndev = {.dev = NULL, .mutex = PTHREAD_MUTEX_INITIALIZER, .name = NULL, .type = DEV_TTY}; void signals(int signo){ signal(signo, SIG_IGN); - if(dtty.dev){ - pthread_mutex_unlock(&dtty.mutex); - pthread_mutex_trylock(&dtty.mutex); - close_tty(&dtty.dev); - } - //fprintf(fd, "stop\n"); - //fflush(fd); + closedev(&conndev); deinit_ncurses(); deinit_readline(); + DBG("Exit"); exit(signo); } int main(int argc, char **argv){ glob_pars *G = NULL; // default parameters see in cmdlnopts.c initial_setup(); +#ifdef EBUG + OPENLOG("debug.log", LOGLEVEL_ANY, 1); +#endif G = parse_args(argc, argv); if(G->tmoutms < 0) ERRX("Timeout should be >= 0"); - dtty.dev = new_tty(G->ttyname, G->speed, BUFLEN); - if(!dtty.dev || !(dtty.dev = tty_open(dtty.dev, 1))){ - WARN("Can't open device %s", G->ttyname); - signals(1); - } - //fd = fopen("loglog", "w"); - //fprintf(fd, "start\n"); const char *EOL = "\n", *seol = "\\n"; if(strcasecmp(G->eol, "n")){ if(strcasecmp(G->eol, "r") == 0){ EOL = "\r"; seol = "\\r"; } @@ -63,10 +51,27 @@ int main(int argc, char **argv){ else if(strcasecmp(G->eol, "nr") == 0){ EOL = "\n\r"; seol = "\\n\\r"; } else ERRX("End of line should be \"r\", \"n\" or \"rn\" or \"nr\""); } - strcpy(dtty.eol, EOL); - strcpy(dtty.seol, seol); + strcpy(conndev.eol, EOL); + strcpy(conndev.seol, seol); int eollen = strlen(EOL); - dtty.eollen = eollen; + conndev.eollen = eollen; + DBG("eol: %s, seol: %s", conndev.eol, conndev.seol); + if(!G->ttyname){ + WARNX("You should point name"); + signals(0); + } + conndev.name = strdup(G->ttyname); + conndev.speed = G->speed; + if(G->socket){ + if(!G->port) conndev.type = DEV_UNIXSOCKET; + else{ + conndev.type = DEV_NETSOCKET; + conndev.port = strdup(G->port); + } + } + if(!opendev(&conndev, G->dumpfile)){ + signals(0); + } init_ncurses(); init_readline(); signal(SIGTERM, signals); // kill (-15) - quit @@ -75,42 +80,29 @@ int main(int argc, char **argv){ signal(SIGQUIT, signals); // ctrl+\ - quit signal(SIGTSTP, SIG_IGN); // ignore ctrl+Z pthread_t writer; - if(pthread_create(&writer, NULL, cmdline, (void*)&dtty)) ERR("pthread_create()"); + if(pthread_create(&writer, NULL, cmdline, (void*)&conndev)) ERR("pthread_create()"); settimeout(G->tmoutms); while(1){ - if(0 == pthread_mutex_lock(&dtty.mutex)){ - int l = Read_tty(dtty.dev); - if(l > 0){ - char *buf = dtty.dev->buf; + if(0 == pthread_mutex_lock(&conndev.mutex)){ + int l; + char *buf = ReadData(&conndev, &l); + if(buf && l > 0){ char *eol = NULL, *estr = buf + l; do{ - /*eol = strchr(buf, '\n'); - if(eol){ - *eol = 0; - add_ttydata(buf); - buf = eol + 1; - }else{ - add_ttydata(buf); - }*/ eol = strstr(buf, EOL); if(eol){ *eol = 0; - add_ttydata(buf); + ShowData(buf); buf = eol + eollen; }else{ - /* char *ptr = buf; - while(*ptr){ - if(*ptr == '\n' || *ptr == '\r'){ *ptr = 0; break;} - ++ptr; - }*/ - add_ttydata(buf); + ShowData(buf); } }while(eol && buf < estr); }else if(l < 0){ - pthread_mutex_unlock(&dtty.mutex); + pthread_mutex_unlock(&conndev.mutex); ERRX("Device disconnected"); } - pthread_mutex_unlock(&dtty.mutex); + pthread_mutex_unlock(&conndev.mutex); usleep(1000); } } diff --git a/ncurses_and_readline.c b/ncurses_and_readline.c index 5615619..11ccc3b 100644 --- a/ncurses_and_readline.c +++ b/ncurses_and_readline.c @@ -31,6 +31,8 @@ #include #include +#include "dbg.h" +#include "ttysocket.h" #include "ncurses_and_readline.h" // Keeps track of the terminal mode so we can reset the terminal if needed on errors @@ -39,7 +41,7 @@ static bool visual_mode = false; static bool insert_mode = true; static bool should_exit = false; -static ttyd *dtty = NULL; +static chardevice *dtty = NULL; static void fail_exit(const char *msg){ // Make sure endwin() is only called in visual mode. As a note, calling it @@ -136,14 +138,41 @@ static void readline_redisplay(){ static void show_mode(bool for_resize){ wclear(sep_win); - if(insert_mode) wprintw(sep_win, "INSERT (TAB to switch, ctrl+D to quit) ENDLINE: %s SPEED: %d", dtty?dtty->seol:"n", dtty?dtty->dev->speed:"NC"); - else wprintw(sep_win, "SCROLL (TAB to switch, q to quit) ENDLINE: %s SPEED: %d", dtty?dtty->seol:"n", dtty?dtty->dev->speed:"NC"); + char buf[128]; + if(insert_mode){ + if(dtty){ + switch(dtty->type){ + case DEV_NETSOCKET: + snprintf(buf, 127, "INSERT (TAB to switch, ctrl+D to quit) HOST: %s, ENDLINE: %s, PORT: %s", + dtty->name, dtty->seol, dtty->port); + break; + case DEV_UNIXSOCKET: + snprintf(buf, 127, "INSERT (TAB to switch, ctrl+D to quit) HOST: %s, ENDLINE: %s, PATH: %s", + dtty->name, dtty->seol, dtty->port); + break; + case DEV_TTY: + snprintf(buf, 127, "INSERT (TAB to switch, ctrl+D to quit) DEV: %s, ENDLINE: %s, SPEED: %d", + dtty->name, dtty->seol, dtty->speed); + break; + default: + break; + }}else{ + snprintf(buf, 127, "INSERT (TAB to switch, ctrl+D to quit) NOT INITIALIZED"); + } + }else{ + snprintf(buf, 127, "SCROLL (TAB to switch, q to quit) ENDLINE: %s", dtty?dtty->seol:"n"); + } + wprintw(sep_win, "%s", buf); if(for_resize) wnoutrefresh(sep_win); else wrefresh(sep_win); cmd_win_redisplay(for_resize); } -void add_ttydata(const char *text){ +/** + * @brief ShowData - show string on display + * @param text - text string + */ +void ShowData(const char *text){ if(!text) return; if(!*text) text = " "; // empty string Line *lp = malloc(sizeof(Line)); @@ -213,8 +242,9 @@ void init_ncurses(){ if(has_colors()){ init_pair(1, COLOR_WHITE, COLOR_BLUE); wbkgd(sep_win, COLOR_PAIR(1)); - }else + }else{ wbkgd(sep_win, A_STANDOUT); + } show_mode(false); mousemask(BUTTON4_PRESSED|BUTTON5_PRESSED, NULL); } @@ -228,21 +258,15 @@ void deinit_ncurses(){ } static void got_command(char *line){ - bool err = false; if(!line) // Ctrl-D pressed on empty line should_exit = true; else{ if(!*line) return; // zero length add_history(line); - if(dtty && dtty->dev){ - if(0 == pthread_mutex_lock(&dtty->mutex)){ - if(write_tty(dtty->dev->comfd, line, strlen(line))) err = true; - else if(write_tty(dtty->dev->comfd, dtty->eol, dtty->eollen)) err = true; - pthread_mutex_unlock(&dtty->mutex); - if(err) ERRX("Device disconnected"); - } + if(SendData(dtty, line) == -1){ + ERRX("Device disconnected"); } - free(line); + FREE(line); } } @@ -276,9 +300,14 @@ static void rollup(){ } } +/** + * @brief cmdline - console reading process; runs as separate thread + * @param arg - tty/socket device to write strings entered by user + * @return NULL + */ void *cmdline(void* arg){ MEVENT event; - dtty = (ttyd*)arg; + dtty = (chardevice*)arg; show_mode(false); do{ int c = wgetch(cmd_win); diff --git a/ncurses_and_readline.h b/ncurses_and_readline.h index bb8d60a..63a8088 100644 --- a/ncurses_and_readline.h +++ b/ncurses_and_readline.h @@ -19,22 +19,14 @@ #ifndef NCURSES_AND_READLINE_H__ #define NCURSES_AND_READLINE_H__ -#include -#include - -typedef struct{ - TTY_descr *dev; - pthread_mutex_t mutex; - char eol[3]; - char seol[5]; - int eollen; -} ttyd; +#include "dbg.h" +#include "ttysocket.h" void init_readline(); void deinit_readline(); void init_ncurses(); void deinit_ncurses(); void *cmdline(void* arg); -void add_ttydata(const char *text); +void ShowData(const char *text); #endif // NCURSES_AND_READLINE_H__ diff --git a/ttysocket.c b/ttysocket.c new file mode 100644 index 0000000..451c060 --- /dev/null +++ b/ttysocket.c @@ -0,0 +1,318 @@ +/* + * This file is part of the ttyterm project. + * Copyright 2022 Edward V. Emelianov . + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include +#include // getchar +#include +#include +#include +#include +#include // unix socket + +#include "dbg.h" +#include "ttysocket.h" + +static int sec = 0, usec = 100; // timeout +static FILE *dupfile = NULL; // file for output +// TODO: if unix socket name starts with \0 translate it as \\0 to d->name! + +// set Read_tty timeout in milliseconds +void settimeout(int tmout){ + sec = 0; + if(tmout > 999){ + sec = tmout / 1000; + tmout -= sec * 1000; + } + usec = tmout * 1000L; +} + +/** + * wait for answer from socket + * @param sock - socket fd + * @return 0 in case of timeout, 1 in case of socket ready, -1 if error + */ +static int waittoread(int fd){ + fd_set fds; + struct timeval timeout; + timeout.tv_sec = sec; + timeout.tv_usec = usec; + FD_ZERO(&fds); + FD_SET(fd, &fds); + do{ + int rc = select(fd+1, &fds, NULL, NULL, &timeout); + if(rc < 0){ + if(errno != EINTR){ + WARN("select()"); + return -1; + } + continue; + } + break; + }while(1); + if(FD_ISSET(fd, &fds)){ + DBG("FD_ISSET"); + return 1; + } + return 0; +} + +// get data drom TTY +static char *getttydata(TTY_descr *D, int *len){ + if(!D || D->comfd < 0) return NULL; + size_t L = 0; + size_t length = D->bufsz; + char *ptr = D->buf; + int s = 0; + do{ + if(!(s = waittoread(D->comfd))) break; + if(s < 0){ + if(len) *len = 0; + return NULL; + } + ssize_t l = read(D->comfd, ptr, length); + if(l < 1){ // disconnected + if(len) *len = -1; + return NULL; + } + ptr += l; L += l; + length -= l; + }while(length); + D->buflen = L; + D->buf[L] = 0; + if(len) *len = L; + return D->buf; +} + +static char *getsockdata(TTY_descr *D, int *len){ + if(!D || D->comfd < 0) return NULL; + char *ptr = NULL; + int n = waittoread(D->comfd); + if(n == 1){ + n = read(D->comfd, D->buf, D->bufsz-1); + if(n > 0){ + ptr = D->buf; + ptr[n] = 0; + DBG("got %d: %s", n, ptr); + }else{ + DBG("Got nothing"); + n = -1; + } + } + if(len) *len = n; + return ptr; +} + +/** + * @brief ReadData - get data from serial device or socket + * @param d - device + * @param len (o) - length of data read (-1 if device disconnected) + * @return NULL or string + */ +char *ReadData(chardevice *d, int *len){ + if(!d || !d->dev) return NULL; + if(len) *len = -1; + char *r = NULL; + switch(d->type){ + case DEV_TTY: + if(!d || !d->dev) return NULL; + r = getttydata(d->dev, len); + break; + case DEV_NETSOCKET: + case DEV_UNIXSOCKET: + r = getsockdata(d->dev, len); + break; + default: + break; + } + if(r && dupfile){ + fprintf(dupfile, "< %s", r); + } + return r; +} + +/** + * @brief SendData - send data to tty or socket + * @param d - device + * @param str - text string + * @return 0 if error, -1 if disconnected + */ +int SendData(chardevice *d, char *str){ + char buf[BUFSIZ]; + if(!d) return -1; + DBG("send %s", str); + if(!str) return 0; + int ret = 0; + if(0 == pthread_mutex_lock(&d->mutex)){ + int l = strlen(str), lplus = l + d->eollen + 1; + if(l < 1) return 0; + if(lplus > BUFSIZ-1) lplus = BUFSIZ-1; + snprintf(buf, lplus+1, "%s%s", str, d->eol); + switch(d->type){ + case DEV_TTY: + if(write_tty(d->dev->comfd, buf, lplus)) ret = 0; + else ret = l; + break; + case DEV_NETSOCKET: + case DEV_UNIXSOCKET: + if(lplus != send(d->dev->comfd, buf, lplus, 0)) ret = 0; + else ret = l; + pthread_mutex_unlock(&d->mutex); + break; + default: + str = NULL; + break; + } + if(str && dupfile){ + fprintf(dupfile, "> %s", buf); + } + pthread_mutex_unlock(&d->mutex); + }else ret = -1; + DBG("ret=%d", ret); + return ret; +} + +static const int socktypes[] = {SOCK_STREAM, SOCK_RAW, SOCK_RDM, SOCK_SEQPACKET, SOCK_DCCP, SOCK_PACKET, SOCK_DGRAM, 0}; + +static TTY_descr* opensocket(chardevice *d){ + if(!d) return FALSE; + TTY_descr *descr = MALLOC(TTY_descr, 1); // only for `buf` and bufsz/buflen + descr->buf = MALLOC(char, BUFSIZ); + descr->bufsz = BUFSIZ; + // now try to open a socket + descr->comfd = -1; + struct hostent *host; + struct sockaddr_in addr = {0}; + struct sockaddr_un saddr = {0}; + struct sockaddr *sa = NULL; + socklen_t addrlen = 0; + int domain = -1; + if(d->type == DEV_NETSOCKET){ + DBG("NETSOCK to %s", d->name); + sa = (struct sockaddr*) &addr; + addrlen = sizeof(addr); + if((host = gethostbyname(d->name)) == NULL ){ + WARN("gethostbyname()"); + FREE(descr->buf); + FREE(descr); + return NULL; + } + struct in_addr *ia = (struct in_addr*)host->h_addr_list[0]; + DBG("addr: %s", inet_ntoa(*ia)); + addr.sin_family = AF_INET; + int p = atoi(d->port); DBG("PORT: %s - %d", d->port, p); + addr.sin_port = htons(p); + //addr.sin_addr.s_addr = *(long*)(host->h_addr); + addr.sin_addr.s_addr = ia->s_addr; + domain = AF_INET; + }else{ + DBG("UNSOCK"); + sa = (struct sockaddr*) &saddr; + addrlen = sizeof(saddr); + saddr.sun_family = AF_UNIX; + strncpy(saddr.sun_path, d->name, 107); // if sun_path[0] == 0 then don't create a file + domain = AF_UNIX; + } + const int *type = socktypes; + while(*type){ + DBG("type = %d", *type); + if((descr->comfd = socket(domain, *type, 0)) > -1){ + if(connect(descr->comfd, sa, addrlen) < 0){ + DBG("CANT connect"); + close(descr->comfd); + }else break; + } + WARNX("socket()"); + ++type; + } + if(descr->comfd < 0){ + DBG("NO types"); + WARNX("No types can be choosen"); + FREE(descr->buf); + FREE(descr); + return NULL; + } + return descr; +} + +/** + * @brief opendev - open TTY or socket output device + * @param d - device type + * @return FALSE if failed + */ +int opendev(chardevice *d, char *path){ + if(!d) return FALSE; + DBG("Try to open device"); + switch(d->type){ + case DEV_TTY: + DBG("Serial"); + d->dev = new_tty(d->name, d->speed, BUFSIZ); + if(!d->dev || !(d->dev = tty_open(d->dev, 1))){ + WARN("Can't open device %s", d->name); + DBG("CANT OPEN"); + return FALSE; + } + break; + case DEV_NETSOCKET: + case DEV_UNIXSOCKET: + d->dev = opensocket(d); + if(!d->dev){ + WARNX("Can't open socket"); + DBG("CANT OPEN"); + return FALSE; + } + break; + default: + return FALSE; + } + if(path){ // open logging file + dupfile = fopen(path, "a"); + if(!dupfile){ + WARN("Can't open %s", path); + closedev(d); + return FALSE; + } + } + return TRUE; +} + +void closedev(chardevice *d){ + if(!d) return; + pthread_mutex_unlock(&d->mutex); + pthread_mutex_trylock(&d->mutex); + if(dupfile){ + fclose(dupfile); + dupfile = NULL; + } + switch(d->type){ + case DEV_TTY: + if(d->dev){ + close_tty(&d->dev); + } + break; + case DEV_NETSOCKET: + if(d->dev){ + close(d->dev->comfd); + FREE(d->dev); + } + break; + default: + return; + } + FREE(d->name); +} diff --git a/ttysocket.h b/ttysocket.h new file mode 100644 index 0000000..317759f --- /dev/null +++ b/ttysocket.h @@ -0,0 +1,50 @@ +/* + * This file is part of the ttyterm project. + * Copyright 2022 Edward V. Emelianov . + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once +#ifndef TTY_H__ +#define TTY_H__ + +#include +#include "dbg.h" + +typedef enum{ + DEV_TTY, + DEV_NETSOCKET, + DEV_UNIXSOCKET, +} devtype; + +typedef struct{ + devtype type; // type + char *name; // filename (dev or UNIX socket) or server name/IP + TTY_descr *dev; // tty serial device + char *port; // port to connect + int speed; // tty speed + pthread_mutex_t mutex; // reading/writing mutex + char eol[3]; // end of line + char seol[5]; // `eol` with doubled backslash (for print @ screen) + int eollen; // length of `eol` +} chardevice; + +char *ReadData(chardevice *d, int *l); +int SendData(chardevice *d, char *str); +void settimeout(int tms); +int opendev(chardevice *d, char *path); +void closedev(chardevice *d); + +#endif // TTY_H__