Files
ttynvt/ttynvt.c
T
Kim Woelders a05b17f142 Properly exit on SIGTERM
Previously we could get hung when killed, e.g. when using cu.
When using minicom or screen this problem did not seem to occur.
2018-10-17 08:11:46 +02:00

1136 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 thread_id;
struct fuse_pollhandle *ph;
volatile char n_fds;
struct pollfd fds[3];
char net_buf[NET_BUF_SIZE];
int net_cnt;
char tmp_buf[TMP_BUF_SIZE];
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->thread_id);
pthread_join(tty->thread_id, 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;
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, tty->tmp_buf, TMP_BUF_SIZE);
if (res < 0)
{
_log(LOG_ERR, "master read error: %m\n");
break;
}
DBG2_BUF("TtyM in ", tty->tmp_buf, res);
telnet_tx(tty->tn, tty->tmp_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__);
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->thread_id, 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;
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, tty->tmp_buf, 0);
}
else
fuse_reply_err(req, EBADFD);
return;
}
if (size > TMP_BUF_SIZE)
size = TMP_BUF_SIZE;
fuse_req_interrupt_func(req, ttynvt_interrupted, (void *)pthread_self());
res =
_is_interrupted ? -2 : read(tty->fds[FD_SLAVE].fd, tty->tmp_buf, size);
_is_interrupted = 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, tty->tmp_buf);
fuse_reply_buf(req, tty->tmp_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);
/* 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)
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;
}