979 lines
24 KiB
C
979 lines
24 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
|
|
|
|
#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;
|
|
/* Derived */
|
|
char host[64];
|
|
unsigned int port;
|
|
/* Picked up */
|
|
int foreground;
|
|
} ttynvt_param;
|
|
|
|
typedef struct {
|
|
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 excl;
|
|
|
|
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;
|
|
|
|
#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);
|
|
#if 0
|
|
vsyslog(prio, fmt, arg);
|
|
#else
|
|
vsnprintf(buf, sizeof(buf), fmt, arg);
|
|
syslog(prio, "[%d] %s", gettid(), buf);
|
|
#endif
|
|
va_end(arg);
|
|
}
|
|
|
|
#define DBG(...) \
|
|
if (ttynvt_param.debug > 0) _log(LOG_DEBUG, __VA_ARGS__)
|
|
#define DBG2(...) \
|
|
if (ttynvt_param.debug > 1) _log(LOG_DEBUG, __VA_ARGS__)
|
|
|
|
|
|
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");
|
|
|
|
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 _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 _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 _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 _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 *_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;
|
|
_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;
|
|
_notify(tty);
|
|
}
|
|
pthread_mutex_unlock(&tty->tty_lock);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (res == 0)
|
|
_log(LOG_INFO, "Connection closed by remote host\n");
|
|
else
|
|
_log(LOG_ERR, "Rx read error:%s\n", strerror(errno));
|
|
|
|
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: %s\n", strerror(errno));
|
|
return sockfd;
|
|
}
|
|
|
|
server = gethostbyname(host);
|
|
if (server == NULL)
|
|
{
|
|
_log(LOG_ERR, "Cannot resolve host name: %s\n", strerror(errno));
|
|
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:%s\n",
|
|
host, port, strerror(errno));
|
|
close(sockfd);
|
|
return -1;
|
|
}
|
|
|
|
return sockfd;
|
|
}
|
|
|
|
static int _srv_write(void *cctx, const void *buf, int len)
|
|
{
|
|
ttynvt_t *tty = cctx;
|
|
int res;
|
|
|
|
res = write(tty->fds[0].fd, buf, len);
|
|
|
|
return res;
|
|
}
|
|
|
|
static 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;
|
|
}
|
|
|
|
static void _modem_status_cb(void *cctx, int status)
|
|
{
|
|
ttynvt_t *tty = cctx;
|
|
|
|
tty->mcr &= ~(TIOCM_CD | TIOCM_RI | TIOCM_CTS | TIOCM_DSR);
|
|
|
|
_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 = _srv_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, &_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;
|
|
|
|
_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)
|
|
{
|
|
_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 = _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' };
|
|
|
|
#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);
|
|
/* Exclusive mode */
|
|
CASE(TIOCEXCL);
|
|
CASE(TIOCGEXCL);
|
|
CASE(TIOCNXCL);
|
|
/* Line discipline */
|
|
CASE(TIOCGETD);
|
|
CASE(TIOCSETD);
|
|
}
|
|
}
|
|
|
|
#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;
|
|
unsigned int tiocm = 0;
|
|
static unsigned int mcr = 0;
|
|
static struct termios tio = {
|
|
.c_cflag = B115200 | CS8 | PARODD | CREAD | CLOCAL,
|
|
};
|
|
int val;
|
|
|
|
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 = 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);
|
|
}
|
|
|
|
DBG("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:
|
|
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));
|
|
val = _read_avail(tty);
|
|
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));
|
|
val = _write_avail(tty);
|
|
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);
|
|
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:
|
|
CHECK_BUF_OUT(sizeof(int));
|
|
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)
|
|
{
|
|
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);
|
|
}
|
|
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)
|
|
{
|
|
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);
|
|
}
|
|
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);
|
|
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);
|
|
goto show_mctl;
|
|
show_mctl:
|
|
DBG("CFG ioctl: DTR=%c RTS=%c\n",
|
|
mcr & TIOCM_DTR ? '1' : '0', mcr & TIOCM_RTS ? '1' : '0');
|
|
break;
|
|
case TCSBRK:
|
|
fuse_reply_ioctl(req, 0, 0, 0);
|
|
break;
|
|
case TIOCGWINSZ:
|
|
CHECK_BUF_OUT(sizeof(struct winsize));
|
|
{
|
|
struct winsize ws = {.ws_row = 25,.ws_col = 80 };
|
|
fuse_reply_ioctl(req, 0, &ws, sizeof(struct winsize));
|
|
}
|
|
break;
|
|
|
|
case TIOCEXCL:
|
|
if (tty->excl)
|
|
{
|
|
fuse_reply_err(req, EBUSY);
|
|
}
|
|
else
|
|
{
|
|
tty->excl = 1;
|
|
fuse_reply_ioctl(req, 0, 0, 0);
|
|
}
|
|
break;
|
|
case TIOCGEXCL:
|
|
CHECK_BUF_OUT(sizeof(int));
|
|
val = tty->excl;
|
|
fuse_reply_ioctl(req, 0, &val, sizeof(int));
|
|
break;
|
|
case TIOCNXCL:
|
|
tty->excl = 0;
|
|
fuse_reply_ioctl(req, 0, 0, 0);
|
|
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 = 0;
|
|
|
|
_update_notify(tty, ph);
|
|
|
|
if (_write_avail(tty) > 0)
|
|
revents |= POLLOUT;
|
|
|
|
if (_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 %d", debug, 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-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");
|
|
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", 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;
|
|
}
|