17 Commits

Author SHA1 Message Date
Kim Woelders 1081fd9235 ttynvt version 0.17 2025-06-25 17:32:41 +02:00
Frank Rolsted Jensen 72e09527b9 Handling of pthread_mutex_init failure fixed
pthread_mutex_init() returns errno on failure
2025-06-04 12:02:39 +02:00
Frank Rolsted Jensen 10d5c9cf3b Handling of initial protocol error changed
The client accepts a long(er) response time when waiting for server to
enable RFC2217 (telnet option).

Bug in hangling of rx timeout fixed
2025-06-04 12:02:34 +02:00
Kim Woelders 4d3f7d384b ttynvt: Move optional delay during close
Move to after closing the fds.

As suggested in https://gitlab.com/lars-thrane-as/ttynvt/-/issues/11.
2025-02-25 20:03:05 +01:00
Kim Woelders 1f516288ab test: Initial testing framework
Configure with --enable-testtools.

BLD_ROOT is now the top build directory.

Current test requires ttynvt to be started (by root) like
 # BLD_ROOT/src/ttynvt -d -E -S localhost:1234 -n ttyNVT99
Device and server name parameters must be as shown above.

Run tests like
 $ make -C BLD_ROOT test 	# Builds the other programs too
 $ make -C BLD_ROOT/test	# Only running tests
 $ make -C BLD_ROOT/test V=1 RUN_OPTS="--gtest_filter=Test1.t1"
2024-11-10 15:01:12 +01:00
Kim Woelders 55c5d0d226 nvtsrv: Change some log messages to debug 2024-11-10 13:34:38 +01:00
Kim Woelders cd5de2fe5d nvttest: Return error if anything fails 2024-11-10 13:31:30 +01:00
Kim Woelders da027d4de1 nvttest: Rework thread handling 2024-11-10 13:31:30 +01:00
Kim Woelders 3bcbf9c8c1 nvttest: Enable specifying device
Also check that the device file exists.
2024-11-10 11:30:16 +01:00
Kim Woelders 611ab3cbf3 nvttest: Improve help
And do help stuff like in nvtsrv.
2024-11-10 11:29:22 +01:00
Kim Woelders 6e2e530215 nvtsrv: Fix inconsistent help 2024-11-10 11:20:52 +01:00
Kim Woelders 3d5848aff0 ttynvt: On TCSET* skip rfc2217 baud rate change if zero
https://gitlab.com/lars-thrane-as/ttynvt/-/merge_requests/15
2024-10-26 10:52:40 +02:00
Nebojsa Sabovic 4223a4b92a Support odd parity. 2024-10-24 19:02:08 +02:00
Kim Woelders 2c090f3721 Re-indent everything with indent-2.2.13 2024-03-06 17:21:48 +01:00
Kim Woelders 797ccf08c8 socket: Indent according to profile 2024-03-06 17:18:46 +01:00
s-wakaba@gitlab.com 9739087594 support TCP/IPv6 address and UNIX domain socket path as rfc2217 server 2024-03-06 17:13:28 +01:00
Kim Woelders f41067c3be nvttest: Add missing -lpthread 2024-03-05 11:20:09 +01:00
16 changed files with 647 additions and 228 deletions
+8
View File
@@ -9,3 +9,11 @@ if BUILD_TESTTOOLS
SUBDIRS += nvtsrv
SUBDIRS += nvttest
endif
if BUILD_TEST
SUBDIRS += test
endif
.PHONY: $(SUBDIRS) test
$(SUBDIRS) test:
$(MAKE) -C $@
test: $(SUBDIRS)
+5 -2
View File
@@ -1,4 +1,4 @@
AC_INIT([ttynvt],[0.16],[frj@thrane.eu])
AC_INIT([ttynvt],[0.17],[frj@thrane.eu])
AM_INIT_AUTOMAKE([foreign dist-xz])
m4_ifdef([AM_SILENT_RULES], [AM_SILENT_RULES([yes])])
@@ -6,10 +6,10 @@ m4_ifdef([AM_SILENT_RULES], [AM_SILENT_RULES([yes])])
AC_USE_SYSTEM_EXTENSIONS
AC_PROG_CC
AC_PROG_CXX
AC_PROG_INSTALL
define([AC_LIBTOOL_LANG_CXX_CONFIG], [:])dnl
define([AC_LIBTOOL_LANG_F77_CONFIG], [:])dnl
define([AC_LIBTOOL_LANG_GCJ_CONFIG], [:])dnl
@@ -31,6 +31,8 @@ AC_ARG_ENABLE(testtools,
enable_testtools=no)
AM_CONDITIONAL([BUILD_TESTTOOLS], [test "x$enable_testtools" = "xyes"])
AM_CONDITIONAL(BUILD_TEST, false)
CFLAGS_WARN="$CFLAGS_WARN -Wall -Wextra -Werror -Wno-unused-parameter"
CFLAGS_WARN="$CFLAGS_WARN -Wmissing-prototypes -Wmissing-declarations -Wstrict-prototypes"
CFLAGS_WARN="$CFLAGS_WARN -Waggregate-return -Wpointer-arith -Wshadow -Wwrite-strings"
@@ -44,6 +46,7 @@ lib/Makefile
src/Makefile
nvtsrv/Makefile
nvttest/Makefile
test/Makefile
])
AC_OUTPUT
+82 -14
View File
@@ -7,6 +7,7 @@
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/un.h>
#include "nvt_log.h"
#include "nvt_socket.h"
@@ -18,42 +19,109 @@ typedef struct {
union {
struct sockaddr gen;
struct sockaddr_in ipv4;
struct sockaddr_in6 ipv6;
struct sockaddr_un un;
};
} sa_t;
static int _sa_host_lookup(sa_t * sa, const char *host)
static int _sa_host_lookup(sa_t *sa, const char *host, const char *port)
{
struct hostent *server;
struct addrinfo hint, *adrinf = NULL, *p;
int ret = -1;
server = gethostbyname(host);
if (server == NULL)
memset(&hint, 0, sizeof(struct addrinfo));
if (getaddrinfo(host, port, &hint, &adrinf) != 0)
{
nvt_log(LOG_ERR, "Cannot resolve host name: %m\n");
return -1;
}
// Priority is given to the use of IPv4
// This code must be modified if the preffered protocol is changed or varied.
for (p = adrinf; p; p = p->ai_next)
{
if (p->ai_family == AF_INET)
{
sa->slen = sizeof(sa->ipv4);
sa->ipv4.sin_family = AF_INET;
memcpy(&sa->ipv4.sin_addr.s_addr, server->h_addr, server->h_length);
return 0;
memcpy(&sa->ipv4, p->ai_addr, sizeof(struct sockaddr_in));
ret = 0;
goto exit_host_lookup;
}
}
for (p = adrinf; p; p = p->ai_next)
{
if (p->ai_family == AF_INET6)
{
sa->slen = sizeof(sa->ipv6);
memcpy(&sa->ipv6, p->ai_addr, sizeof(struct sockaddr_in6));
ret = 0;
goto exit_host_lookup;
}
}
exit_host_lookup:
freeaddrinfo(adrinf);
return ret;
}
static int _sa_addr_parse(sa_t *sa, const char *addr)
{
char host[64], port[64];
ssize_t host_len;
char host[64];
const char *port;
memset(sa, 0, sizeof(sa_t));
sscanf(addr, "%63[^:]:%63s", host, port);
if (strncmp(addr, "unix:", 5) == 0)
{ // case of unix domain sockets ...
addr += 5; // skip "unix:" prefix
if (strlen(addr) > sizeof(sa->un.sun_path) - 1)
{
nvt_log(LOG_ERR, "UNIX domain socket path is too long.\n");
return -1;
}
sa->slen = sizeof(sa->un);
sa->un.sun_family = AF_UNIX;
strcpy(sa->un.sun_path, addr);
sa->type = SOCK_STREAM;
return 0;
}
if (_sa_host_lookup(sa, host) != 0)
if (!(port = strrchr(addr, ':')))
{
nvt_log(LOG_ERR, "bad server address format.\n");
return -1;
}
host_len = (port++) - addr;
// *port indicates the last ':', its next is 1st char of the port number
if (host_len >= 2 && addr[0] == '[' && addr[host_len - 1] == ']')
{
// IPv6 addresses are enclosed in pair of square brackets.
addr += 1;
host_len -= 2;
}
else
{
if (strchr(addr, ':') != port - 1)
{
nvt_log(LOG_ERR,
"addresses containing colons must be enclosed in square brackets.\n");
return -1;
}
}
if ((ssize_t) sizeof(host) <= host_len)
{
nvt_log(LOG_ERR, "host name is too long.\n");
return -1;
}
strncpy(host, addr, host_len);
host[host_len] = '\0'; // strncpy doesn't add null char
if (_sa_host_lookup(sa, host, port) != 0)
return -1;
sa->type = SOCK_STREAM;
sa->ipv4.sin_port = htons(atoi(port));
return 0;
}
@@ -103,7 +171,7 @@ int socket_create_server(const char *addr)
return -1;
}
if (sa.gen.sa_family == AF_INET)
if (sa.gen.sa_family == AF_INET || sa.gen.sa_family == AF_INET6)
{
/* Avoid TIME_WAIT state on socket close */
opt = 1;
+16 -10
View File
@@ -14,9 +14,15 @@
#include "telnet.h"
#define HELP \
"Usage: nvtsrv -deq\n"
/**INDENT-OFF**/
static const char help_text[] =
"Usage: nvtsrv OPTIONS... HOST:PORT\n"
"OPTIONS:\n"
" -d : Enable debug\n"
" -e : Echo received data\n"
" -q : Quiet\n"
;
/**INDENT-ON**/
struct {
unsigned char mline;
@@ -30,10 +36,10 @@ int nfds;
#define fd_cli pfds[2].fd
static void _nvtsrv_usage(void)
static void _usage(int rc)
{
printf("Usage:\n" " nvtsrv host:port\n");
exit(0);
printf(help_text);
exit(rc);
}
static void _tn_cli_close(void)
@@ -68,7 +74,7 @@ static void _tn_tx(void *cctx, const void *buf, int len)
static void _tn_ss(void *cctx, int signal, int value)
{
nvt_log(LOG_INFO, "RFC2217 signal %d: %02x", signal, value);
DBG("RFC2217 signal %d: %02x", signal, value);
}
static void _nvtsrv_init(void)
@@ -127,8 +133,8 @@ int main(int argc, char **argv)
switch (opt)
{
default: /* '?' */
fprintf(stderr, HELP);
exit(EXIT_FAILURE);
_usage(0);
break;
case 'd':
debug += 1;
break;
@@ -144,7 +150,7 @@ int main(int argc, char **argv)
argc -= optind;
argv += optind;
if (argc <= 0)
_nvtsrv_usage();
_usage(1);
nvt_log_level(quiet, debug);
nvt_log_dest(NVT_LOG_STDOUT);
+2 -4
View File
@@ -138,8 +138,7 @@ void telnet_rfc2217_handle_opt(tn_ctx_t * tcc, int opt,
case 9: /* Set DTR Signal State OFF */
valo = vali;
tcc->dtr_state = (valo == 8) ? 1 : 0;
nvt_log(LOG_INFO, "RFC2217: Set DTR %s",
tcc->dtr_state ? "on" : "off");
DBG("RFC2217: Set DTR %s", tcc->dtr_state ? "on" : "off");
goto do_ctl_reply;
case 10: /* Request RTS Signal State */
@@ -149,8 +148,7 @@ void telnet_rfc2217_handle_opt(tn_ctx_t * tcc, int opt,
case 12: /* Set RTS Signal State OFF */
valo = vali;
tcc->rts_state = (valo == 11) ? 1 : 0;
nvt_log(LOG_INFO, "RFC2217: Set RTS %s",
tcc->rts_state ? "on" : "off");
DBG("RFC2217: Set RTS %s", tcc->rts_state ? "on" : "off");
goto do_ctl_reply;
case 13: /* Request Com Port Flow Control Setting (inbound) */
+1 -1
View File
@@ -6,4 +6,4 @@ nvttest_SOURCES = \
nvttest_CPPFLAGS = -I$(top_builddir) -I$(top_srcdir) $(CFLAGS_WARN)
nvttest_LDADD = $(top_builddir)/lib/libnvt.la
nvttest_LDADD = $(top_builddir)/lib/libnvt.la -lpthread
+67 -22
View File
@@ -7,6 +7,7 @@
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
@@ -16,10 +17,20 @@
#include "lib/nvt_log.h"
#define HELP \
"Usage: nvttest -dqr [-n loops] [-t threads]\n"
/**INDENT-OFF**/
static const char help_text[] =
"Usage: nvttest OPTIONS...\n"
"OPTIONS:\n"
" -D name : Specify device name (/dev/name, default /dev/ttyNVT0)\n"
" -d : Enable debug\n"
" -n loops : Test loops per thread\n"
" -q : Quiet\n"
" -r : Insert random delay in loops\n"
" -t threads : N. threads\n"
;
/**INDENT-ON**/
static const char *dev = "/dev/ttyNVT0";
static char dev[64] = "/dev/ttyNVT0";
typedef struct {
int nthread;
@@ -28,22 +39,37 @@ typedef struct {
bool opt_rand_delay;
} topts_t;
typedef struct {
pthread_t thr;
topts_t opts;
int errors;
} thr_t;
static void _usage(int rc)
{
printf(help_text);
exit(rc);
}
static void *_worker(void *arg)
{
topts_t *topts = arg;
thr_t *thr = arg;
int iloop;
int fd, err, nw;
struct termios ios;
for (iloop = 0; iloop < topts->nloop; iloop++)
for (iloop = 0; iloop < thr->opts.nloop; iloop++)
{
nvt_log(LOG_INFO, "Thread %d/%d run %d/%d\n",
topts->ithread, topts->nthread, iloop + 1, topts->nloop);
thr->opts.ithread, thr->opts.nthread,
iloop + 1, thr->opts.nloop);
{
fd = open(dev, O_RDWR);
if (fd < 0)
{
nvt_log(LOG_ERR, "Open '%s' failed: %m\n", dev);
thr->errors++;
break;
}
@@ -52,12 +78,14 @@ static void *_worker(void *arg)
if (err != 0)
{
nvt_log(LOG_ERR, "ioctl(TCGETS) failed: %m\n");
thr->errors++;
break;
}
err = ioctl(fd, TCSETS, &ios);
if (err != 0)
{
nvt_log(LOG_ERR, "ioctl(TCSETS) failed: %m\n");
thr->errors++;
break;
}
close(fd);
@@ -74,6 +102,7 @@ static void *_worker(void *arg)
if (fd < 0)
{
nvt_log(LOG_ERR, "Open '%s' failed: %m\n", dev);
thr->errors++;
break;
}
@@ -84,18 +113,20 @@ static void *_worker(void *arg)
if (nw < 0)
{
nvt_log(LOG_ERR, "write failed: %m\n");
thr->errors++;
break;
}
if (nw < len)
{
nvt_log(LOG_ERR, "write short: %u/%u\n", nw, len);
thr->errors++;
break;
}
}
close(fd);
}
if (topts->opt_rand_delay)
if (thr->opts.opt_rand_delay)
usleep(rand() & 0xfff);
}
@@ -104,24 +135,28 @@ static void *_worker(void *arg)
int main(int argc, char **argv)
{
int err;
int err, errors;
int opt;
int ithr, nthr;
topts_t topts = { }, *popts;
pthread_t *ptids;
thr_t *threads, *thr;
topts_t topts = { };
int debug, quiet;
struct stat st;
debug = quiet = 0;
nthr = 1;
topts.nloop = 1;
while ((opt = getopt(argc, argv, "dn:qrt:")) != -1)
while ((opt = getopt(argc, argv, "D:dn:qrt:")) != -1)
{
switch (opt)
{
default: /* '?' */
fprintf(stderr, HELP);
exit(EXIT_FAILURE);
_usage(1);
break;
case 'D':
snprintf(dev, sizeof(dev), "/dev/%s", optarg);
break;
case 'd':
debug += 1;
break;
@@ -140,22 +175,28 @@ int main(int argc, char **argv)
}
}
if (stat(dev, &st) || !S_ISCHR(st.st_mode))
{
fprintf(stderr, "Invalid device file: '%s'\n", dev);
exit(EXIT_FAILURE);
}
nvt_log_level(quiet, debug);
nvt_log_dest(NVT_LOG_STDOUT);
topts.nthread = nthr;
ptids = malloc(nthr * sizeof(pthread_t));
threads = calloc(nthr, sizeof(thr_t));
for (ithr = 0; ithr < nthr; ithr++)
{
nvt_log(LOG_INFO, "Create worker thread %d/%d\n", ithr + 1, nthr);
popts = malloc(sizeof(topts_t));
*popts = topts;
popts->ithread = ithr + 1;
thr = &threads[ithr];
thr->opts = topts;
thr->opts.ithread = ithr + 1;
err = pthread_create(&ptids[ithr], NULL, _worker, popts);
err = pthread_create(&thr->thr, NULL, _worker, thr);
if (err)
{
nvt_log(LOG_ERR, "Failed to create thread %d/%d\n", ithr, nthr);
@@ -163,13 +204,17 @@ int main(int argc, char **argv)
}
}
errors = 0;
for (ithr = 0; ithr < nthr; ithr++)
{
if (ptids[ithr] == 0)
thr = &threads[ithr];
if (thr->thr == 0)
break;
pthread_join(ptids[ithr], NULL);
nvt_log(LOG_INFO, "Joined worker thread %d/%d\n", ithr + 1, nthr);
pthread_join(thr->thr, NULL);
errors += thr->errors;
nvt_log(LOG_INFO, "Joined worker thread %d/%d (err=%d)\n",
ithr + 1, nthr, thr->errors);
}
return 0;
return errors ? 1 : 0;
}
+5 -4
View File
@@ -7,6 +7,7 @@
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <errno.h>
#include "telnet.h"
#include "telnet_param.h"
@@ -284,12 +285,12 @@ int telnet_open(tn_ctx_t * tcc, int mode)
while (1)
{
res = tcc->srv_rx(tcc->cctx, 1000);
if (res <= 0)
return -1;
if (res < 0)
return errno;
else if (tcc->mode_rfc2217)
return 0;
else if (t - time(NULL) > 2)
return -1;
else if (time(NULL) - t > 10)
return EPROTO;
}
}
+64 -139
View File
@@ -55,17 +55,14 @@ static struct ttynvt_param {
typedef struct {
pthread_mutex_t tty_lock;
pthread_mutex_t poll_lock;
pthread_mutex_t read_lock;
pthread_mutex_t write_lock;
pthread_mutex_t ioctl_lock;
pthread_t ptid_poll;
pthread_t ptid_read;
struct fuse_pollhandle *ph;
volatile int rel_pending;
volatile int slave_suspended; /* Waiting for application to read data */
volatile int master_suspended; /* Server is not ready to receive data */
volatile int slave_suspended; /*waiting for application to read data */
volatile int master_suspended; /*server is not ready to receive data */
struct pollfd fds[3];
char net_buf[NET_BUF_SIZE];
@@ -83,18 +80,8 @@ typedef struct {
int epipe;
tn_ctx_t *tn;
int seq;
struct ttynvt_cli_s *cli;
} ttynvt_t;
static ttynvt_t *ptty = NULL;
typedef struct ttynvt_cli_s {
int id;
ttynvt_t *tty;
struct ttynvt_cli_s *next;
} ttynvt_cli_t;
static pthread_mutex_t access_lock = PTHREAD_MUTEX_INITIALIZER;
@@ -127,6 +114,7 @@ static void ttynvt_interrupted(fuse_req_t req, void *data)
pthread_kill(thread, SIGUSR2);
}
static void _ttynvt_sleep_us(unsigned int tus)
{
struct timespec ts;
@@ -155,6 +143,7 @@ static int _check_dev(void)
return rc;
}
static void _fd_close(int fd)
{
if (fd >= 0)
@@ -417,49 +406,24 @@ static void _ttynvt_ctx_destroy(ttynvt_t * tty)
static void ttynvt_open(fuse_req_t req, struct fuse_file_info *info)
{
ttynvt_t *tty = ptty;
ttynvt_cli_t *tty_cli;
ttynvt_t *tty;
char *slave_name;
struct termios ios;
int fd;
int res;
int n;
tty_cli = calloc(1, sizeof(ttynvt_cli_t));
if (!tty_cli)
{
fuse_reply_err(req, ENOMEM);
return;
}
pthread_mutex_lock(&access_lock);
if (tty == NULL)
nvt_log(LOG_INFO, "Connecting to server: %s\n", ttynvt_param.server);
tty = _ttynvt_ctx_create();
if (!tty)
{
fuse_reply_err(req, ENOMEM);
free(tty_cli);
pthread_mutex_unlock(&access_lock);
return;
}
tty_cli->id = tty->seq++;
tty_cli->tty = tty;
tty_cli->next = tty->cli;
tty->cli = tty_cli;
nvt_log(LOG_INFO, "Client open (%d)\n", tty_cli->id);
if (ptty)
goto out;
ptty = tty;
nvt_log(LOG_INFO, "Connecting to server: %s\n", ttynvt_param.server);
fd = socket_create_client(ttynvt_param.server);
if (fd < 0)
{
@@ -471,7 +435,7 @@ static void ttynvt_open(fuse_req_t req, struct fuse_file_info *info)
tty->fds[FD_NET].events = POLLIN;
tty->net_cnt = 0;
if ((fd = open("/dev/ptmx", O_RDWR)) < 0) /* Open master */
if ((fd = open("/dev/ptmx", O_RDWR)) < 0) /* open master */
goto open_err;
tty->fds[FD_MASTER].fd = fd;
@@ -489,11 +453,11 @@ static void ttynvt_open(fuse_req_t req, struct fuse_file_info *info)
if (ioctl(fd, TCSETS, &ios) < 0)
goto open_err;
grantpt(fd); /* Change permission of slave */
unlockpt(fd); /* Unlock slave */
slave_name = ptsname(fd); /* Get name of slave */
grantpt(fd); /* change permission of slave */
unlockpt(fd); /* unlock slave */
slave_name = ptsname(fd); /* get name of slave */
if ((fd = open(slave_name, O_RDWR | O_NOCTTY)) < 0) /* Open slave */
if ((fd = open(slave_name, O_RDWR | O_NOCTTY)) < 0) /* open slave */
goto open_err;
ioctl(fd, TCGETS, &tty->tio);
@@ -506,42 +470,34 @@ static void ttynvt_open(fuse_req_t req, struct fuse_file_info *info)
res =
telnet_open(tty->tn, ttynvt_param.raw ? TN_MODE_RAW : TN_MODE_TELNET);
if (res < 0)
if (res != 0)
{
errno = EPROTO;
errno = res;
goto open_err;
}
if ((res = pthread_mutex_init(&tty->tty_lock, NULL) < 0) ||
(res = pthread_mutex_init(&tty->poll_lock, NULL) < 0) ||
(res = pthread_mutex_init(&tty->ioctl_lock, NULL) < 0) ||
(res = pthread_mutex_init(&tty->read_lock, NULL) < 0) ||
(res = pthread_mutex_init(&tty->write_lock, NULL) < 0) ||
if ((res = pthread_mutex_init(&tty->tty_lock, NULL)) ||
(res = pthread_mutex_init(&tty->poll_lock, NULL)) ||
(res = pthread_create(&tty->ptid_poll, NULL, &_read_net, tty)))
{
errno = res;
goto open_err;
}
nvt_log(LOG_INFO, "Connected to server: %s\n", ttynvt_param.server);
out:
info->fh = (uintptr_t) tty_cli;
info->fh = (uintptr_t) tty;
info->nonseekable = 1;
info->direct_io = 1;
fuse_reply_open(req, info);
nvt_log(LOG_INFO, "Connected to server: %s\n", ttynvt_param.server);
pthread_mutex_unlock(&access_lock);
fuse_reply_open(req, info);
return;
open_err:
for (n = 0; n < 3; n++)
_fd_close(tty->fds[n].fd);
free(tty_cli);
_ttynvt_ctx_destroy(tty);
ptty = NULL;
fuse_reply_err(req, errno);
@@ -555,14 +511,11 @@ static void
ttynvt_read(fuse_req_t req, size_t size, off_t off,
struct fuse_file_info *info)
{
ttynvt_cli_t *tty_cli = (ttynvt_cli_t *) (uintptr_t) info->fh;
ttynvt_t *tty = tty_cli->tty;
ttynvt_t *tty = (ttynvt_t *) (uintptr_t) info->fh;
char buf[TMP_BUF_SIZE];
int res, nr;
pthread_mutex_lock(&tty->read_lock);
DBG2("%s (%d)\n", __func__, tty_cli->id);
DBG2("%s\n", __func__);
if (tty->error)
{
@@ -576,7 +529,7 @@ ttynvt_read(fuse_req_t req, size_t size, off_t off,
else
fuse_reply_err(req, EBADFD);
goto out;
return;
}
if (size > TMP_BUF_SIZE)
@@ -603,42 +556,36 @@ ttynvt_read(fuse_req_t req, size_t size, off_t off,
{
DBG2("%s: error: %m\n", __func__);
fuse_reply_err(req, errno);
goto out;
return;
}
DBG2("%s (%d): fd=%d: sz=%u/%u: '%.*s'\n", __func__,
tty_cli->id, tty->fds[FD_SLAVE].fd, res, (int)size, res, buf);
DBG2("%s: fd=%d: sz=%u/%u: '%.*s'\n", __func__,
tty->fds[FD_SLAVE].fd, res, (int)size, res, buf);
fuse_reply_buf(req, buf, res);
out:
pthread_mutex_unlock(&tty->read_lock);
}
static void
ttynvt_write(fuse_req_t req, const char *data, size_t size, off_t off,
struct fuse_file_info *info)
{
ttynvt_cli_t *tty_cli = (ttynvt_cli_t *) (uintptr_t) info->fh;
ttynvt_t *tty = tty_cli->tty;
ttynvt_t *tty = (ttynvt_t *) (uintptr_t) info->fh;
int res;
pthread_mutex_lock(&tty->write_lock);
DBG2("%s (%d): fd=%d: sz=%u: '%.*s'\n", __func__,
tty_cli->id, tty->fds[FD_SLAVE].fd, (int)size, (int)size, data);
DBG2("%s: fd=%d: sz=%u: '%.*s'\n", __func__,
tty->fds[FD_SLAVE].fd, (int)size, (int)size, data);
if (size == 0)
{
fuse_reply_write(req, 0);
goto out;
return;
}
if (tty->error)
{
DBG2("%s: tty->error=%d\n", __func__, tty->error);
fuse_reply_err(req, EPIPE);
goto out;
return;
}
res = write(tty->fds[FD_SLAVE].fd, data, size);
@@ -647,7 +594,7 @@ ttynvt_write(fuse_req_t req, const char *data, size_t size, off_t off,
DBG2("%s: error: %m\n", __func__);
tty->error = 1;
fuse_reply_err(req, errno);
goto out;
return;
}
else if (res != (int)size)
{
@@ -656,9 +603,6 @@ ttynvt_write(fuse_req_t req, const char *data, size_t size, off_t off,
}
fuse_reply_write(req, res);
out:
pthread_mutex_unlock(&tty->write_lock);
}
static int baudrate(speed_t speed)
@@ -747,10 +691,12 @@ static unsigned int _tio_parity(const struct termios2 *tio)
{
unsigned int par;
if ((tio->c_cflag & PARENB) && (tio->c_iflag & IGNPAR))
if (!(tio->c_cflag & PARENB))
{
par = 1;
}
#ifdef CMSPAR
else if ((tio->c_cflag & PARENB) && (tio->c_iflag & CMSPAR))
else if (tio->c_iflag & CMSPAR)
{
if (tio->c_cflag & PARODD)
par = 4;
@@ -758,10 +704,14 @@ static unsigned int _tio_parity(const struct termios2 *tio)
par = 5;
}
#endif
else if (tio->c_cflag & PARENB)
par = 3;
else if (tio->c_cflag & PARODD)
{
par = 2;
}
else
par = 1;
{
par = 3;
}
return par;
}
@@ -836,8 +786,7 @@ ttynvt_ioctl(fuse_req_t req, int cmd, void *arg,
struct fuse_file_info *info, unsigned int flags,
const void *in_buf, size_t in_bufsz, size_t out_bufsz)
{
ttynvt_cli_t *tty_cli = (ttynvt_cli_t *) (uintptr_t) info->fh;
ttynvt_t *tty = tty_cli->tty;
ttynvt_t *tty = (ttynvt_t *) (uintptr_t) info->fh;
struct termios2 *tio = &tty->tio;
struct winsize ws;
unsigned int tiocm, baud;
@@ -847,16 +796,14 @@ ttynvt_ioctl(fuse_req_t req, int cmd, void *arg,
uint8_t byte;
uint32_t byte4;
pthread_mutex_lock(&tty->ioctl_lock);
DBG("%s (%d): cmd=%s arg=%p ibuf=%p:%u obuf=:%u\n", __func__,
tty_cli->id, _ioctl_name(cmd), arg, in_buf,
DBG("%s: cmd=%s arg=%p ibuf=%p:%u obuf=:%u\n", __func__,
_ioctl_name(cmd), arg, in_buf,
(unsigned int)in_bufsz, (unsigned int)out_bufsz);
if (ttynvt_param.raw)
{
fuse_reply_ioctl(req, 0, 0, 0);
goto out;
return;
}
switch (cmd)
@@ -877,8 +824,12 @@ ttynvt_ioctl(fuse_req_t req, int cmd, void *arg,
goto do_tcset;
do_tcset:
/* BEWARE of termios/termios2 buf structure difference! */
if (baud != 0)
{
byte4 = htonl(baud);
telnet_rfc2217_cfg(tty->tn, TNS_SET_BAUDRATE, &byte4, 4);
}
byte = _tio_csize(tio);
telnet_rfc2217_cfg(tty->tn, TNS_SET_DATASIZE, &byte, 1);
@@ -1047,7 +998,7 @@ ttynvt_ioctl(fuse_req_t req, int cmd, void *arg,
if (res < 0)
{
fuse_reply_err(req, errno);
goto out;
return;
}
fuse_reply_ioctl(req, 0, &ws, sizeof(struct winsize));
break;
@@ -1058,7 +1009,7 @@ ttynvt_ioctl(fuse_req_t req, int cmd, void *arg,
if (res < 0)
{
fuse_reply_err(req, errno);
goto out;
return;
}
fuse_reply_ioctl(req, 0, &ws, sizeof(struct winsize));
break;
@@ -1119,21 +1070,17 @@ ttynvt_ioctl(fuse_req_t req, int cmd, void *arg,
}
break;
}
out:
pthread_mutex_unlock(&tty->ioctl_lock);
}
static void
ttynvt_poll(fuse_req_t req, struct fuse_file_info *info,
struct fuse_pollhandle *ph)
{
ttynvt_cli_t *tty_cli = (ttynvt_cli_t *) (uintptr_t) info->fh;
ttynvt_t *tty = tty_cli->tty;
ttynvt_t *tty = (ttynvt_t *) (uintptr_t) info->fh;
int revents = 0;
DBG2("%s (%d): tty->pollin=%d tty->error/epipe=%d/%d\n",
__func__, tty_cli->id, tty->pollin, tty->error, tty->epipe);
DBG2("%s: tty->pollin=%d tty->error/epipe=%d/%d\n",
__func__, tty->pollin, tty->error, tty->epipe);
_update_notify(tty, ph);
@@ -1160,57 +1107,33 @@ ttynvt_poll(fuse_req_t req, struct fuse_file_info *info,
static void ttynvt_release(fuse_req_t req, struct fuse_file_info *info)
{
ttynvt_cli_t *tty_cli = (ttynvt_cli_t *) (uintptr_t) info->fh;
ttynvt_t *tty = tty_cli->tty;
ttynvt_cli_t *tmp, *prev;
ttynvt_t *tty = (ttynvt_t *) (uintptr_t) info->fh;
DBG("%s\n", __func__);
pthread_mutex_lock(&access_lock);
for (tmp = tty->cli, prev = NULL; tmp; prev = tmp, tmp = tmp->next)
if (tmp == tty_cli)
{
nvt_log(LOG_INFO, "Client release (%d)\n", tmp->id);
if (prev)
prev->next = tmp->next;
else
tty->cli = tmp->next;
free(tmp);
break;
}
if (tty->cli)
goto out;
tty->rel_pending = 1;
pthread_cancel(tty->ptid_poll);
pthread_join(tty->ptid_poll, NULL);
pthread_mutex_destroy(&tty->tty_lock);
pthread_mutex_destroy(&tty->poll_lock);
pthread_mutex_destroy(&tty->ioctl_lock);
pthread_mutex_destroy(&tty->read_lock);
pthread_mutex_destroy(&tty->write_lock);
if (ttynvt_param.close_delay_us)
_ttynvt_sleep_us(ttynvt_param.close_delay_us);
_fd_close(tty->fds[FD_NET].fd);
_fd_close(tty->fds[FD_MASTER].fd);
_fd_close(tty->fds[FD_SLAVE].fd);
if (ttynvt_param.close_delay_us)
_ttynvt_sleep_us(ttynvt_param.close_delay_us);
if (tty->ph)
fuse_pollhandle_destroy(tty->ph);
_ttynvt_ctx_destroy(tty);
ptty = NULL;
fuse_reply_err(req, 0);
nvt_log(LOG_INFO, "Disconnected from server: %s\n", ttynvt_param.server);
out:
fuse_reply_err(req, 0);
pthread_mutex_unlock(&access_lock);
}
@@ -1259,7 +1182,9 @@ static void print_help(void)
"\t-q\t\tBe quiet (suppress informational messages)\n"
"\t-r\t\tDisable telnet processing\n"
"\t-s delay \t\tDelay at close in ms (use u suffix for us)\n"
"\t-S server, --server=host:port\n");
"\t-S server, --server=host:port\n"
"\t\t\t[ipv6addr]:port (connect via IPv6)\n"
"\t\t\tunix:/path/to.socket (connect via UNIX domain socket)\n");
}
static int
+48
View File
@@ -0,0 +1,48 @@
# Unit test makefile
#
.NOTPARALLEL:
noinst_PROGRAMS = $(GTESTS)
GTEST_LIBS = -lgtest -lstdc++
GTESTS = test_misc test_nvttest
AM_CFLAGS = -Wall -Wextra -Werror -Wno-unused-parameter
AM_CFLAGS += $(CFLAGS_ASAN)
AM_CXXFLAGS = $(AM_CFLAGS)
AM_CPPFLAGS = -I $(top_builddir)
AM_CPPFLAGS += -D TOP_SRC_DIR='"$(top_srcdir)"' -D TOP_BLD_DIR='"$(top_builddir)"'
AM_CPPFLAGS += -D BUILD_TEST=1
LIBS += $(GTEST_LIBS)
TEST_COMMON = test.cpp test.h
test_misc_SOURCES = $(TEST_COMMON) test_misc.cpp
test_misc_LDADD = $(LIBS)
test_nvttest_SOURCES = $(TEST_COMMON) test_nvttest.cpp
test_nvttest_LDADD = $(LIBS)
TESTS_RUN = $(addprefix run-, $(GTESTS))
VG_PROG = valgrind --leak-check=full
all-local: run
.PHONY: run $(TESTS_RUN)
run: $(TESTS_RUN)
$(TESTS_RUN): run-%: %
# $(TEST_ENV) ./.libs/$* $(RUN_OPTS)
$(TEST_ENV) ./$* $(RUN_OPTS)
TESTS_RUN_VG = $(addprefix run-vg-, $(GTESTS))
.PHONY: run-vg $(TESTS_RUN_VG)
run-vg: $(TESTS_RUN_VG)
$(TESTS_RUN_VG): run-vg-%: %
# $(TEST_ENV) $(VG_PROG) ./.libs/$* $(RUN_OPTS)
$(TEST_ENV) $(VG_PROG) ./$* $(RUN_OPTS)
+70
View File
@@ -0,0 +1,70 @@
/*
* General test stuff
*
* - The main() function
* - Some printing functions
*/
#include "test.h"
int debug = 0;
static bool ttyout = false;
int main(int argc, char **argv)
{
const char *s;
ttyout = isatty(STDOUT_FILENO);
::testing::InitGoogleTest(&argc, argv);
for (argc--, argv++; argc > 0; argc--, argv++)
{
s = argv[0];
if (*s++ != '-')
break;
again:
switch (*s++)
{
case 'd':
debug++;
goto again;
}
}
return RUN_ALL_TESTS();
}
#include <stdarg.h>
void _pr_text(const char *col, const char *fmt, va_list args)
{
char fmtx[1024];
if (ttyout)
snprintf(fmtx, sizeof(fmtx), "%s[ ] - %s%s\n",
col, fmt, COL_RST);
else
snprintf(fmtx, sizeof(fmtx), "[ ] - %s\n", fmt);
fmt = fmtx;
vprintf(fmt, args);
}
void pr_text(const char *col, const char *fmt, ...)
{
va_list args;
va_start(args, fmt);
_pr_text(col, fmt, args);
va_end(args);
}
void pr_info(const char *fmt, ...)
{
va_list args;
va_start(args, fmt);
_pr_text(COL_YEL, fmt, args);
va_end(args);
}
+25
View File
@@ -0,0 +1,25 @@
#ifndef TEST_H
#define TEST_H 1
#include <gtest/gtest.h>
#include "config.h"
#define D(...) do{ if (debug) printf(__VA_ARGS__); }while(0)
#define D2(...) do{ if (debug > 1) printf(__VA_ARGS__); }while(0)
extern int debug;
#define COL_RST "\x1B[0m"
#define COL_RED "\x1B[31m"
#define COL_GRN "\x1B[32m"
#define COL_YEL "\x1B[33m"
#define COL_BLU "\x1B[34m"
#define COL_MAG "\x1B[35m"
#define COL_CYN "\x1B[36m"
#define COL_WHT "\x1B[37m"
void pr_text(const char *col, const char *fmt, ...);
void pr_info(const char *fmt, ...);
#endif /* TEST_H */
+6
View File
@@ -0,0 +1,6 @@
#include "test.h"
TEST(Misc, dummy)
{
EXPECT_EQ(1, 1);
}
+216
View File
@@ -0,0 +1,216 @@
#include "test.h"
#include <signal.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <sys/wait.h>
#define NVT_NAME "ttyNVT99"
#define NVT_DEV "/dev/" NVT_NAME
#define NVT_SRV TOP_BLD_DIR "/nvtsrv/nvtsrv"
#define NVT_TEST TOP_BLD_DIR "/nvttest/nvttest"
#define NVT_OPTS "-D" NVT_NAME
#define LAUNCH_NVTTEST(...) proc_launch(NVT_TEST, NVT_OPTS, __VA_ARGS__)
pid_t proc_spawn(char **argv)
{
pid_t pid;
char buf[1024];
for (int i = 0, len = 0; argv[i]; i++)
len += snprintf(buf + len, sizeof(buf) - len, "%s ", argv[i]);
pr_info("Launching: %s", buf);
pid = fork();
if (pid > 0)
{
pr_info("... ok, pid = %d", pid);
return pid;
}
// Child
execv(argv[0], argv);
pr_info("Launch failed: %m");
EXPECT_EQ(1, 0);
exit(1);
}
void proc_waitfor(const char *name, pid_t pid)
{
int err, wstatus;
pr_info("Wait for %s (pid %d) to finish", name, pid);
wstatus = 0;
err = waitpid(pid, &wstatus, 0);
if (err > 0)
pr_info("... ok, status = %x", wstatus);
else
pr_info("... error: %m");
EXPECT_GT(err, 0);
EXPECT_EQ(WEXITSTATUS(wstatus), 0);
}
static pid_t proc_launch(const char *prog, ...)
{
char *argv[16], *arg;
int i;
pid_t pid;
va_list args;
va_start(args, prog);
arg = (char *)prog;
i = 0;
argv[i++] = arg;
for (; arg != NULL;)
{
arg = va_arg(args, char *);
argv[i++] = arg;
}
va_end(args);
pid = proc_spawn(argv);
return pid;
}
bool ttynvt_check()
{
struct stat st;
bool ok;
ok = stat(NVT_DEV, &st) == 0 && S_ISCHR(st.st_mode);
if (!ok)
{
fprintf(stderr, "\n%s"
"************************************************************\n"
"*** Invalid device file: '%s'\n"
"*** Ensure that ttynvt is launched correcly, e.g.:\n"
"# ttynvt -d -E -S localhost:1234 -n %s\n"
"*** ttynvt must run as root, and the device and server names\n"
"*** must be as shown above.\n"
"************************************************************\n"
"%s\n", COL_CYN, NVT_DEV, NVT_NAME, COL_RST);
}
return ok;
}
TEST(Test1, t1)
{
int err;
pid_t pid_srv, pid;
ASSERT_TRUE(ttynvt_check());
pid_srv = proc_launch(NVT_SRV, "-de", "localhost:1234", NULL);
ASSERT_GT(pid_srv, 0);
usleep(100000);
pid = LAUNCH_NVTTEST(NULL);
ASSERT_GT(pid, 0);
proc_waitfor("nvttest", pid);
pr_info("Terminate nvtsrv");
err = kill(pid_srv, SIGTERM);
EXPECT_EQ(err, 0);
proc_waitfor("nvtsrv", pid_srv);
}
/* Using fixture */
/**INDENT-OFF**/
class Test2:public::testing::Test {
pid_t pid_srv;
protected:
Test2() { }
virtual ~Test2() { }
virtual void SetUp() {
// Ensure ttynvt is running
ASSERT_TRUE(ttynvt_check());
// NB! Quiet nvtsrv
pid_srv = proc_launch(NVT_SRV, "-qe", "localhost:1234", NULL);
ASSERT_GT(pid_srv, 0);
usleep(100000);
}
virtual void TearDown() {
int err;
pr_info("Terminate nvtsrv");
err = kill(pid_srv, SIGTERM);
EXPECT_EQ(err, 0);
proc_waitfor("nvtsrv", pid_srv);
}
};
/**INDENT-ON**/
TEST_F(Test2, t1)
{
pid_t pid;
pid = LAUNCH_NVTTEST(NULL);
ASSERT_GT(pid, 0);
proc_waitfor("nvttest", pid);
}
// Multiple threads
TEST_F(Test2, t2)
{
pid_t pid;
// NB! Quiet nvttest
pid = LAUNCH_NVTTEST("-t5", "-q", NULL);
ASSERT_GT(pid, 0);
proc_waitfor("nvttest", pid);
}
// Multiple loops
TEST_F(Test2, t3)
{
pid_t pid;
// NB! Quiet nvttest
pid = LAUNCH_NVTTEST("-n5", "-q", NULL);
ASSERT_GT(pid, 0);
proc_waitfor("nvttest", pid);
}
// Multiple threads and loops
TEST_F(Test2, t4)
{
pid_t pid;
// NB! Quiet nvttest
pid = LAUNCH_NVTTEST("-n5", "-t5", "-q", NULL);
ASSERT_GT(pid, 0);
proc_waitfor("nvttest", pid);
}