Files
ttynvt/ttynvt.c
T
Kim Woelders 1ded3b11d2 ICS-664: Fix "cu problem"
In ttynvt_read(), the "interrupted" signal may be delivered after
installing the handler (fuse_req_interrupt_func()) but before calling
read().
If this happens the signal intended to be delivered while in the read()
function call (SIGUSR2) is handled before the read(), and we may hang
indefinitely.
2018-10-04 09:32:02 +02:00

1050 lines
27 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_SLAVE 1
#define FD_MASTER 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;
/* 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;
struct pollfd fds[3];
char net_buf[NET_BUF_SIZE];
size_t net_cnt;
char tmp_buf[TMP_BUF_SIZE];
struct termios tio;
int cmcr;
int epipe;
volatile int smcr;
volatile int error;
volatile int pollin;
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 volatile char _is_interrupted = 0;
static void sig_handler(int sig)
{
DBG("Interruped, signal = %d\n", sig);
_is_interrupted = 1;
}
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);
}
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");
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;
ssize_t res;
pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
while (1)
{
res = poll(tty->fds, 3, -1);
if (res < 0)
{
if (errno == EINTR)
continue;
else
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: %s\n", strerror(errno));
break;
}
else if (res == 0)
{
_log(LOG_INFO, "Connection closed by remote host\n");
break;
}
tty->net_cnt += res;
res = telnet_rx(tty->tn, tty->net_buf, &tty->net_cnt);
if (tty->net_cnt > 0 && res == 0)
{
res =
write(tty->fds[FD_MASTER].fd, tty->net_buf, tty->net_cnt);
if (res < 0)
{
_log(LOG_ERR, "master write error:%s\n", strerror(errno));
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:%s\n", strerror(errno));
break;
}
telnet_tx(tty->tn, tty->tmp_buf, res);
}
if (tty->fds[FD_SLAVE].revents & POLLIN)
{
tty->pollin = 1;
_notify(tty);
}
}
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[FD_NET].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[FD_NET], 1, timeout);
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);
if (res <= 0)
return res;
tty->net_cnt += (size_t) res;
res = telnet_rx(tty->tn, tty->net_buf, &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;
DBG2("open: thread:%lu\n", pthread_self());
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;
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;
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;
}
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:%s\n",
ttynvt_param.host, ttynvt_param.port, strerror(errno));
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;
if (tty->error)
{
DBG2("read (slave) in error state, thread:%lu\n", pthread_self());
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;
fuse_req_interrupt_func(req, NULL, NULL);
if (res < 0)
{
DBG2("read (slave) error: %s\n", strerror(errno));
fuse_reply_err(req, errno);
return;
}
DBG2("read (slave): size:%d, thread:%lu\n", res, pthread_self());
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;
ssize_t res;
if (size == 0)
{
fuse_reply_write(req, 0);
return;
}
if (tty->error)
{
fuse_reply_err(req, EPIPE);
return;
}
res = write(tty->fds[FD_SLAVE].fd, data, size);
if (res < 0)
{
DBG2("write (slave) error: %s\n", strerror(errno));
tty->error = 1;
fuse_reply_err(req, errno);
return;
}
DBG2("write (slave): size:%d, thread:%lu\n", (int)size, pthread_self());
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 winsize ws;
unsigned int tiocm = 0;
int mcr;
static struct termios tio = {
.c_cflag = B115200 | CS8 | PARODD | CREAD | CLOCAL,
};
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 = 0;
_update_notify(tty, ph);
if (tty->pollin)
{
tty->pollin = 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 = {
.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("-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;
}