diff options
author | David Herrmann <dh.herrmann@gmail.com> | 2013-10-29 10:01:13 +0100 |
---|---|---|
committer | David Herrmann <dh.herrmann@gmail.com> | 2013-10-29 10:01:13 +0100 |
commit | 9f2f11d2a53a0ca91b763d04ca7003d35f83cbf3 (patch) | |
tree | 2c2445b0325e6d4cb521d87a4c969102249d6f2d /src | |
parent | 761434ecac7d9a4f1a95e3f3b4eee6e539947d32 (diff) |
Remove libuvt
uvt is not used inside of kmscon. Moved into a separate library if
some-one is interested.
Signed-off-by: David Herrmann <dh.herrmann@gmail.com>
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, 0 insertions, 2300 deletions
diff --git a/src/uvt.h b/src/uvt.h deleted file mode 100644 index e767005..0000000 --- a/src/uvt.h +++ /dev/null @@ -1,274 +0,0 @@ -/* - * 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 deleted file mode 100644 index 291857b..0000000 --- a/src/uvt_cdev.c +++ /dev/null @@ -1,485 +0,0 @@ -/* - * 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 deleted file mode 100644 index 574001e..0000000 --- a/src/uvt_client.c +++ /dev/null @@ -1,1120 +0,0 @@ -/* - * 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 deleted file mode 100644 index ad1e534..0000000 --- a/src/uvt_ctx.c +++ /dev/null @@ -1,168 +0,0 @@ -/* - * 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 deleted file mode 100644 index 479cf2b..0000000 --- a/src/uvt_internal.h +++ /dev/null @@ -1,118 +0,0 @@ -/* - * 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 deleted file mode 100644 index 0c04942..0000000 --- a/src/uvt_tty_null.c +++ /dev/null @@ -1,135 +0,0 @@ -/* - * 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); -} |