diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/uvt.h | 274 | ||||
-rw-r--r-- | src/uvt_cdev.c | 485 | ||||
-rw-r--r-- | src/uvt_client.c | 1120 | ||||
-rw-r--r-- | src/uvt_ctx.c | 168 | ||||
-rw-r--r-- | src/uvt_internal.h | 118 | ||||
-rw-r--r-- | src/uvt_tty_null.c | 135 |
6 files changed, 2300 insertions, 0 deletions
diff --git a/src/uvt.h b/src/uvt.h new file mode 100644 index 0000000..e767005 --- /dev/null +++ b/src/uvt.h @@ -0,0 +1,274 @@ +/* + * UVT - Userspace Virtual Terminals + * + * Copyright (c) 2011-2013 David Herrmann <dh.herrmann@gmail.com> + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/* + * Userspace Virtual Terminals + * Virtual terminals were historically implemented in the kernel via a + * character-device. This layer provides a user-space implementation via + * CUSE/FUSE that can be used to provide the same API from user-space. + */ + +#ifndef UVT_H +#define UVT_H + +#include <inttypes.h> +#include <linux/kd.h> +#include <linux/vt.h> +#include <stdbool.h> +#include <stdlib.h> +#include <termio.h> +#include <termios.h> + +/* UVT types */ + +struct uvt_client; +struct uvt_cdev; +struct uvt_ctx; + +/* TTYs */ + +enum uvt_tty_event_type { + UVT_TTY_HUP = 0x01, + UVT_TTY_READ = 0x02, + UVT_TTY_WRITE = 0x04, +}; + +struct uvt_tty_event { + unsigned int type; +}; + +typedef void (*uvt_tty_cb) (void *tty, struct uvt_tty_event *ev, void *data); + +struct uvt_tty_ops { + void (*ref) (void *data); + void (*unref) (void *data); + int (*register_cb) (void *data, uvt_tty_cb cb, void *cb_data); + void (*unregister_cb) (void *data, uvt_tty_cb cb, void *cb_data); + + int (*read) (void *data, uint8_t *mem, size_t len); + int (*write) (void *data, const uint8_t *mem, size_t len); + unsigned int (*poll) (void *data); +}; + +/* virtual terminals */ + +enum uvt_vt_event_type { + UVT_VT_HUP = 0x01, + UVT_VT_TTY = 0x02, +}; + +struct uvt_vt_event { + unsigned int type; + + union { + struct uvt_tty_event tty; + }; +}; + +typedef void (*uvt_vt_cb) (void *vt, struct uvt_vt_event *ev, void *data); + +struct uvt_vt_ops { + void (*ref) (void *data); + void (*unref) (void *data); + int (*register_cb) (void *data, uvt_vt_cb cb, void *cb_data); + void (*unregister_cb) (void *data, uvt_vt_cb cb, void *cb_data); + + int (*read) (void *data, uint8_t *mem, size_t len); + int (*write) (void *data, const uint8_t *mem, size_t len); + unsigned int (*poll) (void *data); + + /* TTY ioctls */ + int (*ioctl_TCFLSH) (void *data, unsigned long arg); + + /* VT ioctls */ + int (*ioctl_VT_ACTIVATE) (void *data, unsigned long arg); + int (*ioctl_VT_WAITACTIVE) (void *data, unsigned long arg); + int (*ioctl_VT_GETSTATE) (void *data, struct vt_stat *arg); + int (*ioctl_VT_OPENQRY) (void *data, unsigned int *arg); + int (*ioctl_VT_GETMODE) (void *data, struct vt_mode *arg); + int (*ioctl_VT_SETMODE) (void *data, const struct vt_mode *arg, + pid_t pid); + int (*ioctl_VT_RELDISP) (void *data, unsigned long arg); + int (*ioctl_KDGETMODE) (void *data, unsigned int *arg); + int (*ioctl_KDSETMODE) (void *data, unsigned int arg); + int (*ioctl_KDGKBMODE) (void *data, unsigned int *arg); + int (*ioctl_KDSKBMODE) (void *data, unsigned int arg); + +/* + Complete list of all ioctls that the kernel supports. The internal handler + returns -EOPNOTSUPP for all of them as they haven't been implemented, yet. + We need to check if they are actually required or whether it's not worth the + effort. + Please implement them only if you know a client that requires them. Also + consider implementing them as a no-op if the client doesn't depend on the + call to actually do something. We want to keep the actual callbacks at a + minimum. + + TTY ioctls + + int (*ioctl_TIOCPKT) (void *data, ...); + int (*ioctl_TCXONC) (void *data, ...); + int (*ioctl_TCGETS) (void *data, struct termios *arg); + int (*ioctl_TCSETS) (void *data, const struct termios *arg); + int (*ioctl_TCSETSF) (void *data, const struct termios *arg); + int (*ioctl_TCSETSW) (void *data, const struct termios *arg); + int (*ioctl_TCGETA) (void *data, ...); + int (*ioctl_TCSETA) (void *data, ...); + int (*ioctl_TCSETAF) (void *data, ...); + int (*ioctl_TCSETAW) (void *data, ...); + int (*ioctl_TIOCGLCKTRMIOS) (void *data, ...); + int (*ioctl_TIOCSLCKTRMIOS) (void *data, ...); + int (*ioctl_TCGETX) (void *data, ...); + int (*ioctl_TCSETX) (void *data, ...); + int (*ioctl_TCSETXW) (void *data, ...); + int (*ioctl_TCSETXF) (void *data, ...); + int (*ioctl_TIOCGSOFTCAR) (void *data, ...); + int (*ioctl_TIOCSSOFTCAR) (void *data, ...); + + VT ioctls + + int (*ioctl_TIOCLINUX) (void *data, ...); + int (*ioctl_KIOCSOUND) (void *data, ...); + int (*ioctl_KDMKTONE) (void *data, ...); + int (*ioctl_KDGKBTYPE) (void *data, char *arg); + int (*ioctl_KDADDIO) (void *data, unsigned long arg); + int (*ioctl_KDDELIO) (void *data, unsigned long arg); + int (*ioctl_KDENABIO) (void *data); + int (*ioctl_KDDISABIO) (void *data); + int (*ioctl_KDKBDREP) (void *data, struct kbd_repeat *arg); + int (*ioctl_KDMAPDISP) (void *data); + int (*ioctl_KDUNMAPDISP) (void *data); + int (*ioctl_KDGKBMETA) (void *data, long *arg); + int (*ioctl_KDSKBMETA) (void *data, long arg); + int (*ioctl_KDGETKEYCODE) (void *data, ...); + int (*ioctl_KDSETKEYCODE) (void *data, ...); + int (*ioctl_KDGKBENT) (void *data, ...); + int (*ioctl_KDSKBENT) (void *data, ...); + int (*ioctl_KDGKBSENT) (void *data, ...); + int (*ioctl_KDSKBSENT) (void *data, ...); + int (*ioctl_KDGKBDIACR) (void *data, ...); + int (*ioctl_KDSKBDIACR) (void *data, ...); + int (*ioctl_KDGKBDIACRUC) (void *data, ...); + int (*ioctl_KDSKBDIACRUC) (void *data, ...); + int (*ioctl_KDGETLED) (void *data, char *arg); + int (*ioctl_KDSETLED) (void *data, long arg); + int (*ioctl_KDGKBLED) (void *data, char *arg); + int (*ioctl_KDSKBLED) (void *data, long arg); + int (*ioctl_KDSIGACCEPT) (void *data, ...); + int (*ioctl_VT_SETACTIVATE) (void *data, ...); + int (*ioctl_VT_DISALLOCATE) (void *data, ...); + int (*ioctl_VT_RESIZE) (void *data, ...); + int (*ioctl_VT_RESIZEX) (void *data, ...); + int (*ioctl_GIO_FONT) (void *data, ...); + int (*ioctl_PIO_FONT) (void *data, ...); + int (*ioctl_GIO_CMAP) (void *data, ...); + int (*ioctl_PIO_CMAP) (void *data, ...); + int (*ioctl_GIO_FONTX) (void *data, ...); + int (*ioctl_PIO_FONTX) (void *data, ...); + int (*ioctl_PIO_FONTRESET) (void *data, ...); + int (*ioctl_KDFONTOP) (void *data, ...); + int (*ioctl_GIO_SCRNMAP) (void *data, ...); + int (*ioctl_PIO_SCRNMAP) (void *data, ...); + int (*ioctl_GIO_UNISCRNMAP) (void *data, ...); + int (*ioctl_PIO_UNISCRNMAP) (void *data, ...); + int (*ioctl_PIO_UNIMAPCLR) (void *data, ...); + int (*ioctl_GIO_UNIMAP) (void *data, ...); + int (*ioctl_PIO_UNIMAP) (void *data, ...); + int (*ioctl_VT_LOCKSWITCH) (void *data); + int (*ioctl_VT_UNLOCKSWITCH) (void *data); + int (*ioctl_VT_GETHIFONTMASK) (void *data, ...); + int (*ioctl_VT_WAITEVENT) (void *data, ...); +*/ +}; + +/* client sessions */ + +void uvt_client_ref(struct uvt_client *client); +void uvt_client_unref(struct uvt_client *client); + +int uvt_client_set_vt(struct uvt_client *client, const struct uvt_vt_ops *vt, + void *vt_data); +void uvt_client_kill(struct uvt_client *client); +bool uvt_client_is_dead(struct uvt_client *client); + +/* character devices */ + +enum uvt_cdev_event_type { + UVT_CDEV_HUP, + UVT_CDEV_OPEN, +}; + +struct uvt_cdev_event { + unsigned int type; + + union { + struct uvt_client *client; + }; +}; + +typedef void (*uvt_cdev_cb) (struct uvt_cdev *cdev, + struct uvt_cdev_event *ev, + void *data); + +int uvt_cdev_new(struct uvt_cdev **out, struct uvt_ctx *ctx, + const char *name, unsigned int major, unsigned int minor); +void uvt_cdev_ref(struct uvt_cdev *cdev); +void uvt_cdev_unref(struct uvt_cdev *cdev); + +int uvt_cdev_register_cb(struct uvt_cdev *cdev, uvt_cdev_cb cb, void *data); +void uvt_cdev_unregister_cb(struct uvt_cdev *cdev, uvt_cdev_cb cb, void *data); + +/* contexts */ + +typedef void (*uvt_log_t) (void *data, + const char *file, + int line, + const char *func, + const char *subs, + unsigned int sev, + const char *format, + va_list args); + +int uvt_ctx_new(struct uvt_ctx **out, uvt_log_t log, void *log_data); +void uvt_ctx_ref(struct uvt_ctx *ctx); +void uvt_ctx_unref(struct uvt_ctx *ctx); + +int uvt_ctx_get_fd(struct uvt_ctx *ctx); +void uvt_ctx_dispatch(struct uvt_ctx *ctx); + +unsigned int uvt_ctx_get_major(struct uvt_ctx *ctx); +int uvt_ctx_new_minor(struct uvt_ctx *ctx, unsigned int *out); +void uvt_ctx_free_minor(struct uvt_ctx *ctx, unsigned int minor); + +/* pty tty implementation */ + +struct uvt_tty_null; +extern const struct uvt_tty_ops uvt_tty_null_ops; + +int uvt_tty_null_new(struct uvt_tty_null **out, struct uvt_ctx *ctx); +void uvt_tty_null_ref(struct uvt_tty_null *tty); +void uvt_tty_null_unref(struct uvt_tty_null *tty); + +#endif /* UVT_H */ diff --git a/src/uvt_cdev.c b/src/uvt_cdev.c new file mode 100644 index 0000000..291857b --- /dev/null +++ b/src/uvt_cdev.c @@ -0,0 +1,485 @@ +/* + * UVT - Userspace Virtual Terminals + * + * Copyright (c) 2011-2013 David Herrmann <dh.herrmann@gmail.com> + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/* + * Character Devices + * This implements a VT character device entry point via the CUSE API. It does + * not implement the VT API on top of the character-device (cdev) but only + * provides the entry point. It is up to the user to bind open-files to VT and + * client objects. + */ + +#include <errno.h> +#include <fcntl.h> +#include <linux/major.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include "shl_dlist.h" +#include "shl_hook.h" +#include "shl_llog.h" +#include "uvt.h" +#include "uvt_internal.h" + +#include <fuse/fuse.h> +#include <fuse/fuse_common.h> +#include <fuse/fuse_lowlevel.h> +#include <fuse/fuse_opt.h> +#include <fuse/cuse_lowlevel.h> + +#define LLOG_SUBSYSTEM "uvt_cdev" + +/* + * FUSE low-level ops + * This implements all the file-system operations on the character-device. It + * is important that we handle interrupts correctly (ENOENT) and never loose + * any data. This is all single threaded as it is not performance critical at + * all. + * We simply dispatch each call to uvt_client as this implements all the + * client-session related operations. + */ + +static void ll_open(fuse_req_t req, struct fuse_file_info *fi) +{ + struct uvt_cdev *cdev = fuse_req_userdata(req); + struct uvt_client *client; + struct uvt_cdev_event ev; + int ret; + + ret = uvt_client_ll_open(&client, cdev, req, fi); + if (ret) + return; + + memset(&ev, 0, sizeof(ev)); + ev.type = UVT_CDEV_OPEN; + ev.client = client; + shl_hook_call(cdev->hook, cdev, &ev); +} + +static void ll_destroy(void *data) { + struct uvt_cdev *cdev = data; + struct uvt_client *client; + + /* on unexpected shutdown this kills all open clients */ + while (!shl_dlist_empty(&cdev->clients)) { + client = shl_dlist_entry(cdev->clients.next, + struct uvt_client, list); + uvt_client_kill(client); + uvt_client_unref(client); + } +} + +static const struct cuse_lowlevel_ops ll_ops = { + .init = NULL, + .destroy = ll_destroy, + .open = ll_open, + .release = uvt_client_ll_release, + .read = uvt_client_ll_read, + .write = uvt_client_ll_write, + .poll = uvt_client_ll_poll, + .ioctl = uvt_client_ll_ioctl, + .flush = NULL, + .fsync = NULL, +}; + +/* + * FUSE channel ops + * The connection to the FUSE kernel module is done via a file-descriptor. + * Writing to it is synchronous, so the commands that we write are + * _immediately_ executed and return the result to us. Furthermore, write() + * is always non-blocking and always succeeds so no reason to watch for + * EAGAIN. Reading from the FD, on the other hand, may block if there is no + * data available so we mark it as O_NONBLOCK. The kernel maintains + * an event-queue that we read from. So there may be pending events that we + * haven't read but which affect the calls that we write to the kernel. This + * is important when handling interrupts. + * chan_receive() and chan_send() handle I/O to the kernel module and are + * hooked up into a fuse-channel. + */ + +static int chan_receive(struct fuse_chan **chp, char *buf, size_t size) +{ + struct fuse_chan *ch = *chp; + struct uvt_cdev *cdev = fuse_chan_data(ch); + struct fuse_session *se = fuse_chan_session(ch); + int fd = fuse_chan_fd(ch); + ssize_t res; + + if (!se || !cdev) + return -EINVAL; + + if (!size) + return 0; + +restart: + if (fuse_session_exited(se)) + return 0; + + res = read(fd, buf, size); + if (!res) { + /* EOF on cuse file */ + llog_error(cdev, "fuse channel shut down on cdev %p", cdev); + fuse_session_exit(se); + return 0; + } else if (res < 0) { + /* ENOENT is returned if the operation was interrupted, it's + * safe to restart */ + if (errno == ENOENT) + goto restart; + + /* ENODEV is returned if the FS got unmounted. This shouldn't + * occur for CUSE devices. Anyway, exit if this happens. */ + if (errno == ENODEV) { + llog_error(cdev, "fuse channel unmounted on cdev %p", + cdev); + fuse_session_exit(se); + return 0; + } + + /* EINTR and EAGAIN are simply forwarded to the caller. */ + if (errno == EINTR || errno == EAGAIN) + return -errno; + + cdev->error = -errno; + llog_error(cdev, "fuse channel read error on cdev %p (%d): %m", + cdev, errno); + fuse_session_exit(se); + return cdev->error; + } + + return res; +} + +static int chan_send(struct fuse_chan *ch, const struct iovec iov[], + size_t count) +{ + struct uvt_cdev *cdev = fuse_chan_data(ch); + struct fuse_session *se = fuse_chan_session(ch); + int fd = fuse_chan_fd(ch); + int ret; + + if (!cdev || !se) + return -EINVAL; + if (!iov || !count) + return 0; + + ret = writev(fd, iov, count); + if (ret < 0) { + /* ENOENT is returned on interrupts */ + if (!fuse_session_exited(se) && errno != ENOENT) { + cdev->error = -errno; + llog_error(cdev, "cannot write to fuse-channel on cdev %p (%d): %m", + cdev, errno); + fuse_session_exit(se); + } + return cdev->error; + } + + return 0; +} + +static const struct fuse_chan_ops chan_ops = { + .receive = chan_receive, + .send = chan_send, + .destroy = NULL, +}; + +/* + * Character Device + * This creates the high-level character-device driver and registers a + * fake-session that is used to control each character file. + * channel_event() is a callback when I/O is possible on the FUSE FD and + * performs all outstanding tasks. + * On error, the fake-session is unregistered and deleted. This also stops all + * client sessions, obviously. + */ + +static void uvt_cdev_hup(struct uvt_cdev *cdev, int error) +{ + struct uvt_cdev_event ev; + + ev_eloop_rm_fd(cdev->efd); + cdev->efd = NULL; + cdev->error = error; + + memset(&ev, 0, sizeof(ev)); + ev.type = UVT_CDEV_HUP; + + shl_hook_call(cdev->hook, cdev, &ev); +} + +static void channel_event(struct ev_fd *fd, int mask, void *data) +{ + struct uvt_cdev *cdev = data; + int ret; + struct fuse_buf buf; + struct fuse_chan *ch; + struct shl_dlist *iter; + struct uvt_client *client; + + if (!(mask & EV_READABLE)) { + if (mask & (EV_HUP | EV_ERR)) { + llog_error(cdev, "HUP/ERR on fuse channel on cdev %p", + cdev); + uvt_cdev_hup(cdev, -EPIPE); + } + + return; + } + + memset(&buf, 0, sizeof(buf)); + buf.mem = cdev->buf; + buf.size = cdev->bufsize; + ch = cdev->channel; + ret = fuse_session_receive_buf(cdev->session, &buf, &ch); + if (ret == -EINTR || ret == -EAGAIN) { + return; + } else if (ret < 0) { + llog_error(cdev, "fuse channel read error on cdev %p: %d", + cdev, ret); + uvt_cdev_hup(cdev, ret); + return; + } + + fuse_session_process_buf(cdev->session, &buf, ch); + if (fuse_session_exited(cdev->session)) { + llog_error(cdev, "fuse session exited on cdev %p", cdev); + uvt_cdev_hup(cdev, cdev->error ? : -EFAULT); + return; + } + + /* Readers can get interrupted asynchronously. Due to heavy locking + * inside of FUSE, we cannot release them right away. So cleanup all + * killed readers after we processed all buffers. */ + shl_dlist_for_each(iter, &cdev->clients) { + client = shl_dlist_entry(iter, struct uvt_client, list); + uvt_client_cleanup(client); + } +} + +static int uvt_cdev_init(struct uvt_cdev *cdev, const char *name, + unsigned int major, unsigned int minor) +{ + const char *dev_info_argv[1]; + struct cuse_info ci; + size_t bufsize; + char *nparam; + int ret; + + /* TODO: libfuse makes sure that fd 0, 1 and 2 are available as + * standard streams, otherwise they fail. This is awkward and we + * should check whether this is really needed and _why_? + * If it is needed, fix upstream to stop that crazy! */ + + if (!major) + major = TTY_MAJOR; + + if (!major || major > 255) { + llog_error(cdev, "invalid major %u on cdev %p", + major, cdev); + return -EINVAL; + } + if (!minor) { + llog_error(cdev, "invalid minor %u on cdev %p", + minor, cdev); + return -EINVAL; + } + if (!name || !*name) { + llog_error(cdev, "empty name on cdev %p", + cdev); + return -EINVAL; + } + + llog_info(cdev, "creating device /dev/%s %u:%u on cdev %p", + name, major, minor, cdev); + + ret = asprintf(&nparam, "DEVNAME=%s", name); + if (ret <= 0) + return llog_ENOMEM(cdev); + + dev_info_argv[0] = nparam; + memset(&ci, 0, sizeof(ci)); + ci.dev_major = major; + ci.dev_minor = minor; + ci.dev_info_argc = 1; + ci.dev_info_argv = dev_info_argv; + ci.flags = CUSE_UNRESTRICTED_IOCTL; + + cdev->session = cuse_lowlevel_new(NULL, &ci, &ll_ops, cdev); + free(nparam); + + if (!cdev->session) { + llog_error(cdev, "cannot create fuse-ll session on cdev %p", + cdev); + return -ENOMEM; + } + + cdev->fd = open(cdev->ctx->cuse_file, O_RDWR | O_CLOEXEC | O_NONBLOCK); + if (cdev->fd < 0) { + llog_error(cdev, "cannot open cuse-file %s on cdev %p (%d): %m", + cdev->ctx->cuse_file, cdev, errno); + ret = -EFAULT; + goto err_session; + } + + bufsize = getpagesize() + 0x1000; + if (bufsize < 0x21000) + bufsize = 0x21000; + + cdev->bufsize = bufsize; + cdev->buf = malloc(bufsize); + if (!cdev->buf) { + ret = llog_ENOMEM(cdev); + goto err_fd; + } + + /* Argh! libfuse does not use "const" for the "chan_ops" pointer so we + * actually have to cast it. Their implementation does not write into it + * so we can safely use a constant storage for it. + * TODO: Fix libfuse upstream! */ + cdev->channel = fuse_chan_new((void*)&chan_ops, cdev->fd, bufsize, + cdev); + if (!cdev->channel) { + llog_error(cdev, "cannot allocate fuse-channel on cdev %p", + cdev); + ret = -ENOMEM; + goto err_buf; + } + + ret = ev_eloop_new_fd(cdev->ctx->eloop, &cdev->efd, cdev->fd, + EV_READABLE, channel_event, cdev); + if (ret) + goto err_chan; + + fuse_session_add_chan(cdev->session, cdev->channel); + return 0; + +err_chan: + fuse_chan_destroy(cdev->channel); +err_buf: + free(cdev->buf); +err_fd: + close(cdev->fd); +err_session: + fuse_session_destroy(cdev->session); + return ret; +} + +static void uvt_cdev_destroy(struct uvt_cdev *cdev) +{ + if (cdev->error) + llog_warning(cdev, "cdev %p failed with error %d", + cdev, cdev->error); + + fuse_session_destroy(cdev->session); + ev_eloop_rm_fd(cdev->efd); + free(cdev->buf); + close(cdev->fd); +} + +SHL_EXPORT +int uvt_cdev_new(struct uvt_cdev **out, struct uvt_ctx *ctx, + const char *name, unsigned int major, unsigned int minor) +{ + struct uvt_cdev *cdev; + int ret; + + if (!ctx) + return -EINVAL; + if (!out) + return llog_EINVAL(ctx); + + cdev = malloc(sizeof(*cdev)); + if (!cdev) + return llog_ENOMEM(ctx); + memset(cdev, 0, sizeof(*cdev)); + cdev->ref = 1; + cdev->ctx = ctx; + cdev->llog = ctx->llog; + cdev->llog_data = ctx->llog_data; + shl_dlist_init(&cdev->clients); + + llog_debug(cdev, "new cdev %p on ctx %p", cdev, cdev->ctx); + + ret = shl_hook_new(&cdev->hook); + if (ret) + goto err_free; + + ret = uvt_cdev_init(cdev, name, major, minor); + if (ret) + goto err_hook; + + uvt_ctx_ref(cdev->ctx); + *out = cdev; + return 0; + +err_hook: + shl_hook_free(cdev->hook); +err_free: + free(cdev); + return ret; +} + +SHL_EXPORT +void uvt_cdev_ref(struct uvt_cdev *cdev) +{ + if (!cdev || !cdev->ref) + return; + + ++cdev->ref; +} + +SHL_EXPORT +void uvt_cdev_unref(struct uvt_cdev *cdev) +{ + if (!cdev || !cdev->ref || --cdev->ref) + return; + + llog_debug(cdev, "free cdev %p", cdev); + + uvt_cdev_destroy(cdev); + shl_hook_free(cdev->hook); + uvt_ctx_unref(cdev->ctx); + free(cdev); +} + +SHL_EXPORT +int uvt_cdev_register_cb(struct uvt_cdev *cdev, uvt_cdev_cb cb, void *data) +{ + if (!cdev) + return -EINVAL; + + return shl_hook_add_cast(cdev->hook, cb, data, false); +} + +SHL_EXPORT +void uvt_cdev_unregister_cb(struct uvt_cdev *cdev, uvt_cdev_cb cb, void *data) +{ + if (!cdev) + return; + + shl_hook_rm_cast(cdev->hook, cb, data); +} diff --git a/src/uvt_client.c b/src/uvt_client.c new file mode 100644 index 0000000..574001e --- /dev/null +++ b/src/uvt_client.c @@ -0,0 +1,1120 @@ +/* + * UVT - Userspace Virtual Terminals + * + * Copyright (c) 2011-2013 David Herrmann <dh.herrmann@gmail.com> + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/* + * Client Sessions + * A client session represents the internal object that corresponds to a single + * open-file in the kernel. That is, for each user calling open() on a cdev, we + * create a client-session in UVT. + * Note that multiple client-sessions can share the same VT object. It is up to + * the API user to assign clients to the correct VTs. You can even move clients + * from one VT to another. + * On the other hand, user-space can have multiple FDs open for a single + * client-session similar to how they can have multiple FDs for a single + * open-file. + */ + +#include <errno.h> +#include <fcntl.h> +#include <linux/kd.h> +#include <linux/vt.h> +#include <signal.h> +#include <stdlib.h> +#include <string.h> +#include <sys/epoll.h> +#include <termio.h> +#include <termios.h> +#include <unistd.h> +#include "shl_dlist.h" +#include "shl_llog.h" +#include "shl_misc.h" +#include "uvt.h" +#include "uvt_internal.h" + +#define LLOG_SUBSYSTEM "uvt_client" + +/* + * Blocking Waiters + * I/O has always two modes: blocking and nonblocking + * Nonblocking I/O is easy. We simply check whether we can actually forward the + * data. If we can't, we signal that back. However, blocking I/O is a lot more + * complex to implement. If a user submits a blocking I/O call, we have to wait + * until we can finish that request. In the kernel we simply put the user + * context asleep until we call can finish. However, in user-space via FUSE we + * have no user-context. Instead, we need to work around that. + * The most straightforward way would be to create a thread and put that thread + * asleep. However, this would create one thread for every blocking I/O call + * which seems to be way too much overhead. Also, we don't want threads in a + * library. Therefore, we use a different approach. + * For each blocking request, we create a uvt_waiter. This waiter is then linked + * into the waiter list and we continue with other requests. Everytime the I/O + * status changes, we retry the whole waiter list and try to finish the + * requests. If a request is done, we signal it back and destroy the waiter. + * This gets slightly more complex with interrupts and fuse_req objects. See + * below for the implementation. + */ + +enum uvt_waiter_type { + UVT_WAITER_INVALID = 0x00, + + UVT_WAITER_READ = 0x01, + UVT_WAITER_WRITE = 0x02, + + UVT_WAITER_ALL = UVT_WAITER_READ | + UVT_WAITER_WRITE, +}; + +enum uvt_waiter_flags { + UVT_WAITER_KILLED = 0x01, + UVT_WAITER_RELEASED = 0x02, +}; + +struct uvt_waiter { + struct shl_dlist list; + struct uvt_client *client; + unsigned int flags; + fuse_req_t req; + + unsigned int type; + + union { + struct { + size_t size; + uint8_t *buf; + } read; + + struct { + size_t size; + uint8_t *buf; + } write; + }; +}; + +static bool uvt_waiter_is_killed(struct uvt_waiter *waiter) +{ + return !waiter || (waiter->flags & UVT_WAITER_KILLED); +} + +static void uvt_waiter_set_killed(struct uvt_waiter *waiter) +{ + if (waiter) + waiter->flags |= UVT_WAITER_KILLED; +} + +static bool uvt_waiter_is_released(struct uvt_waiter *waiter) +{ + return !waiter || (waiter->flags & UVT_WAITER_RELEASED); +} + +static void uvt_waiter_set_released(struct uvt_waiter *waiter) +{ + if (waiter) + waiter->flags |= UVT_WAITER_RELEASED; +} + +static void uvt_waiter_interrupt(fuse_req_t req, void *data) +{ + struct uvt_waiter *waiter = data; + + uvt_waiter_set_killed(waiter); +} + +static int uvt_waiter_new(struct uvt_waiter **out, struct uvt_client *client, + fuse_req_t req) +{ + struct uvt_waiter *waiter; + + if (!client->vt) + return -EPIPE; + if (fuse_req_interrupted(req)) + return -ENOENT; + + waiter = malloc(sizeof(*waiter)); + if (!waiter) + return -ENOMEM; + memset(waiter, 0, sizeof(*waiter)); + waiter->client = client; + waiter->flags = 0; + waiter->req = req; + + fuse_req_interrupt_func(req, uvt_waiter_interrupt, waiter); + if (uvt_waiter_is_killed(waiter)) { + fuse_req_interrupt_func(req, NULL, NULL); + free(waiter); + return -ENOENT; + } + + shl_dlist_link_tail(&client->waiters, &waiter->list); + *out = waiter; + return 0; +} + +static int uvt_waiter_new_read(struct uvt_waiter **out, + struct uvt_client *client, fuse_req_t req, + uint8_t *buf, size_t size) +{ + struct uvt_waiter *waiter; + int ret; + + if (!size) + return -EINVAL; + + ret = uvt_waiter_new(&waiter, client, req); + if (ret) + return ret; + waiter->type = UVT_WAITER_READ; + waiter->read.size = size; + waiter->read.buf = buf; + + *out = waiter; + return 0; +} + +static int uvt_waiter_new_write(struct uvt_waiter **out, + struct uvt_client *client, fuse_req_t req, + const uint8_t *mem, size_t size) +{ + struct uvt_waiter *waiter; + uint8_t *buf; + int ret; + + if (!size) + return -EINVAL; + + buf = malloc(size); + if (!buf) + return -ENOMEM; + memcpy(buf, mem, size); + + ret = uvt_waiter_new(&waiter, client, req); + if (ret) + goto err_free; + waiter->type = UVT_WAITER_WRITE; + waiter->write.size = size; + waiter->write.buf = buf; + + *out = waiter; + return 0; + +err_free: + free(buf); + return ret; +} + +static void uvt_waiter_release(struct uvt_waiter *waiter, int error) +{ + if (!waiter || uvt_waiter_is_released(waiter)) + return; + + uvt_waiter_set_released(waiter); + fuse_req_interrupt_func(waiter->req, NULL, NULL); + if (error) + fuse_reply_err(waiter->req, abs(error)); +} + +static void uvt_waiter_free(struct uvt_waiter *waiter, int error) +{ + shl_dlist_unlink(&waiter->list); + uvt_waiter_release(waiter, error); + + switch (waiter->type) { + case UVT_WAITER_READ: + free(waiter->read.buf); + break; + case UVT_WAITER_WRITE: + free(waiter->write.buf); + break; + } + + free(waiter); +} + +static void uvt_waiter_free_read(struct uvt_waiter *waiter, size_t len) +{ + if (!waiter) + return; + + if (!uvt_waiter_is_released(waiter)) { + uvt_waiter_release(waiter, 0); + fuse_reply_buf(waiter->req, (void*)waiter->read.buf, len); + } + uvt_waiter_free(waiter, -EINVAL); +} + +static void uvt_waiter_free_write(struct uvt_waiter *waiter, size_t len) +{ + if (!waiter) + return; + + if (!uvt_waiter_is_released(waiter)) { + uvt_waiter_release(waiter, 0); + fuse_reply_write(waiter->req, len); + } + uvt_waiter_free(waiter, -EINVAL); +} + +/* + * Client Sessions + * A client session is the user-space counterpart of kernel-space open-files. + * For each open-file we have one client-session in user-space. Users can access + * a single client-session via multiple file-descriptors via dup(). However, for + * each open() call on the device, we create a new open-file, that is, a new + * client-session. + * A single client session dispatches all the I/O calls on the file. It does + * blocking and nonblocking I/O, parses ioctls() and correctly performs any + * other state-tracking. But it does not implement any device logic. That means, + * the client-session doesn't provide any functionality. Instead, you have to + * assign a VT to the session. The client-session performs any maintenance tasks + * and then forwards the requests to the VT object. If no VT object is assigned, + * the user gets ENODEV as error. + * Because the client-session performs all state-tracking and parsing, the VT + * object can be a lot simpler and doesn't have to be aware of any FUSE objects + * or sessions. Instead, the VT object can concentrate on implementing a _VT_ + * and nothing more. + * Furthermore, this allows to assign the same VT object to multiple different + * sessions at the same time. Or to assign a different VT to each session on the + * same device, or any other combination you want. + */ + +static void uvt_client_waiters_retry(struct uvt_client *client, + unsigned int types); + +static int uvt_client_new(struct uvt_client **out, struct uvt_cdev *cdev) +{ + struct uvt_client *client; + + if (!cdev) + return -EINVAL; + if (!out) + return llog_EINVAL(cdev); + + client = malloc(sizeof(*client)); + if (!client) + return llog_ENOMEM(cdev); + memset(client, 0, sizeof(*client)); + client->ref = 1; + client->cdev = cdev; + client->llog = cdev->llog; + client->llog_data = cdev->llog_data; + shl_dlist_init(&client->waiters); + + llog_debug(client, "new client %p on cdev %p", client, cdev); + + shl_dlist_link_tail(&cdev->clients, &client->list); + *out = client; + return 0; +} + +SHL_EXPORT +void uvt_client_ref(struct uvt_client *client) +{ + if (!client || !client->ref) + return; + + ++client->ref; +} + +SHL_EXPORT +void uvt_client_unref(struct uvt_client *client) +{ + if (!client || !client->ref || --client->ref) + return; + + llog_debug(client, "free client %p", client); + + uvt_client_kill(client); + free(client); +} + +/* + * This must be called after each event dispatch round. It cleans up all + * interrupted/killed readers. The readers cannot be released right away due + * to heavy locking inside of FUSE. We have to delay these tasks and clean up + * after each dispatch round. + */ +void uvt_client_cleanup(struct uvt_client *client) +{ + struct shl_dlist *i, *tmp; + struct uvt_waiter *waiter; + + if (!client) + return; + + shl_dlist_for_each_safe(i, tmp, &client->waiters) { + waiter = shl_dlist_entry(i, struct uvt_waiter, list); + if (uvt_waiter_is_killed(waiter)) + uvt_waiter_free(waiter, -ENOENT); + } +} + +static void uvt_client_waiters_release(struct uvt_client *client, int error) +{ + struct uvt_waiter *waiter; + int err; + + if (!client) + return; + + while (!shl_dlist_empty(&client->waiters)) { + waiter = shl_dlist_entry(client->waiters.next, + struct uvt_waiter, list); + + if (uvt_waiter_is_killed(waiter)) + err = -ENOENT; + else + err = error; + + uvt_waiter_free(waiter, err); + } +} + +SHL_EXPORT +bool uvt_client_is_dead(struct uvt_client *client) +{ + return !client || !client->cdev; +} + +SHL_EXPORT +void uvt_client_kill(struct uvt_client *client) +{ + if (!client || !client->cdev) + return; + + llog_debug(client, "kill client %p", client); + + if (client->ph) { + fuse_notify_poll(client->ph); + fuse_pollhandle_destroy(client->ph); + client->ph = NULL; + } + + shl_dlist_unlink(&client->list); + client->cdev = NULL; + uvt_client_set_vt(client, NULL, NULL); + uvt_client_waiters_release(client, -EPIPE); +} + +/* + * We allow recursive VT-actions so we need sophisticated locking. That is, we + * allow each client->vt->XY() function to itself raise VT events. These VT + * events cause our uvt_client_vt_event() handler to call + * uvt_client_waiters_retry(). But uvt_client_waiters_retry() itself can call + * VT functions again. + * This recursion isn't particularly bad, as any _proper_ implementation would + * have an upper limit (which is the number of active waiters). However, to + * avoid wasting stack space for recursion, we lock the VT when calling VT + * callbacks. The uvt_client_vt_event() handler checks whether the callbacks are + * currently locked and sets markers otherwise. These markers cause our + * unlock-function to notice that we got events in between and then retries all + * interrupted operations. + * The client->vt_in_unlock is used to avoid recursion in unlock() itself. + */ + +static bool uvt_client_lock_vt(struct uvt_client *client) +{ + if (!client || client->vt_locked) + return false; + + client->vt_locked = true; + return true; +} + +static void uvt_client_unlock_vt(struct uvt_client *client) +{ + unsigned int retry; + + if (!client || !client->vt_locked) + return; + + client->vt_locked = false; + if (client->vt_in_unlock) + return; + + while (client->vt_retry) { + retry = client->vt_retry; + client->vt_retry = 0; + + client->vt_in_unlock = true; + uvt_client_waiters_retry(client, retry); + client->vt_in_unlock = false; + } +} + +static void uvt_client_waiters_retry(struct uvt_client *client, + unsigned int types) +{ + struct shl_dlist *iter, *tmp; + struct uvt_waiter *waiter; + int ret; + + if (!client || !types || uvt_client_is_dead(client) || !client->vt) + return; + + if (!uvt_client_lock_vt(client)) + return; + + shl_dlist_for_each_safe(iter, tmp, &client->waiters) { + if (!types) + break; + + waiter = shl_dlist_entry(iter, struct uvt_waiter, list); + if (!(waiter->type & types) || uvt_waiter_is_killed(waiter)) + continue; + + if (waiter->type == UVT_WAITER_READ) { + ret = client->vt->read(client->vt_data, + waiter->read.buf, + waiter->read.size); + if (ret == -EAGAIN) { + types &= ~UVT_WAITER_READ; + continue; + } else if (ret < 0) { + uvt_waiter_free(waiter, ret); + } else { + if (ret > waiter->read.size) + ret = waiter->read.size; + uvt_waiter_free_read(waiter, ret); + } + } else if (waiter->type == UVT_WAITER_WRITE) { + ret = client->vt->write(client->vt_data, + waiter->write.buf, + waiter->write.size); + if (ret == -EAGAIN) { + types &= ~UVT_WAITER_WRITE; + continue; + } else if (ret < 0) { + uvt_waiter_free(waiter, ret); + } else { + if (ret > waiter->write.size) + ret = waiter->write.size; + uvt_waiter_free_write(waiter, ret); + } + } + } + + uvt_client_unlock_vt(client); +} + +static void uvt_client_vt_event(void *vt, struct uvt_vt_event *ev, void *data) +{ + struct uvt_client *client = data; + + if (uvt_client_is_dead(client)) + return; + + switch (ev->type) { + case UVT_VT_HUP: + uvt_client_kill(client); + break; + case UVT_VT_TTY: + switch (ev->tty.type) { + case UVT_TTY_HUP: + uvt_client_kill(client); + break; + case UVT_TTY_READ: + if (client->ph) + fuse_notify_poll(client->ph); + client->vt_retry |= UVT_WAITER_READ; + break; + case UVT_TTY_WRITE: + if (client->ph) + fuse_notify_poll(client->ph); + client->vt_retry |= UVT_WAITER_WRITE; + break; + } + break; + } + + uvt_client_waiters_retry(client, client->vt_retry); +} + +SHL_EXPORT +int uvt_client_set_vt(struct uvt_client *client, const struct uvt_vt_ops *vt, + void *vt_data) +{ + int ret; + + if (!client) + return -EINVAL; + if (uvt_client_is_dead(client) && vt) + return -EINVAL; + + if (client->vt) { + client->vt->unregister_cb(client->vt_data, uvt_client_vt_event, + client); + client->vt->unref(client->vt_data); + } + + client->vt = vt; + client->vt_data = vt_data; + + if (client->vt) { + ret = client->vt->register_cb(client->vt_data, + uvt_client_vt_event, client); + if (!ret) { + client->vt->ref(client->vt_data); + uvt_client_waiters_retry(client, UVT_WAITER_ALL); + return 0; + } + } else { + ret = 0; + } + + client->vt = NULL; + client->vt_data = NULL; + uvt_client_waiters_release(client, -ENODEV); + return ret; +} + +/* + * Internal FUSE low-level fops implementation + * These functions implement the callbacks used by the CUSE/FUSE-ll + * implementation in uvt_cdev objects. Our infrastructure allows to provide + * other callbacks, too, but this is currently not needed. Moreover, I cannot + * see any reason to add them to the public API as nobody would want anything + * different than CUSE/FUSE as frontend. + */ + +int uvt_client_ll_open(struct uvt_client **out, struct uvt_cdev *cdev, + fuse_req_t req, struct fuse_file_info *fi) +{ + struct uvt_client *client; + int ret; + + ret = uvt_client_new(&client, cdev); + if (ret) { + fuse_reply_err(req, -ret); + return ret; + } + + fi->fh = (uint64_t)(uintptr_t)(void*)client; + fi->nonseekable = 1; + fi->direct_io = 1; + ret = fuse_reply_open(req, fi); + if (ret < 0) { + uvt_client_kill(client); + uvt_client_unref(client); + return -EFAULT; + } + + *out = client; + return 0; +} + +void uvt_client_ll_release(fuse_req_t req, struct fuse_file_info *fi) +{ + struct uvt_client *client = (void*)(uintptr_t)fi->fh; + + if (!client) { + fuse_reply_err(req, EINVAL); + return; + } + + uvt_client_kill(client); + uvt_client_unref(client); + fuse_reply_err(req, 0); +} + +void uvt_client_ll_read(fuse_req_t req, size_t size, off_t off, + struct fuse_file_info *fi) +{ + struct uvt_client *client = (void*)(uintptr_t)fi->fh; + struct uvt_waiter *waiter; + uint8_t *buf; + int ret; + + if (!client) { + fuse_reply_err(req, EINVAL); + return; + } else if (uvt_client_is_dead(client)) { + fuse_reply_err(req, EPIPE); + return; + } else if (off) { + fuse_reply_err(req, EINVAL); + return; + } else if (!size) { + fuse_reply_buf(req, "", 0); + return; + } else if (!client->vt) { + fuse_reply_err(req, ENODEV); + return; + } + + buf = malloc(size); + if (!buf) { + fuse_reply_err(req, ENOMEM); + return; + } + + ret = client->vt->read(client->vt_data, buf, size); + if (ret >= 0) { + if (ret > size) + ret = size; + + fuse_reply_buf(req, (void*)buf, ret); + free(buf); + return; + } else if (ret == -EAGAIN && !(fi->flags & O_NONBLOCK)) { + ret = uvt_waiter_new_read(&waiter, client, req, buf, size); + if (!ret) + return; + } + + fuse_reply_err(req, -ret); + free(buf); +} + +void uvt_client_ll_write(fuse_req_t req, const char *buf, size_t size, + off_t off, struct fuse_file_info *fi) +{ + struct uvt_client *client = (void*)(uintptr_t)fi->fh; + struct uvt_waiter *waiter; + int ret; + + if (!client) { + fuse_reply_err(req, EINVAL); + return; + } else if (uvt_client_is_dead(client)) { + fuse_reply_err(req, EPIPE); + return; + } else if (off) { + fuse_reply_err(req, EINVAL); + return; + } else if (!size) { + fuse_reply_write(req, 0); + return; + } else if (!client->vt) { + fuse_reply_err(req, ENODEV); + return; + } + + ret = client->vt->write(client->vt_data, (void*)buf, size); + if (ret >= 0) { + if (ret > size) + ret = size; + + fuse_reply_write(req, ret); + return; + } else if (ret == -EAGAIN && !(fi->flags & O_NONBLOCK)) { + ret = uvt_waiter_new_write(&waiter, client, req, (void*)buf, + size); + if (!ret) + return; + } + + fuse_reply_err(req, -ret); +} + +void uvt_client_ll_poll(fuse_req_t req, struct fuse_file_info *fi, + struct fuse_pollhandle *ph) +{ + struct uvt_client *client = (void*)(uintptr_t)fi->fh; + unsigned int flags, fl; + + if (!client) { + fuse_reply_err(req, EINVAL); + return; + } else if (uvt_client_is_dead(client)) { + if (ph) + fuse_pollhandle_destroy(ph); + fuse_reply_poll(req, EPOLLHUP | EPOLLIN | EPOLLOUT | + EPOLLWRNORM | EPOLLRDNORM); + return; + } + + if (client->ph) + fuse_pollhandle_destroy(client->ph); + client->ph = ph; + + if (!client->vt) { + fuse_reply_err(req, ENODEV); + return; + } + + flags = 0; + fl = client->vt->poll(client->vt_data); + if (fl & UVT_TTY_HUP) + flags |= EPOLLHUP; + if (fl & UVT_TTY_READ) + flags |= EPOLLIN | EPOLLRDNORM; + if (fl & UVT_TTY_WRITE) + flags |= EPOLLOUT | EPOLLWRNORM; + + fuse_reply_poll(req, flags); +} + +static bool ioctl_param(fuse_req_t req, void *arg, size_t in_want, + size_t in_have, size_t out_want, size_t out_have) +{ + bool retry; + struct iovec in, out; + size_t in_num, out_num; + + retry = false; + memset(&in, 0, sizeof(in)); + in_num = 0; + memset(&out, 0, sizeof(out)); + out_num = 0; + + if (in_want) { + if (!in_have) { + retry = true; + } else if (in_have < in_want) { + fuse_reply_err(req, EFAULT); + return true; + } + + in.iov_base = arg; + in.iov_len = in_want; + in_num = 1; + } + if (out_want) { + if (!out_have) { + retry = true; + } else if (out_have < out_want) { + fuse_reply_err(req, EFAULT); + return true; + } + + out.iov_base = arg; + out.iov_len = out_want; + out_num = 1; + } + + if (retry) + fuse_reply_ioctl_retry(req, in_num ? &in : NULL, in_num, + out_num ? &out : NULL, out_num); + return retry; +} + +void uvt_client_ll_ioctl(fuse_req_t req, int cmd, void *arg, + struct fuse_file_info *fi, unsigned int flags, + const void *in_buf, size_t in_bufsz, size_t out_bufsz) +{ + struct uvt_client *client = (void*)(uintptr_t)fi->fh; + uintptr_t uarg = (uintptr_t)arg; + bool compat; + int ret; + struct vt_stat vtstat; + struct vt_mode vtmode; + unsigned int uval; + + if (!client) { + fuse_reply_err(req, EINVAL); + return; + } else if (uvt_client_is_dead(client)) { + fuse_reply_err(req, EPIPE); + return; + } else if (!client->vt) { + fuse_reply_err(req, ENODEV); + return; + } + + /* TODO: fix compat-ioctls */ + compat = !!(flags & FUSE_IOCTL_COMPAT); + if (compat) { + fuse_reply_err(req, EOPNOTSUPP); + return; + } + + switch (cmd) { + + /* TTY ioctls */ + + case TCFLSH: + if (ioctl_param(req, arg, 0, in_bufsz, 0, out_bufsz)) + return; + if (!client->vt->ioctl_TCFLSH) { + fuse_reply_err(req, EOPNOTSUPP); + } else { + ret = client->vt->ioctl_TCFLSH(client->vt_data, + (unsigned long)uarg); + if (ret) + fuse_reply_err(req, abs(ret)); + else + fuse_reply_ioctl(req, 0, NULL, 0); + } + break; + + case TIOCPKT: + case TCXONC: + case TCGETS: + case TCSETS: + case TCSETSF: + case TCSETSW: + case TCGETA: + case TCSETA: + case TCSETAF: + case TCSETAW: + case TIOCGLCKTRMIOS: + case TIOCSLCKTRMIOS: + case TCGETX: + case TCSETX: + case TCSETXW: + case TCSETXF: + case TIOCGSOFTCAR: + case TIOCSSOFTCAR: + fuse_reply_err(req, EOPNOTSUPP); + break; + + /* VT ioctls */ + + case VT_ACTIVATE: + if (ioctl_param(req, arg, 0, in_bufsz, 0, out_bufsz)) + return; + if (!client->vt->ioctl_VT_ACTIVATE) { + fuse_reply_err(req, EOPNOTSUPP); + } else { + ret = client->vt->ioctl_VT_ACTIVATE(client->vt_data, + (unsigned long)uarg); + if (ret) + fuse_reply_err(req, abs(ret)); + else + fuse_reply_ioctl(req, 0, NULL, 0); + } + break; + + case VT_WAITACTIVE: + if (ioctl_param(req, arg, 0, in_bufsz, 0, out_bufsz)) + return; + if (!client->vt->ioctl_VT_WAITACTIVE) { + fuse_reply_err(req, EOPNOTSUPP); + } else { + ret = client->vt->ioctl_VT_WAITACTIVE(client->vt_data, + (unsigned long)uarg); + if (ret) + fuse_reply_err(req, abs(ret)); + else + fuse_reply_ioctl(req, 0, NULL, 0); + } + break; + + case VT_GETSTATE: + if (ioctl_param(req, arg, 0, in_bufsz, + sizeof(struct vt_stat), out_bufsz)) + return; + if (!client->vt->ioctl_VT_GETSTATE) { + fuse_reply_err(req, EOPNOTSUPP); + } else { + memset(&vtstat, 0, sizeof(vtstat)); + ret = client->vt->ioctl_VT_GETSTATE(client->vt_data, + &vtstat); + if (ret) + fuse_reply_err(req, abs(ret)); + else + fuse_reply_ioctl(req, 0, &vtstat, + sizeof(vtstat)); + } + break; + + case VT_OPENQRY: + if (ioctl_param(req, arg, 0, in_bufsz, + sizeof(unsigned int), out_bufsz)) + return; + if (!client->vt->ioctl_VT_OPENQRY) { + fuse_reply_err(req, EOPNOTSUPP); + } else { + uval = 0; + ret = client->vt->ioctl_VT_OPENQRY(client->vt_data, + &uval); + if (ret) + fuse_reply_err(req, abs(ret)); + else + fuse_reply_ioctl(req, 0, &uval, sizeof(uval)); + } + break; + + case VT_GETMODE: + if (ioctl_param(req, arg, 0, in_bufsz, + sizeof(struct vt_mode), out_bufsz)) + return; + if (!client->vt->ioctl_VT_GETMODE) { + fuse_reply_err(req, EOPNOTSUPP); + } else { + memset(&vtmode, 0, sizeof(vtmode)); + ret = client->vt->ioctl_VT_GETMODE(client->vt_data, + &vtmode); + if (ret) + fuse_reply_err(req, abs(ret)); + else + fuse_reply_ioctl(req, 0, &vtmode, + sizeof(vtmode)); + } + break; + + case VT_SETMODE: + if (ioctl_param(req, arg, sizeof(struct vt_mode), in_bufsz, + 0, out_bufsz)) + return; + if (!client->vt->ioctl_VT_SETMODE) { + fuse_reply_err(req, EOPNOTSUPP); + } else { + ret = client->vt->ioctl_VT_SETMODE(client->vt_data, + (const struct vt_mode*)in_buf, + fuse_req_ctx(req)->pid); + if (ret) + fuse_reply_err(req, abs(ret)); + else + fuse_reply_ioctl(req, 0, NULL, 0); + } + break; + + case VT_RELDISP: + if (ioctl_param(req, arg, 0, in_bufsz, 0, out_bufsz)) + return; + if (!client->vt->ioctl_VT_RELDISP) { + fuse_reply_err(req, EOPNOTSUPP); + } else { + ret = client->vt->ioctl_VT_RELDISP(client->vt_data, + (unsigned long)uarg); + if (ret) + fuse_reply_err(req, abs(ret)); + else + fuse_reply_ioctl(req, 0, NULL, 0); + } + break; + + case KDGETMODE: + if (ioctl_param(req, arg, 0, in_bufsz, + sizeof(unsigned int), out_bufsz)) + return; + if (!client->vt->ioctl_KDGETMODE) { + fuse_reply_err(req, EOPNOTSUPP); + } else { + uval = 0; + ret = client->vt->ioctl_KDGETMODE(client->vt_data, + &uval); + if (ret) + fuse_reply_err(req, abs(ret)); + else + fuse_reply_ioctl(req, 0, &uval, sizeof(uval)); + } + break; + + case KDSETMODE: + if (ioctl_param(req, arg, 0, in_bufsz, 0, out_bufsz)) + return; + if (!client->vt->ioctl_KDSETMODE) { + fuse_reply_err(req, EOPNOTSUPP); + } else { + ret = client->vt->ioctl_KDSETMODE(client->vt_data, + (unsigned int)uarg); + if (ret) + fuse_reply_err(req, abs(ret)); + else + fuse_reply_ioctl(req, 0, NULL, 0); + } + break; + + case KDGKBMODE: + if (ioctl_param(req, arg, 0, in_bufsz, + sizeof(unsigned int), out_bufsz)) + return; + if (!client->vt->ioctl_KDGKBMODE) { + fuse_reply_err(req, EOPNOTSUPP); + } else { + uval = 0; + ret = client->vt->ioctl_KDGKBMODE(client->vt_data, + &uval); + if (ret) + fuse_reply_err(req, abs(ret)); + else + fuse_reply_ioctl(req, 0, &uval, sizeof(uval)); + } + break; + + case KDSKBMODE: + if (ioctl_param(req, arg, 0, in_bufsz, 0, out_bufsz)) + return; + if (!client->vt->ioctl_KDSKBMODE) { + fuse_reply_err(req, EOPNOTSUPP); + } else { + ret = client->vt->ioctl_KDSKBMODE(client->vt_data, + (unsigned int)uarg); + if (ret) + fuse_reply_err(req, abs(ret)); + else + fuse_reply_ioctl(req, 0, NULL, 0); + } + break; + + case TIOCLINUX: + case KIOCSOUND: + case KDMKTONE: + case KDGKBTYPE: + case KDADDIO: + case KDDELIO: + case KDENABIO: + case KDDISABIO: + case KDKBDREP: + case KDMAPDISP: + case KDUNMAPDISP: + case KDGKBMETA: + case KDSKBMETA: + case KDGETKEYCODE: + case KDSETKEYCODE: + case KDGKBENT: + case KDSKBENT: + case KDGKBSENT: + case KDSKBSENT: + case KDGKBDIACR: + case KDSKBDIACR: + case KDGKBDIACRUC: + case KDSKBDIACRUC: + case KDGETLED: + case KDSETLED: + case KDGKBLED: + case KDSKBLED: + case KDSIGACCEPT: + case VT_SETACTIVATE: + case VT_DISALLOCATE: + case VT_RESIZE: + case VT_RESIZEX: + case GIO_FONT: + case PIO_FONT: + case GIO_CMAP: + case PIO_CMAP: + case GIO_FONTX: + case PIO_FONTX: + case PIO_FONTRESET: + case KDFONTOP: + case GIO_SCRNMAP: + case PIO_SCRNMAP: + case GIO_UNISCRNMAP: + case PIO_UNISCRNMAP: + case PIO_UNIMAPCLR: + case GIO_UNIMAP: + case PIO_UNIMAP: + case VT_LOCKSWITCH: + case VT_UNLOCKSWITCH: + case VT_GETHIFONTMASK: + case VT_WAITEVENT: + fuse_reply_err(req, EOPNOTSUPP); + break; + default: + fuse_reply_err(req, EINVAL); + break; + } +} diff --git a/src/uvt_ctx.c b/src/uvt_ctx.c new file mode 100644 index 0000000..ad1e534 --- /dev/null +++ b/src/uvt_ctx.c @@ -0,0 +1,168 @@ +/* + * UVT - Userspace Virtual Terminals + * + * Copyright (c) 2011-2013 David Herrmann <dh.herrmann@gmail.com> + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/* + * UVT Contexts + * A UVT context is used to provide basic infrastructure for all other UVT + * objects. It allows easy integration of multiple UVT objects into a single + * application. + */ + +#include <eloop.h> +#include <errno.h> +#include <fcntl.h> +#include <linux/major.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include "shl_array.h" +#include "shl_flagset.h" +#include "shl_llog.h" +#include "shl_misc.h" +#include "uvt.h" +#include "uvt_internal.h" + +#define LLOG_SUBSYSTEM "uvt_ctx" + +SHL_EXPORT +int uvt_ctx_new(struct uvt_ctx **out, uvt_log_t log, void *log_data) +{ + struct uvt_ctx *ctx; + int ret; + + if (!out) + return llog_dEINVAL(log, log_data); + + ctx = malloc(sizeof(*ctx)); + if (!ctx) + return llog_dENOMEM(log, log_data); + memset(ctx, 0, sizeof(*ctx)); + ctx->ref = 1; + ctx->llog = log; + ctx->llog_data = log_data; + + /* Default major/minor uses the TTY_MAJOR number with an offset of 2^15 + * to avoid ID-clashes with any in-kernel TTY driver. As kernel drivers + * use static IDs only, a lower number would be fine, too, but lets be + * safe and just use high numbers. */ + ctx->major = TTY_MAJOR; + ctx->minor_offset = 16384; + + llog_debug(ctx, "new ctx %p", ctx); + + ret = ev_eloop_new(&ctx->eloop, ctx->llog, ctx->llog_data); + if (ret) + goto err_free; + + ctx->cuse_file = strdup("/dev/cuse"); + if (!ctx->cuse_file) { + ret = llog_ENOMEM(ctx); + goto err_eloop; + } + + ret = shl_flagset_new(&ctx->minors); + if (ret) + goto err_file; + + *out = ctx; + return 0; + +err_file: + free(ctx->cuse_file); +err_eloop: + ev_eloop_unref(ctx->eloop); +err_free: + free(ctx); + return ret; +} + +SHL_EXPORT +void uvt_ctx_ref(struct uvt_ctx *ctx) +{ + if (!ctx || !ctx->ref) + return; + + ++ctx->ref; +} + +SHL_EXPORT +void uvt_ctx_unref(struct uvt_ctx *ctx) +{ + if (!ctx || !ctx->ref || --ctx->ref) + return; + + llog_debug(ctx, "free ctx %p", ctx); + + shl_flagset_free(ctx->minors); + free(ctx->cuse_file); + ev_eloop_unref(ctx->eloop); + free(ctx); +} + +SHL_EXPORT +int uvt_ctx_get_fd(struct uvt_ctx *ctx) +{ + if (!ctx) + return -1; + + return ev_eloop_get_fd(ctx->eloop); +} + +SHL_EXPORT +void uvt_ctx_dispatch(struct uvt_ctx *ctx) +{ + if (!ctx) + return; + + ev_eloop_dispatch(ctx->eloop, 0); +} + +SHL_EXPORT +unsigned int uvt_ctx_get_major(struct uvt_ctx *ctx) +{ + return ctx->major; +} + +SHL_EXPORT +int uvt_ctx_new_minor(struct uvt_ctx *ctx, unsigned int *out) +{ + int ret; + + ret = shl_flagset_alloc(ctx->minors, out); + if (ret) + return ret; + + *out += ctx->minor_offset; + return 0; +} + +SHL_EXPORT +void uvt_ctx_free_minor(struct uvt_ctx *ctx, unsigned int minor) +{ + if (!ctx || minor < ctx->minor_offset) + return; + + shl_flagset_unset(ctx->minors, minor - ctx->minor_offset); +} diff --git a/src/uvt_internal.h b/src/uvt_internal.h new file mode 100644 index 0000000..479cf2b --- /dev/null +++ b/src/uvt_internal.h @@ -0,0 +1,118 @@ +/* + * UVT - Userspace Virtual Terminals + * + * Copyright (c) 2011-2013 David Herrmann <dh.herrmann@gmail.com> + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/* + * Userspace Virtual Terminals Internals + * Internal header of the UVT implementation. + */ + +#ifndef UVT_INTERNAL_H +#define UVT_INTERNAL_H + +#include <eloop.h> +#include <stdlib.h> +#include <uvt.h> +#include "shl_array.h" +#include "shl_dlist.h" +#include "shl_hook.h" +#include "shl_llog.h" + +#include <fuse/fuse.h> +#include <fuse/fuse_common.h> +#include <fuse/fuse_lowlevel.h> +#include <fuse/fuse_opt.h> +#include <fuse/cuse_lowlevel.h> + +/* contexts */ + +struct uvt_ctx { + unsigned long ref; + llog_submit_t llog; + void *llog_data; + struct ev_eloop *eloop; + + char *cuse_file; + unsigned int major; + unsigned int minor_offset; + struct shl_array *minors; +}; + +/* character devices */ + +struct uvt_cdev { + unsigned long ref; + struct uvt_ctx *ctx; + llog_submit_t llog; + void *llog_data; + + int error; + struct shl_hook *hook; + + struct fuse_session *session; + int fd; + struct fuse_chan *channel; + struct ev_fd *efd; + + size_t bufsize; + char *buf; + + struct shl_dlist clients; +}; + +/* client sessions */ + +struct uvt_client { + unsigned long ref; + struct shl_dlist list; + struct uvt_cdev *cdev; + llog_submit_t llog; + void *llog_data; + + struct fuse_pollhandle *ph; + struct shl_dlist waiters; + + const struct uvt_vt_ops *vt; + void *vt_data; + bool vt_locked; + bool vt_in_unlock; + unsigned int vt_retry; +}; + +void uvt_client_cleanup(struct uvt_client *client); + +int uvt_client_ll_open(struct uvt_client **out, struct uvt_cdev *cdev, + fuse_req_t req, struct fuse_file_info *fi); +void uvt_client_ll_release(fuse_req_t req, struct fuse_file_info *fi); +void uvt_client_ll_read(fuse_req_t req, size_t size, off_t off, + struct fuse_file_info *fi); +void uvt_client_ll_write(fuse_req_t req, const char *buf, size_t size, + off_t off, struct fuse_file_info *fi); +void uvt_client_ll_poll(fuse_req_t req, struct fuse_file_info *fi, + struct fuse_pollhandle *ph); +void uvt_client_ll_ioctl(fuse_req_t req, int cmd, void *arg, + struct fuse_file_info *fi, unsigned int flags, + const void *in_buf, size_t in_bufsz, size_t out_bufsz); + +#endif /* UVT_INTERNAL_H */ diff --git a/src/uvt_tty_null.c b/src/uvt_tty_null.c new file mode 100644 index 0000000..0c04942 --- /dev/null +++ b/src/uvt_tty_null.c @@ -0,0 +1,135 @@ +/* + * UVT - Userspace Virtual Terminals + * + * Copyright (c) 2011-2013 David Herrmann <dh.herrmann@gmail.com> + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/* + * Null TTY + * This tty simply discards all incoming messages and never produces any + * outgoing messages. Ioctls return static data or fail with some generic error + * code if they would modify internal state that we cannot emulate easily. + */ + +#include <eloop.h> +#include <errno.h> +#include <fcntl.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include "shl_llog.h" +#include "uvt.h" +#include "uvt_internal.h" + +#define LLOG_SUBSYSTEM "uvt_tty_null" + +struct uvt_tty_null { + unsigned long ref; + struct uvt_ctx *ctx; + llog_submit_t llog; + void *llog_data; +}; + +static void tty_null_ref(void *data) +{ + uvt_tty_null_ref(data); +} + +static void tty_null_unref(void *data) +{ + uvt_tty_null_unref(data); +} + +static int tty_null_register_cb(void *data, uvt_tty_cb cb, void *cb_data) +{ + return 0; +} + +static void tty_null_unregister_cb(void *data, uvt_tty_cb cb, void *cb_data) +{ +} + +static int tty_null_read(void *data, uint8_t *buf, size_t size) +{ + return -EAGAIN; +} + +static int tty_null_write(void *data, const uint8_t *buf, size_t size) +{ + return size; +} + +static unsigned int tty_null_poll(void *data) +{ + return UVT_TTY_WRITE; +} + +const struct uvt_tty_ops uvt_tty_null_ops = { + .ref = tty_null_ref, + .unref = tty_null_unref, + .register_cb = tty_null_register_cb, + .unregister_cb = tty_null_unregister_cb, + + .read = tty_null_read, + .write = tty_null_write, + .poll = tty_null_poll, +}; + +int uvt_tty_null_new(struct uvt_tty_null **out, struct uvt_ctx *ctx) +{ + struct uvt_tty_null *tty; + + if (!ctx) + return -EINVAL; + if (!out) + return llog_EINVAL(ctx); + + tty = malloc(sizeof(*tty)); + if (!tty) + return llog_ENOMEM(ctx); + memset(tty, 0, sizeof(*tty)); + tty->ref = 1; + tty->ctx = ctx; + tty->llog = tty->ctx->llog; + tty->llog_data = tty->ctx->llog_data; + + uvt_ctx_ref(tty->ctx); + *out = tty; + return 0; +} + +void uvt_tty_null_ref(struct uvt_tty_null *tty) +{ + if (!tty || !tty->ref) + return; + + ++tty->ref; +} + +void uvt_tty_null_unref(struct uvt_tty_null *tty) +{ + if (!tty || !tty->ref || --tty->ref) + return; + + uvt_ctx_unref(tty->ctx); + free(tty); +} |