Files
ttynvt/ttynvt.c
T
Kim Woelders 673ddd9739 Fix RFC2217 server death
We now properly kick the connected clients hanging in reads (cu) instead
of locking up.
2018-10-17 13:51:55 +02:00

1147 lines
30 KiB
C

/*
* Copyright (c) 2018 Lars Thrane A/S
* SPDX-License-Identifier: GPL-2.0
*/
#include "config.h"
#include <cuse_lowlevel.h>
#include <errno.h>
#include <fuse_opt.h>
#include <netdb.h>
#include <pthread.h>
#include <signal.h>
#include <stdarg.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <syslog.h>
#include <unistd.h>
#include <asm/termbits.h>
#include <linux/sockios.h>
#include <linux/ppp-ioctl.h>
#include <netinet/in.h>
#include <sys/ioctl.h>
#include <sys/poll.h>
#include <sys/socket.h>
#include <sys/types.h>
#include "telnet.h"
#define NET_BUF_SIZE 1024
#define TMP_BUF_SIZE 1024
#define FD_NET 0
#define FD_MASTER 1
#define FD_SLAVE 2
#define _PRF_N_(no) __attribute__((__format__(__printf__, (no), (no) + 1)))
#define _PRF2_ _PRF_N_(2)
static struct ttynvt_param {
unsigned int major;
unsigned int minor;
const char *dev_name;
char *server;
unsigned int debug;
int logstd;
/* Derived */
char host[64];
unsigned int port;
/* Picked up */
int foreground;
} ttynvt_param;
typedef struct {
pthread_mutex_t tty_lock;
pthread_mutex_t poll_lock;
pthread_t ptid_poll;
pthread_t ptid_read;
struct fuse_pollhandle *ph;
volatile char n_fds;
struct pollfd fds[3];
char net_buf[NET_BUF_SIZE];
int net_cnt;
struct termios tio;
int cmcr;
volatile int smcr;
volatile int pollin;
volatile int error;
int epipe;
tn_ctx_t *tn;
} ttynvt_t;
#include <sys/syscall.h>
#define gettid() (pid_t)syscall(__NR_gettid)
_PRF2_ static void _log(int prio, const char *fmt, ...)
{
va_list arg;
char buf[256];
va_start(arg, fmt);
vsnprintf(buf, sizeof(buf), fmt, arg);
if (ttynvt_param.logstd)
printf("[%d] %d: %s", gettid(), prio, buf);
else
syslog(prio, "[%d] %s", gettid(), buf);
va_end(arg);
}
#define BPL 16 /* Bytes per line */
void _log_buf(int prio, const char *txt, const void *ptr_, unsigned int len)
{
unsigned int ix, i, imax;
int l, n;
char buf[128];
const unsigned char *ptr = ptr_;
for (ix = 0; ix < len || (len == 0 && ix == 0); ix += BPL)
{
n = snprintf(buf, sizeof(buf), "%s: %04x:", txt, ix);
if (n <= 0)
return; /* Just playing it safe */
imax = (len - ix < BPL) ? len - ix : BPL;
for (i = 0; i < imax; i++)
{
l = snprintf(buf + n, sizeof(buf) - n, " %02x", *ptr++);
if (l <= 0)
return; /* Just playing it safe */
n += l;
}
_log(prio, "%s\n", buf);
}
}
#define DBG(...) \
if (ttynvt_param.debug > 0) _log(LOG_DEBUG, __VA_ARGS__)
#define DBG2(...) \
if (ttynvt_param.debug > 1) _log(LOG_DEBUG, __VA_ARGS__)
#define DBG2_BUF(txt, ptr, len) \
if (ttynvt_param.debug > 1) _log_buf(LOG_DEBUG, txt, ptr, len)
static volatile char _is_interrupted = 0;
static void sig_handler(int sig)
{
DBG("Interruped, signal = %d\n", sig);
_is_interrupted = 1;
if (sig != SIGUSR2)
exit(0);
}
static void set_signal_handler(void)
{
struct sigaction sa;
memset(&sa, 0, sizeof(struct sigaction));
sa.sa_handler = sig_handler;
sigemptyset(&(sa.sa_mask));
sa.sa_flags = 0;
sigaction(SIGUSR2, &sa, NULL);
sigaction(SIGTERM, &sa, NULL);
}
static void ttynvt_interrupted(fuse_req_t req, void *data)
{
pthread_t thread = (pthread_t) data;
pthread_kill(thread, SIGUSR2);
}
static int _check_dev(void)
{
int rc;
char dev_nbuf[128];
struct stat st;
snprintf(dev_nbuf, sizeof(dev_nbuf), "/dev/%s", ttynvt_param.dev_name);
rc = stat(dev_nbuf, &st);
DBG("Checking for %s: %d\n", dev_nbuf, rc);
return rc;
}
static void ttynvt_release(fuse_req_t req, struct fuse_file_info *info)
{
ttynvt_t *tty = (ttynvt_t *) (uintptr_t) info->fh;
_log(LOG_INFO, "connection closed\n");
tty->n_fds = 0;
close(tty->fds[FD_NET].fd);
close(tty->fds[FD_MASTER].fd);
close(tty->fds[FD_SLAVE].fd);
pthread_cancel(tty->ptid_poll);
pthread_join(tty->ptid_poll, NULL);
pthread_mutex_destroy(&tty->tty_lock);
pthread_mutex_destroy(&tty->poll_lock);
if (tty->ph)
fuse_pollhandle_destroy(tty->ph);
free(tty->tn);
free(tty);
fuse_reply_err(req, 0);
}
static void _notify(ttynvt_t * tty)
{
pthread_mutex_lock(&tty->poll_lock);
if (tty->ph)
{
fuse_lowlevel_notify_poll(tty->ph);
fuse_pollhandle_destroy(tty->ph);
tty->ph = NULL;
}
pthread_mutex_unlock(&tty->poll_lock);
}
static void _update_notify(ttynvt_t * tty, struct fuse_pollhandle *ph)
{
pthread_mutex_lock(&tty->poll_lock);
struct fuse_pollhandle *tmp_ph = tty->ph;
tty->ph = ph;
pthread_mutex_unlock(&tty->poll_lock);
if (tmp_ph)
fuse_pollhandle_destroy(tmp_ph);
}
static void *_read_net(void *arg)
{
ttynvt_t *tty = (ttynvt_t *) arg;
char buf[TMP_BUF_SIZE];
int res;
pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
DBG2("%s: net_cnt=%d\n", __func__, tty->net_cnt);
while (tty->n_fds > 0)
{
tty->fds[FD_NET].revents = tty->fds[FD_MASTER].revents =
tty->fds[FD_SLAVE].revents = 0;
res = poll(tty->fds, tty->n_fds, -1);
DBG2("%s: res=%d N=%d events-N/M/S=%#x/%#x/%#x\n", __func__,
res, tty->n_fds,
tty->fds[FD_NET].revents, tty->fds[FD_MASTER].revents,
tty->fds[FD_SLAVE].revents);
if (res < 0)
{
if (errno == EINTR)
continue;
break;
}
if (tty->n_fds == 0)
break;
if (tty->fds[FD_NET].revents & POLLIN)
{
res = read(tty->fds[FD_NET].fd, tty->net_buf + tty->net_cnt,
NET_BUF_SIZE - tty->net_cnt);
if (res < 0)
{
_log(LOG_ERR, "net read error: %m\n");
break;
}
if (res == 0)
{
_log(LOG_INFO, "Connection closed by remote host\n");
break;
}
tty->net_cnt += res;
DBG2_BUF("Serv in A", tty->net_buf, tty->net_cnt);
res = telnet_rx(tty->tn, tty->net_buf, &tty->net_cnt);
DBG2_BUF("Serv in B", tty->net_buf, tty->net_cnt);
if (tty->net_cnt > 0 && res == 0)
{
DBG2_BUF("TtyM out ", tty->net_buf, tty->net_cnt);
res =
write(tty->fds[FD_MASTER].fd, tty->net_buf, tty->net_cnt);
if (res < 0)
{
_log(LOG_ERR, "master write error: %m\n");
break;
}
tty->net_cnt = 0;
}
}
if (tty->fds[FD_MASTER].revents & POLLIN)
{
res = read(tty->fds[FD_MASTER].fd, buf, TMP_BUF_SIZE);
if (res < 0)
{
_log(LOG_ERR, "master read error: %m\n");
break;
}
DBG2_BUF("TtyM in ", buf, res);
telnet_tx(tty->tn, buf, res);
}
if (tty->fds[FD_SLAVE].revents & POLLIN)
{
tty->pollin = 1;
tty->n_fds = 2; /* Disable polling of Slave */
_notify(tty);
}
}
DBG2("%s: done\n", __func__);
if (tty->ptid_read)
pthread_kill(tty->ptid_read, SIGUSR2);
tty->error = 1;
_notify(tty);
return 0;
}
static int _srv_connect(const char *host, unsigned int port)
{
struct sockaddr_in serveraddr;
struct hostent *server;
int sockfd, err;
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0)
{
_log(LOG_ERR, "Socket open failed: %m\n");
return sockfd;
}
server = gethostbyname(host);
if (server == NULL)
{
_log(LOG_ERR, "Cannot resolve host name: %m\n");
close(sockfd);
return -1;
}
bzero((char *)&serveraddr, sizeof(serveraddr));
serveraddr.sin_family = AF_INET;
bcopy((char *)server->h_addr, (char *)&serveraddr.sin_addr.s_addr,
server->h_length);
serveraddr.sin_port = htons(port);
err = connect(sockfd, (struct sockaddr *)&serveraddr, sizeof(serveraddr));
if (err < 0)
{
_log(LOG_ERR, "Failed to connect to: %s:%u: %m\n", host, port);
close(sockfd);
return -1;
}
return sockfd;
}
static int _srv_write(void *cctx, const void *buf, int len)
{
ttynvt_t *tty = cctx;
int res;
DBG2_BUF("Serv out ", buf, len);
res = write(tty->fds[FD_NET].fd, buf, len);
DBG2("%s: fd=%d: len=%u: res=%d\n", __func__,
tty->fds[FD_NET].fd, len, res);
return res;
}
static int _srv_read(void *cctx, int timeout)
{
ttynvt_t *tty = cctx;
int res;
DBG2("%s: net_cnt=%d\n", __func__, tty->net_cnt);
while (1)
{
res = poll(&tty->fds[FD_NET], 1, timeout);
DBG("%s: res=%d events-N=%#x\n",
__func__, res, tty->fds[FD_NET].revents);
if (res <= 0)
return res;
if (tty->fds[FD_NET].revents & POLLIN)
{
res = read(tty->fds[FD_NET].fd, tty->net_buf + tty->net_cnt,
NET_BUF_SIZE - tty->net_cnt);
DBG2("%s: fd=%d: res=%d net_cnt=%d\n", __func__,
tty->fds[FD_NET].fd, res, tty->net_cnt);
if (res <= 0)
return res;
tty->net_cnt += res;
DBG2_BUF("Serv in a", tty->net_buf, tty->net_cnt);
res = telnet_rx(tty->tn, tty->net_buf, &tty->net_cnt);
DBG2_BUF("Serv in b", tty->net_buf, tty->net_cnt);
DBG2("%s: res=%d net_cnt=%d\n", __func__, res, tty->net_cnt);
if (res == 0)
break;
}
}
return 1;
}
static void _modem_status_cb(void *cctx, int status)
{
ttynvt_t *tty = cctx;
int mcr = 0;
DBG("MCR: DSR=%c CTS=%c RI=%c CD=%c\n",
status & TNS_STATE_DSR ? '1' : '0',
status & TNS_STATE_CTS ? '1' : '0',
status & TNS_STATE_RI ? '1' : '0', status & TNS_STATE_CD ? '1' : '0');
if (status & TNS_STATE_CD)
mcr |= TIOCM_CD;
if (status & TNS_STATE_RI)
mcr |= TIOCM_RI;
if (status & TNS_STATE_CTS)
mcr |= TIOCM_CTS;
if (status & TNS_STATE_DSR)
mcr |= TIOCM_DSR;
pthread_mutex_lock(&tty->tty_lock);
tty->smcr = mcr;
pthread_mutex_unlock(&tty->tty_lock);
}
static void ttynvt_init(void *userdata, struct fuse_conn_info *conn)
{
set_signal_handler();
}
static void ttynvt_open(fuse_req_t req, struct fuse_file_info *info)
{
ttynvt_t *tty;
char *slave_name;
struct termios ios;
int sockfd;
int fd;
int res;
int n;
DBG("%s\n", __func__);
tty = calloc(1, sizeof(*tty));
if (!tty)
{
fuse_reply_err(req, ENOMEM);
return;
}
for (n = 0; n < 3; n++)
tty->fds[n].fd = -1;
tty->tn = telnet_ctx_init(tty, _srv_write, _srv_read, _modem_status_cb);
if (!tty->tn)
{
free(tty);
fuse_reply_err(req, ENOMEM);
return;
}
sockfd = _srv_connect(ttynvt_param.host, ttynvt_param.port);
if (sockfd < 0)
{
free(tty->tn);
free(tty);
fuse_reply_err(req, EHOSTUNREACH);
return;
}
tty->fds[FD_NET].fd = sockfd;
tty->fds[FD_NET].events = POLLIN;
tty->net_cnt = 0;
if ((fd = open("/dev/ptmx", O_RDWR)) < 0) /* open master */
goto open_err;
tty->fds[FD_MASTER].fd = fd;
tty->fds[FD_MASTER].events = POLLIN;
memset(&ios, 0, sizeof(ios));
ios.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP
| INLCR | IGNCR | ICRNL | IXON);
ios.c_oflag &= ~OPOST;
ios.c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN);
ios.c_cflag &= ~(CSIZE | PARENB);
ios.c_cflag |= CS8;
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 */
if ((fd = open(slave_name, O_RDWR | O_NOCTTY)) < 0) /* open slave */
goto open_err;
ioctl(fd, TCGETS, &tty->tio);
tty->tio.c_cflag &= ~(CSIZE | PARENB);
tty->tio.c_cflag |= B115200 | CS8 | CREAD | CLOCAL;
ioctl(fd, TCSETS, &tty->tio);
tty->fds[FD_SLAVE].fd = fd;
tty->fds[FD_SLAVE].events = POLLIN;
res = telnet_open(tty->tn);
if (res < 0)
{
errno = EPROTO;
goto open_err;
}
if ((res = pthread_mutex_init(&tty->tty_lock, NULL) < 0) ||
(res = pthread_mutex_init(&tty->poll_lock, NULL) < 0) ||
(res = pthread_create(&tty->ptid_poll, NULL, &_read_net, tty)))
{
errno = res;
goto open_err;
}
tty->n_fds = 3; /* Initially poll Net, Master, and Slave */
info->fh = (uintptr_t) tty;
info->nonseekable = 1;
info->direct_io = 1;
_log(LOG_INFO, "Connected to server: %s:%d\n",
ttynvt_param.host, ttynvt_param.port);
fuse_reply_open(req, info);
return;
open_err:
_log(LOG_INFO, "Connection to server: %s:%d failed: %m\n",
ttynvt_param.host, ttynvt_param.port);
for (n = 0; n < 3; n++)
if (tty->fds[n].fd >= 0)
close(tty->fds[n].fd);
free(tty->tn);
free(tty);
fuse_reply_err(req, errno);
}
static void
ttynvt_read(fuse_req_t req, size_t size, off_t off,
struct fuse_file_info *info)
{
ttynvt_t *tty = (ttynvt_t *) (uintptr_t) info->fh;
char buf[TMP_BUF_SIZE];
int res;
DBG2("%s\n", __func__);
if (tty->error)
{
DBG2("%s: tty->error=%d\n", __func__, tty->error);
if (tty->epipe == 0)
{
_log(LOG_WARNING, "Rx EPIPE\n");
tty->epipe = 1;
fuse_reply_buf(req, NULL, 0);
}
else
fuse_reply_err(req, EBADFD);
return;
}
if (size > TMP_BUF_SIZE)
size = TMP_BUF_SIZE;
tty->ptid_read = pthread_self();
fuse_req_interrupt_func(req, ttynvt_interrupted, (void *)tty->ptid_read);
res = _is_interrupted ? -2 : read(tty->fds[FD_SLAVE].fd, buf, size);
_is_interrupted = 0;
tty->ptid_read = 0;
tty->n_fds = 3; /* Enable polling of Slave */
fuse_req_interrupt_func(req, NULL, NULL);
if (res < 0)
{
DBG2("%s: error: %m\n", __func__);
fuse_reply_err(req, errno);
return;
}
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);
}
static void
ttynvt_write(fuse_req_t req, const char *data, size_t size, off_t off,
struct fuse_file_info *info)
{
ttynvt_t *tty = (ttynvt_t *) (uintptr_t) info->fh;
int res;
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);
return;
}
if (tty->error)
{
DBG2("%s: tty->error=%d\n", __func__, tty->error);
fuse_reply_err(req, EPIPE);
return;
}
res = write(tty->fds[FD_SLAVE].fd, data, size);
if (res < 0)
{
DBG2("%s: error: %m\n", __func__);
tty->error = 1;
fuse_reply_err(req, errno);
return;
}
fuse_reply_write(req, res);
}
static int baudrate(speed_t speed)
{
return speed == B50 ? 50 :
speed == B75 ? 75 :
speed == B110 ? 110 :
speed == B300 ? 300 :
speed == B600 ? 600 :
speed == B1200 ? 1200 :
speed == B1800 ? 1800 :
speed == B2400 ? 2400 :
speed == B4800 ? 4800 :
speed == B9600 ? 9600 :
speed == B19200 ? 19200 :
speed == B38400 ? 38400 :
speed == B57600 ? 57600 :
speed == B115200 ? 115200 : speed == B230400 ? 230400 : 0;
}
static const char pstr[] = { ' ', 'N', 'O', 'E', 'M', 'S' };
#define CASE(name) case name: return #name
static const char *_ioctl_name(int cmd)
{
static char buf[16];
switch (cmd)
{
default:
snprintf(buf, sizeof(buf), "0x%x", cmd);
return buf;
CASE(TCSETSF);
CASE(TCSETSW);
CASE(TCSETS);
CASE(TCGETS);
CASE(TIOCINQ);
CASE(TIOCOUTQ);
CASE(TCFLSH);
CASE(TCIFLUSH);
CASE(TCOFLUSH);
CASE(TCIOFLUSH);
CASE(TIOCMGET);
CASE(TIOCMBIS);
CASE(TIOCMBIC);
CASE(TIOCMSET);
CASE(TCSBRK);
CASE(TIOCGWINSZ);
CASE(TIOCSWINSZ);
/* Controlling terminal */
CASE(TIOCSCTTY);
CASE(TIOCNOTTY);
/* Exclusive mode */
CASE(TIOCEXCL);
CASE(TIOCGEXCL);
CASE(TIOCNXCL);
/* Line discipline */
CASE(TIOCGETD);
CASE(TIOCSETD);
/*PPP support */
CASE(PPPIOCGCHAN);
}
}
#define CHECK_BUF_IN(sz) \
if (in_bufsz < sz) do { in_bufsz = sz; goto retry_in; } while(0)
#define CHECK_BUF_OUT(sz) \
if (out_bufsz < sz) do { out_bufsz = sz; goto retry_out; } while(0)
static void
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_t *tty = (ttynvt_t *) (uintptr_t) info->fh;
struct termios *tio = &tty->tio;
struct winsize ws;
unsigned int tiocm = 0;
int mcr;
int val;
int res;
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);
switch (cmd)
{
case TCSETSF:
case TCSETSW:
case TCSETS:
CHECK_BUF_IN(sizeof(struct termios));
{
speed_t speed;
int csize, par, sb;
memcpy(tio, in_buf, in_bufsz);
speed = tio->c_cflag & (CBAUD | CBAUDEX);
val = htonl(baudrate(speed));
telnet_rfc2217_cfg(tty->tn, TNS_SET_BAUDRATE, &val, 4);
if ((tio->c_cflag & CSIZE) == CS5)
csize = 5;
else if ((tio->c_cflag & CSIZE) == CS6)
csize = 6;
else if ((tio->c_cflag & CSIZE) == CS7)
csize = 7;
else
csize = 8;
telnet_rfc2217_cfg(tty->tn, TNS_SET_DATASIZE, &csize, 1);
if (tio->c_cflag & PARENB && tio->c_iflag & IGNPAR)
par = 1;
#ifdef CMSPAR
else if (tio->c_cflag & PARENB && tio->c_iflag & CMSPAR)
{
if (tio->c_cflag & PARODD)
par = 4;
else
par = 5;
}
#endif
else if (tio->c_cflag & PARENB)
par = 3;
else
par = 1;
telnet_rfc2217_cfg(tty->tn, TNS_SET_PARITY, &par, 1);
if (tio->c_cflag & CSTOPB)
sb = 2;
else
sb = 1;
telnet_rfc2217_cfg(tty->tn, TNS_SET_STOPSIZE, &sb, 1);
if (tio->c_cflag & CRTSCTS)
{
tty->cmcr |= TIOCM_RTS;
telnet_rfc2217_ctl(tty->tn, TNS_CTL_CTSRTS);
}
if (!(tty->cmcr & TIOCM_DTR) && speed != B0)
{
tty->cmcr |= TIOCM_DTR;
telnet_rfc2217_ctl(tty->tn, TNS_CTL_DTR_ON);
}
DBG("termios: %u%c%u%u %cCTSRTS %cCREAD %cHUPCL %cCLOCAL\n",
baudrate(speed), pstr[par], csize, sb,
tio->c_cflag & CRTSCTS ? '+' : '-',
tio->c_cflag & CREAD ? '+' : '-',
tio->c_cflag & HUPCL ? '+' : '-',
tio->c_cflag & CLOCAL ? '+' : '-');
ioctl(tty->fds[FD_SLAVE].fd, cmd, tio);
fuse_reply_ioctl(req, 0, 0, 0);
}
break;
case TCGETS:
CHECK_BUF_OUT(sizeof(struct termios));
fuse_reply_ioctl(req, 0, tio, sizeof(struct termios));
break;
case TIOCINQ: /* Get the number of bytes in the input
* buffer */
CHECK_BUF_OUT(sizeof(int));
ioctl(tty->fds[FD_SLAVE].fd, TIOCINQ, &val);
fuse_reply_ioctl(req, 0, &val, sizeof(int));
break;
case TIOCOUTQ: /* Get the number of bytes in the output
* buffer */
CHECK_BUF_OUT(sizeof(int));
ioctl(tty->fds[FD_SLAVE].fd, TIOCOUTQ, &val);
fuse_reply_ioctl(req, 0, &val, sizeof(int));
break;
case TCFLSH:
if (arg == TCIFLUSH)
{
/*todo */
}
else if ((size_t) arg == TCOFLUSH)
{
/*todo */
}
else if ((size_t) arg == TCIOFLUSH)
{
/*todo */
}
fuse_reply_ioctl(req, 0, 0, 0);
break;
case TCIFLUSH:
val = 1;
telnet_rfc2217_cfg(tty->tn, TNS_SET_PURGE, &val, 1);
fuse_reply_ioctl(req, 0, 0, 0);
break;
case TCOFLUSH:
val = 2;
telnet_rfc2217_cfg(tty->tn, TNS_SET_PURGE, &val, 1);
fuse_reply_ioctl(req, 0, 0, 0);
break;
case TCIOFLUSH:
val = 3;
telnet_rfc2217_cfg(tty->tn, TNS_SET_PURGE, &val, 1);
fuse_reply_ioctl(req, 0, 0, 0);
break;
case TIOCMGET:
CHECK_BUF_OUT(sizeof(int));
pthread_mutex_lock(&tty->tty_lock);
mcr = tty->cmcr | tty->smcr;
pthread_mutex_unlock(&tty->tty_lock);
fuse_reply_ioctl(req, 0, &mcr, sizeof(int));
break;
case TIOCMBIS:
CHECK_BUF_IN(sizeof(int));
tiocm = *(unsigned int *)in_buf;
fuse_reply_ioctl(req, 0, 0, 0);
DBG("%s: cmd=%s tiocm=%#x\n", __func__, _ioctl_name(cmd), tiocm);
if (tiocm & TIOCM_RTS)
{
tty->cmcr |= TIOCM_RTS;
telnet_rfc2217_ctl(tty->tn, TNS_CTL_RTS_ON);
}
if (tiocm & TIOCM_DTR)
{
tty->cmcr |= TIOCM_DTR;
telnet_rfc2217_ctl(tty->tn, TNS_CTL_DTR_ON);
}
goto show_mctl;
case TIOCMBIC:
CHECK_BUF_IN(sizeof(int));
tiocm = *(unsigned int *)in_buf;
fuse_reply_ioctl(req, 0, 0, 0);
DBG("%s: cmd=%s tiocm=%#x\n", __func__, _ioctl_name(cmd), tiocm);
if (tiocm & TIOCM_RTS)
{
tty->cmcr &= ~TIOCM_RTS;
telnet_rfc2217_ctl(tty->tn, TNS_CTL_RTS_OFF);
}
if (tiocm & TIOCM_DTR)
{
tty->cmcr &= ~TIOCM_DTR;
telnet_rfc2217_ctl(tty->tn, TNS_CTL_DTR_OFF);
}
goto show_mctl;
case TIOCMSET:
CHECK_BUF_IN(sizeof(int));
tiocm = *(unsigned int *)in_buf;
fuse_reply_ioctl(req, 0, 0, 0);
DBG("%s: cmd=%s tiocm=%#x\n", __func__, _ioctl_name(cmd), tiocm);
tty->cmcr &= ~(TIOCM_RTS | TIOCM_DTR);
tty->cmcr |= tiocm & (TIOCM_RTS | TIOCM_DTR);
telnet_rfc2217_ctl(tty->tn, (tiocm & TIOCM_DTR) ?
TNS_CTL_DTR_ON : TNS_CTL_DTR_OFF);
telnet_rfc2217_ctl(tty->tn, (tiocm & TIOCM_RTS) ?
TNS_CTL_RTS_ON : TNS_CTL_RTS_OFF);
goto show_mctl;
show_mctl:
DBG("MCR: DTR=%c RTS=%c\n",
tty->cmcr & TIOCM_DTR ? '1' : '0',
tty->cmcr & TIOCM_RTS ? '1' : '0');
break;
case TCSBRK:
fuse_reply_ioctl(req, 0, 0, 0);
break;
case TIOCGWINSZ:
CHECK_BUF_OUT(sizeof(struct winsize));
res = ioctl(tty->fds[FD_SLAVE].fd, TIOCGWINSZ, &ws);
if (res < 0)
{
fuse_reply_err(req, errno);
return;
}
fuse_reply_ioctl(req, 0, &ws, sizeof(struct winsize));
break;
case TIOCSWINSZ:
CHECK_BUF_IN(sizeof(struct winsize));
ws = *(struct winsize *)in_buf;
res = ioctl(tty->fds[FD_SLAVE].fd, TIOCSWINSZ, &ws);
if (res < 0)
{
fuse_reply_err(req, errno);
return;
}
fuse_reply_ioctl(req, 0, &ws, sizeof(struct winsize));
break;
case TIOCEXCL:
ioctl(tty->fds[FD_SLAVE].fd, TIOCEXCL, NULL);
fuse_reply_ioctl(req, 0, 0, 0);
break;
case TIOCGEXCL:
CHECK_BUF_OUT(sizeof(int));
ioctl(tty->fds[FD_SLAVE].fd, TIOCGEXCL, &val);
fuse_reply_ioctl(req, 0, &val, sizeof(int));
break;
case TIOCNXCL:
ioctl(tty->fds[FD_SLAVE].fd, TIOCNXCL, NULL);
fuse_reply_ioctl(req, 0, 0, 0);
break;
case TIOCSETD:
CHECK_BUF_IN(sizeof(int));
val = *(int *)in_buf;
res = ioctl(tty->fds[FD_SLAVE].fd, TIOCSETD, &val);
if (res < 0)
fuse_reply_err(req, errno);
else
fuse_reply_ioctl(req, 0, 0, 0);
break;
case TIOCGETD:
CHECK_BUF_OUT(sizeof(int));
res = ioctl(tty->fds[FD_SLAVE].fd, TIOCGETD, &val);
if (res < 0)
fuse_reply_err(req, errno);
else
fuse_reply_ioctl(req, 0, &val, sizeof(int));
break;
case PPPIOCGCHAN:
CHECK_BUF_OUT(sizeof(int));
res = ioctl(tty->fds[FD_SLAVE].fd, PPPIOCGCHAN, &val);
if (res < 0)
fuse_reply_err(req, errno);
else
fuse_reply_ioctl(req, 0, &val, sizeof(int));
break;
default:
fuse_reply_err(req, ENOSYS);
break;
retry_in:
{
struct iovec iov = { arg, in_bufsz };
fuse_reply_ioctl_retry(req, &iov, 1, NULL, 0);
}
break;
retry_out:
{
struct iovec iov = { arg, out_bufsz };
fuse_reply_ioctl_retry(req, NULL, 0, &iov, 1);
}
break;
}
}
static void
ttynvt_poll(fuse_req_t req, struct fuse_file_info *info,
struct fuse_pollhandle *ph)
{
ttynvt_t *tty = (ttynvt_t *) (uintptr_t) info->fh;
int revents;
DBG2("%s: tty->pollin=%d tty->error/epipe=%d/%d\n",
__func__, tty->pollin, tty->error, tty->epipe);
_update_notify(tty, ph);
revents = POLLOUT;
if (tty->pollin)
{
tty->pollin = 0;
revents |= POLLIN;
}
if (tty->error)
{
if (tty->epipe == 0)
revents |= POLLHUP | POLLIN;
else
revents = 0;
}
DBG2("%s: revents=%#x\n", __func__, revents);
fuse_reply_poll(req, revents);
}
static const struct cuse_lowlevel_ops ttynvt_op = {
.init = ttynvt_init,
.open = ttynvt_open,
.read = ttynvt_read,
.write = ttynvt_write,
.ioctl = ttynvt_ioctl,
.poll = ttynvt_poll,
.release = ttynvt_release,
};
#define TTYNET_OPT(t, p, v) { t, offsetof(struct ttynvt_param, p), v }
static const struct fuse_opt ttynvt_opts[] = {
TTYNET_OPT("-D %d", debug, 1),
TTYNET_OPT("-E", logstd, 1),
TTYNET_OPT("-M %u", major, 1),
TTYNET_OPT("--maj=%u", major, 1),
TTYNET_OPT("-m %u", minor, 1),
TTYNET_OPT("--min=%u", minor, 1),
TTYNET_OPT("-n %s", dev_name, 0),
TTYNET_OPT("--name=%s", dev_name, 0),
TTYNET_OPT("-S %s", server, 0),
TTYNET_OPT("--server=%s", server, 0),
FUSE_OPT_KEY("-h", 0),
FUSE_OPT_KEY("--help", 0),
FUSE_OPT_END
};
static void print_help(void)
{
fprintf(stderr, "ttynvt Usage:\n"
"\t-D level\tEnable debug\n"
"\t-E\t\tPrint debug to stdout (default is syslog)\n"
"\t-M major, --maj=major\n"
"\t-m minor, --min=minor\n"
"\t-n name, --name=name (default: ttyNVT0)\n"
"\t\tDevice will be created in /dev/$name.\n"
"\t-S server, --server=host:port\n");
}
static int
process_arg(void *data, const char *arg, int key, struct fuse_args *outargs)
{
switch (key)
{
case 0:
print_help();
return fuse_opt_add_arg(outargs, "-ho");
default:
if (strcmp(arg, "-d") == 0 || strcmp(arg, "-f") == 0)
ttynvt_param.foreground = 1;
return 1;
}
}
int main(int argc, char *argv[])
{
int rc;
struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
char dev_name[128];
const char *dev_info_argv[] = { dev_name };
pid_t cpid;
openlog("ttynvt", LOG_PID, LOG_DAEMON);
if (fuse_opt_parse(&args, &ttynvt_param, ttynvt_opts, process_arg))
{
fprintf(stderr, "Failed to parse arguments\n");
return 1;
}
if (!ttynvt_param.dev_name)
ttynvt_param.dev_name = "ttyNVT0";
if (!ttynvt_param.server)
{
fprintf(stderr, "Server must be specified\n");
return 1;
}
rc = sscanf(ttynvt_param.server, "%63[^:]:%u", ttynvt_param.host,
&ttynvt_param.port);
if (rc <= 1)
{
fprintf(stderr, "Bad server specification: '%s'\n",
ttynvt_param.server);
return 1;
}
if (ttynvt_param.port == 0)
{
fprintf(stderr, "Bad server port: %u\n", ttynvt_param.port);
return 1;
}
snprintf(dev_name, sizeof(dev_name), "DEVNAME=%s", ttynvt_param.dev_name);
struct cuse_info ci = {
.dev_major = ttynvt_param.major,
.dev_minor = ttynvt_param.minor,
.dev_info_argc = sizeof(dev_info_argv) / sizeof(char *),
.dev_info_argv = dev_info_argv,
.flags = CUSE_UNRESTRICTED_IOCTL,
};
if (ttynvt_param.foreground)
{
return cuse_lowlevel_main(args.argc, args.argv, &ci, &ttynvt_op, NULL);
}
cpid = fork();
switch (cpid)
{
case -1:
_log(LOG_ERR, "fork(): %m\n");
return -1;
case 0: /* Child */
DBG("Child proceeding...\n");
rc = cuse_lowlevel_main(args.argc, args.argv, &ci, &ttynvt_op, NULL);
if (rc != 0)
_log(LOG_ERR, "ttynvt failed rc=%d\n", rc);
else
DBG("Child exited\n");
return rc;
default: /* Parent */
break;
}
DBG("Parent proceeding (child=%d)...\n", cpid);
for (rc = 0; rc < 10; rc++)
{
if (_check_dev() == 0)
break;
usleep(500000);
}
DBG("Parent exiting\n");
return 0;
}