Allow multiple clients to open the ttynvt device

This patch allows multiple clients to open the ttynvt device at the same time.
This is useful if an application wants to get/set termios (ioctls) while the
device is already opened by an other application.
This commit is contained in:
Frank Rolsted Jensen
2023-10-04 10:53:39 +02:00
parent 4fce33a5d7
commit 015da8a55e
+115 -31
View File
@@ -55,6 +55,9 @@ static struct ttynvt_param {
typedef struct {
pthread_mutex_t tty_lock;
pthread_mutex_t poll_lock;
pthread_mutex_t read_lock;
pthread_mutex_t write_lock;
pthread_mutex_t ioctl_lock;
pthread_t ptid_poll;
pthread_t ptid_read;
@@ -80,8 +83,18 @@ typedef struct {
int epipe;
tn_ctx_t *tn;
int seq;
struct ttynvt_cli_s *cli;
} ttynvt_t;
static ttynvt_t *ptty = NULL;
typedef struct ttynvt_cli_s {
int id;
ttynvt_t *tty;
struct ttynvt_cli_s *next;
} ttynvt_cli_t;
static pthread_mutex_t access_lock = PTHREAD_MUTEX_INITIALIZER;
@@ -406,7 +419,8 @@ static void _ttynvt_ctx_destroy(ttynvt_t * tty)
static void ttynvt_open(fuse_req_t req, struct fuse_file_info *info)
{
ttynvt_t *tty;
ttynvt_t *tty = ptty;
ttynvt_cli_t *tty_cli;
char *slave_name;
struct termios ios;
int fd;
@@ -415,14 +429,35 @@ static void ttynvt_open(fuse_req_t req, struct fuse_file_info *info)
pthread_mutex_lock(&access_lock);
nvt_log(LOG_INFO, "Connecting to server: %s\n", ttynvt_param.server);
tty = _ttynvt_ctx_create();
if (!tty)
tty_cli = calloc(1, sizeof(ttynvt_cli_t));
if (!tty_cli)
{
fuse_reply_err(req, ENOMEM);
return;
}
if (tty == NULL)
tty = _ttynvt_ctx_create();
if (!tty)
{
fuse_reply_err(req, ENOMEM);
free(tty_cli);
return;
}
tty_cli->id = tty->seq++;
tty_cli->tty = tty;
tty_cli->next = tty->cli;
tty->cli = tty_cli;
nvt_log(LOG_INFO, "Client open (%d)\n", tty_cli->id);
if (ptty)
goto out;
ptty = tty;
nvt_log(LOG_INFO, "Connecting to server: %s\n", ttynvt_param.server);
fd = socket_create_client(ttynvt_param.server);
if (fd < 0)
@@ -478,25 +513,32 @@ static void ttynvt_open(fuse_req_t req, struct fuse_file_info *info)
if ((res = pthread_mutex_init(&tty->tty_lock, NULL) < 0) ||
(res = pthread_mutex_init(&tty->poll_lock, NULL) < 0) ||
(res = pthread_mutex_init(&tty->ioctl_lock, NULL) < 0) ||
(res = pthread_mutex_init(&tty->read_lock, NULL) < 0) ||
(res = pthread_mutex_init(&tty->write_lock, NULL) < 0) ||
(res = pthread_create(&tty->ptid_poll, NULL, &_read_net, tty)))
{
errno = res;
goto open_err;
}
info->fh = (uintptr_t) tty;
nvt_log(LOG_INFO, "Connected to server: %s\n", ttynvt_param.server);
out:
info->fh = (uintptr_t) tty_cli;
info->nonseekable = 1;
info->direct_io = 1;
nvt_log(LOG_INFO, "Connected to server: %s\n", ttynvt_param.server);
fuse_reply_open(req, info);
pthread_mutex_unlock(&access_lock);
return;
open_err:
for (n = 0; n < 3; n++)
_fd_close(tty->fds[n].fd);
free(tty_cli);
_ttynvt_ctx_destroy(tty);
fuse_reply_err(req, errno);
@@ -511,10 +553,13 @@ 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;
ttynvt_cli_t *tty_cli = (ttynvt_cli_t *) (uintptr_t) info->fh;
ttynvt_t *tty = tty_cli->tty;
char buf[TMP_BUF_SIZE];
int res, nr;
pthread_mutex_lock(&tty->read_lock);
DBG2("%s\n", __func__);
if (tty->error)
@@ -529,7 +574,7 @@ ttynvt_read(fuse_req_t req, size_t size, off_t off,
else
fuse_reply_err(req, EBADFD);
return;
goto out;
}
if (size > TMP_BUF_SIZE)
@@ -556,36 +601,42 @@ ttynvt_read(fuse_req_t req, size_t size, off_t off,
{
DBG2("%s: error: %m\n", __func__);
fuse_reply_err(req, errno);
return;
goto out;
}
DBG2("%s: fd=%d: sz=%u/%u: '%.*s'\n", __func__,
tty->fds[FD_SLAVE].fd, res, (int)size, res, buf);
DBG2("%s: cli=%d fd=%d: sz=%u/%u: '%.*s'\n", __func__,
tty_cli->id, tty->fds[FD_SLAVE].fd, res, (int)size, res, buf);
fuse_reply_buf(req, buf, res);
out:
pthread_mutex_unlock(&tty->read_lock);
}
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;
ttynvt_cli_t *tty_cli = (ttynvt_cli_t *) (uintptr_t) info->fh;
ttynvt_t *tty = tty_cli->tty;
int res;
DBG2("%s: fd=%d: sz=%u: '%.*s'\n", __func__,
tty->fds[FD_SLAVE].fd, (int)size, (int)size, data);
pthread_mutex_lock(&tty->write_lock);
DBG2("%s: cli=%d fd=%d: sz=%u: '%.*s'\n", __func__,
tty_cli->id, tty->fds[FD_SLAVE].fd, (int)size, (int)size, data);
if (size == 0)
{
fuse_reply_write(req, 0);
return;
goto out;
}
if (tty->error)
{
DBG2("%s: tty->error=%d\n", __func__, tty->error);
fuse_reply_err(req, EPIPE);
return;
goto out;
}
res = write(tty->fds[FD_SLAVE].fd, data, size);
@@ -594,7 +645,7 @@ ttynvt_write(fuse_req_t req, const char *data, size_t size, off_t off,
DBG2("%s: error: %m\n", __func__);
tty->error = 1;
fuse_reply_err(req, errno);
return;
goto out;
}
else if (res != (int)size)
{
@@ -603,6 +654,9 @@ ttynvt_write(fuse_req_t req, const char *data, size_t size, off_t off,
}
fuse_reply_write(req, res);
out:
pthread_mutex_unlock(&tty->write_lock);
}
static int baudrate(speed_t speed)
@@ -780,7 +834,8 @@ 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;
ttynvt_cli_t *tty_cli = (ttynvt_cli_t *) (uintptr_t) info->fh;
ttynvt_t *tty = tty_cli->tty;
struct termios2 *tio = &tty->tio;
struct winsize ws;
unsigned int tiocm, baud;
@@ -790,14 +845,16 @@ ttynvt_ioctl(fuse_req_t req, int cmd, void *arg,
uint8_t byte;
uint32_t byte4;
DBG("%s: cmd=%s arg=%p ibuf=%p:%u obuf=:%u\n", __func__,
_ioctl_name(cmd), arg, in_buf,
pthread_mutex_lock(&tty->ioctl_lock);
DBG("%s: cli=%d cmd=%s arg=%p ibuf=%p:%u obuf=:%u\n", __func__,
tty_cli->id, _ioctl_name(cmd), arg, in_buf,
(unsigned int)in_bufsz, (unsigned int)out_bufsz);
if (ttynvt_param.raw)
{
fuse_reply_ioctl(req, 0, 0, 0);
return;
goto out;
}
switch (cmd)
@@ -988,7 +1045,7 @@ ttynvt_ioctl(fuse_req_t req, int cmd, void *arg,
if (res < 0)
{
fuse_reply_err(req, errno);
return;
goto out;
}
fuse_reply_ioctl(req, 0, &ws, sizeof(struct winsize));
break;
@@ -999,7 +1056,7 @@ ttynvt_ioctl(fuse_req_t req, int cmd, void *arg,
if (res < 0)
{
fuse_reply_err(req, errno);
return;
goto out;
}
fuse_reply_ioctl(req, 0, &ws, sizeof(struct winsize));
break;
@@ -1060,17 +1117,21 @@ ttynvt_ioctl(fuse_req_t req, int cmd, void *arg,
}
break;
}
out:
pthread_mutex_unlock(&tty->ioctl_lock);
}
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;
ttynvt_cli_t *tty_cli = (ttynvt_cli_t *) (uintptr_t) info->fh;
ttynvt_t *tty = tty_cli->tty;
int revents = 0;
DBG2("%s: tty->pollin=%d tty->error/epipe=%d/%d\n",
__func__, tty->pollin, tty->error, tty->epipe);
DBG2("%s: cli=%d, tty->pollin=%d tty->error/epipe=%d/%d\n",
__func__, tty_cli->id, tty->pollin, tty->error, tty->epipe);
_update_notify(tty, ph);
@@ -1097,16 +1158,38 @@ ttynvt_poll(fuse_req_t req, struct fuse_file_info *info,
static void ttynvt_release(fuse_req_t req, struct fuse_file_info *info)
{
ttynvt_t *tty = (ttynvt_t *) (uintptr_t) info->fh;
ttynvt_cli_t *tty_cli = (ttynvt_cli_t *) (uintptr_t) info->fh;
ttynvt_t *tty = tty_cli->tty;
ttynvt_cli_t *tmp, *prev;
DBG("%s\n", __func__);
pthread_mutex_lock(&access_lock);
for (tmp = tty->cli, prev = NULL; tmp; prev = tmp, tmp = tmp->next)
if (tmp == tty_cli)
{
nvt_log(LOG_INFO, "Client release (%d)\n", tmp->id);
if (prev)
prev->next = tmp->next;
else
tty->cli = tmp->next;
free(tmp);
break;
}
if (tty->cli)
goto out;
tty->rel_pending = 1;
pthread_cancel(tty->ptid_poll);
pthread_join(tty->ptid_poll, NULL);
pthread_mutex_destroy(&tty->tty_lock);
pthread_mutex_destroy(&tty->poll_lock);
pthread_mutex_destroy(&tty->ioctl_lock);
pthread_mutex_destroy(&tty->read_lock);
pthread_mutex_destroy(&tty->write_lock);
if (ttynvt_param.close_delay_us)
_ttynvt_sleep_us(ttynvt_param.close_delay_us);
@@ -1120,10 +1203,11 @@ static void ttynvt_release(fuse_req_t req, struct fuse_file_info *info)
_ttynvt_ctx_destroy(tty);
fuse_reply_err(req, 0);
nvt_log(LOG_INFO, "Disconnected from server: %s\n", ttynvt_param.server);
out:
fuse_reply_err(req, 0);
pthread_mutex_unlock(&access_lock);
}