Add sockets support & dump to file

This commit is contained in:
Edward Emelianov 2022-02-01 21:53:31 +03:00
parent c03d11e7b6
commit c8028d6156
9 changed files with 506 additions and 79 deletions

View File

@ -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)

View File

@ -20,8 +20,9 @@
#include <assert.h> // assert
#include <stdio.h> // printf
#include <string.h> // memcpy
#include <usefull_macros.h>
#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
};

View File

@ -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;

41
dbg.h Normal file
View File

@ -0,0 +1,41 @@
/*
* This file is part of the ttyterm project.
* Copyright 2022 Edward V. Emelianov <edward.emelianoff@gmail.com>.
*
* 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 <http://www.gnu.org/licenses/>.
*/
#pragma once
#ifndef DBG_H__
#define DBG_H__
#include <usefull_macros.h>
#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__

82
main.c
View File

@ -19,43 +19,31 @@
#include <signal.h>
#include <stdio.h>
#include <string.h> // strcmp
#include <usefull_macros.h>
#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);
}
}

View File

@ -31,6 +31,8 @@
#include <stdlib.h>
#include <string.h>
#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);

View File

@ -19,22 +19,14 @@
#ifndef NCURSES_AND_READLINE_H__
#define NCURSES_AND_READLINE_H__
#include <pthread.h>
#include <usefull_macros.h>
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__

318
ttysocket.c Normal file
View File

@ -0,0 +1,318 @@
/*
* This file is part of the ttyterm project.
* Copyright 2022 Edward V. Emelianov <edward.emelianoff@gmail.com>.
*
* 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 <http://www.gnu.org/licenses/>.
*/
#include <arpa/inet.h>
#include <netdb.h>
#include <stdio.h> // getchar
#include <string.h>
#include <sys/select.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/un.h> // 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);
}

50
ttysocket.h Normal file
View File

@ -0,0 +1,50 @@
/*
* This file is part of the ttyterm project.
* Copyright 2022 Edward V. Emelianov <edward.emelianoff@gmail.com>.
*
* 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 <http://www.gnu.org/licenses/>.
*/
#pragma once
#ifndef TTY_H__
#define TTY_H__
#include <pthread.h>
#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__