summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/uvt.h274
-rw-r--r--src/uvt_cdev.c485
-rw-r--r--src/uvt_client.c1120
-rw-r--r--src/uvt_ctx.c168
-rw-r--r--src/uvt_internal.h118
-rw-r--r--src/uvt_tty_null.c135
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);
+}