0f879c8b61
In certain cases we may get zero size reads (e.g. if the server sends an rfc2217 notification without any other data). In this situation we must not propagate the zero size read to user space as it may be interpreted as end of input. I haven't found a way to tell fuse that it should just ignore this read but passing EINTR seems to work.
857 lines
21 KiB
C
857 lines
21 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 <termios.h>
|
|
#include <unistd.h>
|
|
#include <linux/sockios.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 RX_BUF_SIZE 1024
|
|
|
|
|
|
static struct ttynvt_param {
|
|
unsigned int major;
|
|
unsigned int minor;
|
|
char *dev_name;
|
|
char *host;
|
|
unsigned int port;
|
|
unsigned int debug;
|
|
} ttynvt_param;
|
|
|
|
typedef struct ttynvt {
|
|
int nonblock;
|
|
pthread_mutex_t tty_lock;
|
|
pthread_mutex_t poll_lock;
|
|
pthread_t thread_id;
|
|
|
|
pthread_cond_t cond;
|
|
|
|
struct fuse_pollhandle *ph;
|
|
|
|
struct pollfd fds[1];
|
|
|
|
struct termios tio;
|
|
unsigned char mcr;
|
|
|
|
char buf[RX_BUF_SIZE];
|
|
size_t blen;
|
|
size_t rxcnt;
|
|
|
|
int txbuf_size;
|
|
int epipe;
|
|
volatile sig_atomic_t error;
|
|
|
|
tn_ctx_t *tn;
|
|
} ttynvt_t;
|
|
|
|
static void ttynvt_log(int prio, const char *fmt, ...)
|
|
{
|
|
va_list arg;
|
|
|
|
if (prio >= LOG_DEBUG && ttynvt_param.debug <= 0)
|
|
return;
|
|
|
|
va_start(arg, fmt);
|
|
vsyslog(prio, fmt, arg);
|
|
va_end(arg);
|
|
}
|
|
|
|
static void ttynvt_release(fuse_req_t req, struct fuse_file_info *info)
|
|
{
|
|
ttynvt_t *tty = (ttynvt_t *) (uintptr_t) info->fh;
|
|
|
|
ttynvt_log(LOG_INFO, "connection closed\n");
|
|
|
|
close(tty->fds[0].fd);
|
|
|
|
(void)pthread_cancel(tty->thread_id);
|
|
(void)pthread_join(tty->thread_id, NULL);
|
|
(void)pthread_mutex_destroy(&tty->tty_lock);
|
|
(void)pthread_mutex_destroy(&tty->poll_lock);
|
|
(void)pthread_cond_destroy(&tty->cond);
|
|
|
|
if (tty->ph)
|
|
fuse_pollhandle_destroy(tty->ph);
|
|
|
|
free(tty->tn);
|
|
free(tty);
|
|
fuse_reply_err(req, 0);
|
|
}
|
|
|
|
static void ttynvt_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);
|
|
pthread_cond_signal(&tty->cond);
|
|
}
|
|
|
|
static void ttynvt_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 unsigned int ttynvt_write_avail(ttynvt_t * tty)
|
|
{
|
|
size_t write_avail;
|
|
int used;
|
|
|
|
pthread_mutex_lock(&tty->tty_lock);
|
|
if (ioctl(tty->fds[0].fd, SIOCOUTQ, &used) < 0)
|
|
return 0;
|
|
|
|
write_avail = tty->txbuf_size - used;
|
|
|
|
pthread_mutex_unlock(&tty->tty_lock);
|
|
return write_avail;
|
|
}
|
|
|
|
static int ttynvt_read_avail(ttynvt_t * tty)
|
|
{
|
|
int read_avail;
|
|
|
|
pthread_mutex_lock(&tty->tty_lock);
|
|
|
|
read_avail = tty->rxcnt;
|
|
|
|
pthread_mutex_unlock(&tty->tty_lock);
|
|
return read_avail;
|
|
}
|
|
|
|
static void *ttynvt_read_net(void *arg)
|
|
{
|
|
ttynvt_t *tty = (ttynvt_t *) arg;
|
|
ssize_t res;
|
|
int tn_res = -1;
|
|
|
|
pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
|
|
|
|
while (1)
|
|
{
|
|
res = poll(tty->fds, 1, -1);
|
|
if (res < 0)
|
|
{
|
|
if (errno == EINTR)
|
|
continue;
|
|
else
|
|
break;
|
|
}
|
|
else if (tty->fds[0].revents & (POLLERR | POLLHUP | POLLNVAL))
|
|
{
|
|
break;
|
|
}
|
|
else if (tty->fds[0].revents & POLLIN)
|
|
{
|
|
pthread_mutex_lock(&tty->tty_lock);
|
|
int read_avail = RX_BUF_SIZE - tty->blen;
|
|
|
|
if (read_avail == 0)
|
|
{
|
|
/*buffer overflow */
|
|
tty->blen = 0;
|
|
read_avail = RX_BUF_SIZE;
|
|
ttynvt_log(LOG_WARNING, "Rx buffer overflow\n");
|
|
}
|
|
res = read(tty->fds[0].fd, tty->buf + tty->blen, read_avail);
|
|
if (res <= 0)
|
|
{
|
|
pthread_mutex_unlock(&tty->tty_lock);
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
tty->blen += res;
|
|
|
|
tn_res = telnet_rx(tty->tn, tty->buf, &tty->blen);
|
|
if (tn_res == 0)
|
|
{
|
|
tty->rxcnt = tty->blen;
|
|
ttynvt_notify(tty);
|
|
}
|
|
pthread_mutex_unlock(&tty->tty_lock);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (res == 0)
|
|
ttynvt_log(LOG_INFO, "Connection closed by remote host\n");
|
|
else
|
|
ttynvt_log(LOG_ERR, "Rx read error:%s\n", strerror(errno));
|
|
|
|
tty->error = 1;
|
|
ttynvt_notify(tty);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int ttynvt_connect(const char *host, unsigned int port)
|
|
{
|
|
|
|
struct sockaddr_in serveraddr;
|
|
struct hostent *server;
|
|
|
|
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
|
|
|
|
if (sockfd < 0)
|
|
{
|
|
ttynvt_log(LOG_ERR, "Socket open failed: %s\n", strerror(errno));
|
|
return sockfd;
|
|
}
|
|
server = gethostbyname(host);
|
|
if (server == NULL)
|
|
{
|
|
ttynvt_log(LOG_ERR, "Cannot resolve host name: %s\n", strerror(errno));
|
|
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);
|
|
|
|
if (connect
|
|
(sockfd, (const struct sockaddr *)&serveraddr, sizeof(serveraddr)) < 0)
|
|
{
|
|
ttynvt_log(LOG_ERR, "Failed to connect to: %s:%u:%s\n",
|
|
host, port, strerror(errno));
|
|
return -1;
|
|
}
|
|
return sockfd;
|
|
}
|
|
|
|
int srv_write(void *cctx, const void *buf, int len)
|
|
{
|
|
ttynvt_t *tty = cctx;
|
|
|
|
return write(tty->fds[0].fd, buf, len);
|
|
}
|
|
|
|
int srv_read(void *cctx, int timeout)
|
|
{
|
|
ttynvt_t *tty = cctx;
|
|
int res;
|
|
|
|
while (1)
|
|
{
|
|
res = poll(tty->fds, 1, timeout);
|
|
if (res <= 0)
|
|
return res;
|
|
|
|
if (tty->fds[0].revents & POLLIN)
|
|
{
|
|
res = read(tty->fds[0].fd, tty->buf + tty->blen,
|
|
RX_BUF_SIZE - tty->blen);
|
|
if (res <= 0)
|
|
return res;
|
|
tty->blen += (size_t) res;
|
|
res = telnet_rx(tty->tn, tty->buf, &tty->blen);
|
|
if (res == 0)
|
|
{
|
|
tty->rxcnt = tty->blen;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
void modem_status_cb(void *cctx, int status)
|
|
{
|
|
ttynvt_t *tty = cctx;
|
|
|
|
tty->mcr &= ~(TIOCM_CD | TIOCM_RI | TIOCM_CTS | TIOCM_DSR);
|
|
|
|
ttynvt_log(LOG_INFO, "Modem status: 0x%02x\n", status);
|
|
|
|
if (status & TNS_STATE_CD)
|
|
tty->mcr |= TIOCM_CD;
|
|
if (status & TNS_STATE_RI)
|
|
tty->mcr |= TIOCM_RI;
|
|
if (status & TNS_STATE_CTS)
|
|
tty->mcr |= TIOCM_CTS;
|
|
if (status & TNS_STATE_DSR)
|
|
tty->mcr |= TIOCM_DSR;
|
|
}
|
|
|
|
static void ttynvt_open(fuse_req_t req, struct fuse_file_info *info)
|
|
{
|
|
ttynvt_t *tty;
|
|
socklen_t len;
|
|
int sockfd;
|
|
int res;
|
|
|
|
tty = calloc(1, sizeof(*tty));
|
|
if (!tty)
|
|
{
|
|
fuse_reply_err(req, ENOMEM);
|
|
return;
|
|
}
|
|
|
|
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 = ttynvt_connect(ttynvt_param.host, ttynvt_param.port);
|
|
if (sockfd < 0)
|
|
{
|
|
free(tty->tn);
|
|
free(tty);
|
|
fuse_reply_err(req, EFAULT);
|
|
return;
|
|
}
|
|
|
|
tty->fds[0].fd = sockfd;
|
|
tty->fds[0].events = POLLIN;
|
|
tty->blen = 0;
|
|
tty->rxcnt = 0;
|
|
|
|
res = telnet_open(tty->tn);
|
|
if (res < 0)
|
|
{
|
|
free(tty->tn);
|
|
free(tty);
|
|
fuse_reply_err(req, EBADE);
|
|
return;
|
|
}
|
|
|
|
len = sizeof(tty->txbuf_size);
|
|
if (getsockopt(sockfd, SOL_SOCKET, SO_SNDBUF, &tty->txbuf_size, &len) < 0)
|
|
{
|
|
free(tty->tn);
|
|
free(tty);
|
|
fuse_reply_err(req, EIO);
|
|
return;
|
|
}
|
|
|
|
if (pthread_mutex_init(&tty->tty_lock, NULL) < 0 ||
|
|
pthread_mutex_init(&tty->poll_lock, NULL) < 0 ||
|
|
pthread_cond_init(&tty->cond, NULL) < 0 ||
|
|
pthread_create(&tty->thread_id, NULL, &ttynvt_read_net, tty))
|
|
{
|
|
free(tty->tn);
|
|
free(tty);
|
|
fuse_reply_err(req, ENOMEM);
|
|
return;
|
|
}
|
|
|
|
info->fh = (uintptr_t) tty;
|
|
info->nonseekable = 1;
|
|
info->direct_io = 1;
|
|
|
|
ttynvt_log(LOG_INFO, "Open connection to server: %s:%d\n",
|
|
ttynvt_param.host, ttynvt_param.port);
|
|
|
|
fuse_reply_open(req, info);
|
|
}
|
|
|
|
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;
|
|
|
|
if (tty->error)
|
|
{
|
|
if (tty->epipe == 0)
|
|
{
|
|
ttynvt_log(LOG_WARNING, "Rx EPIPE\n");
|
|
tty->epipe = 1;
|
|
fuse_reply_buf(req, tty->buf, 0);
|
|
}
|
|
else
|
|
fuse_reply_err(req, EBADFD);
|
|
|
|
return;
|
|
}
|
|
|
|
pthread_mutex_lock(&tty->tty_lock);
|
|
if (tty->rxcnt == 0 && !(info->flags & O_NONBLOCK))
|
|
{
|
|
pthread_cond_wait(&tty->cond, &tty->tty_lock);
|
|
}
|
|
if (size > tty->rxcnt)
|
|
size = tty->rxcnt;
|
|
if (size > 0)
|
|
{
|
|
fuse_reply_buf(req, tty->buf, size);
|
|
memmove(tty->buf, tty->buf + size, tty->blen - size);
|
|
tty->blen -= size;
|
|
tty->rxcnt -= size;
|
|
}
|
|
else
|
|
{
|
|
fuse_reply_err(req, EINTR);
|
|
}
|
|
pthread_mutex_unlock(&tty->tty_lock);
|
|
}
|
|
|
|
static void
|
|
ttynvt_write(fuse_req_t req, const char *data, size_t size, off_t off,
|
|
struct fuse_file_info *info)
|
|
{
|
|
int res;
|
|
|
|
ttynvt_t *tty = (ttynvt_t *) (uintptr_t) info->fh;
|
|
|
|
if (size == 0)
|
|
{
|
|
fuse_reply_write(req, 0);
|
|
return;
|
|
}
|
|
|
|
if (tty->error)
|
|
{
|
|
fuse_reply_err(req, EPIPE);
|
|
return;
|
|
}
|
|
|
|
if (tty->nonblock || (info->flags & O_NONBLOCK))
|
|
{
|
|
size_t write_avail = ttynvt_write_avail(tty);
|
|
|
|
if (size > write_avail)
|
|
size = write_avail;
|
|
}
|
|
|
|
res = telnet_tx(tty->tn, data, size);
|
|
if (res < 0)
|
|
{
|
|
tty->error = 1;
|
|
fuse_reply_err(req, EPIPE);
|
|
return;
|
|
}
|
|
#if 0
|
|
else if (res == 0)
|
|
{
|
|
fuse_reply_err(req, EAGAIN);
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
fuse_reply_write(req, size);
|
|
}
|
|
|
|
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' };
|
|
|
|
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;
|
|
unsigned int tiocm = 0;
|
|
static unsigned int mcr = 0;
|
|
static struct termios tio = {
|
|
.c_cflag = B115200 | CS8 | PARODD | CREAD | CLOCAL,
|
|
};
|
|
int val;
|
|
|
|
switch (cmd)
|
|
{
|
|
case TCSETSF:
|
|
case TCSETSW:
|
|
case TCSETS:
|
|
if (!in_bufsz)
|
|
{
|
|
struct iovec iov = { arg, sizeof(struct termios) };
|
|
fuse_reply_ioctl_retry(req, &iov, 1, NULL, 0);
|
|
}
|
|
else
|
|
{
|
|
speed_t speed;
|
|
int csize, par, sb;
|
|
|
|
memcpy(&tio, in_buf, in_bufsz);
|
|
|
|
speed = cfgetospeed(&tio);
|
|
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 (!(mcr & TIOCM_DTR) && speed != B0)
|
|
{
|
|
mcr |= TIOCM_DTR;
|
|
telnet_rfc2217_ctl(tty->tn, TNS_CTL_DTR_ON);
|
|
}
|
|
|
|
ttynvt_log
|
|
(LOG_DEBUG,
|
|
"CFG ioctl: %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 ? '+' : '-');
|
|
|
|
fuse_reply_ioctl(req, 0, 0, 0);
|
|
}
|
|
break;
|
|
case TCGETS:
|
|
if (out_bufsz)
|
|
{
|
|
fuse_reply_ioctl(req, 0, &tio, sizeof(struct termio));
|
|
}
|
|
else
|
|
{
|
|
struct iovec iov = { arg, sizeof(struct termio) };
|
|
fuse_reply_ioctl_retry(req, NULL, 0, &iov, 1);
|
|
}
|
|
break;
|
|
|
|
case TIOCINQ: /* Get the number of bytes in the input
|
|
* buffer */
|
|
if (out_bufsz)
|
|
{
|
|
size_t rx_size = ttynvt_read_avail(tty);
|
|
|
|
fuse_reply_ioctl(req, 0, &rx_size, sizeof(size_t));
|
|
}
|
|
else
|
|
{
|
|
struct iovec iov = { arg, sizeof(size_t) };
|
|
fuse_reply_ioctl_retry(req, NULL, 0, &iov, 1);
|
|
}
|
|
|
|
break;
|
|
case TIOCOUTQ: /* Get the number of bytes in the output
|
|
* buffer */
|
|
if (out_bufsz)
|
|
{
|
|
size_t tx_size = ttynvt_write_avail(tty);
|
|
|
|
fuse_reply_ioctl(req, 0, &tx_size, sizeof(size_t));
|
|
}
|
|
else
|
|
{
|
|
struct iovec iov = { arg, sizeof(size_t) };
|
|
fuse_reply_ioctl_retry(req, NULL, 0, &iov, 1);
|
|
}
|
|
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);
|
|
pthread_mutex_lock(&tty->tty_lock);
|
|
tty->blen = 0;
|
|
pthread_mutex_unlock(&tty->tty_lock);
|
|
|
|
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:
|
|
if (out_bufsz)
|
|
{
|
|
fuse_reply_ioctl(req, 0, &mcr, sizeof(unsigned int));
|
|
}
|
|
else
|
|
{
|
|
struct iovec iov = { arg, sizeof(unsigned int) };
|
|
fuse_reply_ioctl_retry(req, NULL, 0, &iov, 1);
|
|
}
|
|
break;
|
|
case TIOCMBIS:
|
|
if (!in_bufsz)
|
|
{
|
|
struct iovec iov = { arg, sizeof(unsigned int) };
|
|
fuse_reply_ioctl_retry(req, &iov, 1, NULL, 0);
|
|
}
|
|
else
|
|
{
|
|
tiocm = *(unsigned int *)in_buf;
|
|
fuse_reply_ioctl(req, 0, 0, 0);
|
|
}
|
|
if (tiocm & TIOCM_RTS)
|
|
{
|
|
mcr |= TIOCM_RTS;
|
|
telnet_rfc2217_ctl(tty->tn, TNS_CTL_RTS_ON);
|
|
}
|
|
if (tiocm & TIOCM_DTR)
|
|
{
|
|
mcr |= TIOCM_DTR;
|
|
telnet_rfc2217_ctl(tty->tn, TNS_CTL_DTR_ON);
|
|
}
|
|
break;
|
|
case TIOCMBIC:
|
|
if (!in_bufsz)
|
|
{
|
|
struct iovec iov = { arg, sizeof(unsigned int) };
|
|
fuse_reply_ioctl_retry(req, &iov, 1, NULL, 0);
|
|
}
|
|
else
|
|
{
|
|
tiocm = *(unsigned int *)in_buf;
|
|
fuse_reply_ioctl(req, 0, 0, 0);
|
|
}
|
|
if (tiocm & TIOCM_RTS)
|
|
{
|
|
mcr &= ~TIOCM_RTS;
|
|
telnet_rfc2217_ctl(tty->tn, TNS_CTL_RTS_OFF);
|
|
}
|
|
if (tiocm & TIOCM_DTR)
|
|
{
|
|
mcr &= ~TIOCM_DTR;
|
|
telnet_rfc2217_ctl(tty->tn, TNS_CTL_DTR_OFF);
|
|
}
|
|
break;
|
|
case TIOCMSET:
|
|
if (!in_bufsz)
|
|
{
|
|
struct iovec iov = { arg, sizeof(unsigned int) };
|
|
fuse_reply_ioctl_retry(req, &iov, 1, NULL, 0);
|
|
}
|
|
else
|
|
{
|
|
tiocm = *(unsigned int *)in_buf;
|
|
fuse_reply_ioctl(req, 0, 0, 0);
|
|
|
|
ttynvt_log(LOG_DEBUG, "CFG ioctl: DTR=%c RTS=%c\n",
|
|
tiocm & TIOCM_DTR ? '1' : '0',
|
|
tiocm & TIOCM_RTS ? '1' : '0');
|
|
|
|
mcr &= ~(TIOCM_RTS | TIOCM_DTR);
|
|
mcr |= 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);
|
|
}
|
|
break;
|
|
case TCSBRK:
|
|
fuse_reply_ioctl(req, 0, 0, 0);
|
|
break;
|
|
case TIOCGWINSZ:
|
|
if (out_bufsz)
|
|
{
|
|
struct winsize ws = {.ws_row = 25,.ws_col = 80 };
|
|
fuse_reply_ioctl(req, 0, &ws, sizeof(struct winsize));
|
|
}
|
|
else
|
|
{
|
|
struct iovec iov = { arg, sizeof(struct winsize) };
|
|
fuse_reply_ioctl_retry(req, NULL, 0, &iov, 1);
|
|
}
|
|
break;
|
|
default:
|
|
fuse_reply_err(req, ENOSYS);
|
|
return;
|
|
}
|
|
}
|
|
|
|
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 = 0;
|
|
|
|
ttynvt_update_notify(tty, ph);
|
|
|
|
if (ttynvt_write_avail(tty) > 0)
|
|
revents |= POLLOUT;
|
|
if (ttynvt_read_avail(tty) > 0)
|
|
revents |= POLLIN;
|
|
if (tty->error)
|
|
{
|
|
if (tty->epipe == 0)
|
|
revents = POLLHUP | POLLIN;
|
|
else
|
|
revents = 0;
|
|
}
|
|
|
|
fuse_reply_poll(req, revents);
|
|
}
|
|
|
|
static const struct cuse_lowlevel_ops ttynvt_op = {
|
|
.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", debug, 0),
|
|
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", host, 0),
|
|
TTYNET_OPT("--server=%s", host, 0),
|
|
TTYNET_OPT("-p %u", port, 0),
|
|
TTYNET_OPT("--port=%u", port, 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\tEnable debug\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 name or IP address\n"
|
|
"\t-p port, --port=host port number\n" "\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:
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
int main(int argc, char *argv[])
|
|
{
|
|
struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
|
|
|
|
char dev_name[128] = { 0 };
|
|
const char *dev_info_argv[] = { dev_name };
|
|
|
|
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;
|
|
}
|
|
|
|
snprintf(dev_name, sizeof(dev_name), "DEVNAME=%s",
|
|
ttynvt_param.dev_name ? ttynvt_param.dev_name : "ttyNVT0");
|
|
|
|
struct cuse_info ci = {
|
|
.dev_major = ttynvt_param.major,
|
|
.dev_minor = ttynvt_param.minor,
|
|
.dev_info_argc = 1,
|
|
.dev_info_argv = dev_info_argv,
|
|
.flags = CUSE_UNRESTRICTED_IOCTL,
|
|
};
|
|
|
|
return cuse_lowlevel_main(args.argc, args.argv, &ci, &ttynvt_op, NULL);
|
|
}
|