summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDavid Herrmann <dh.herrmann@gmail.com>2013-10-29 09:35:55 +0100
committerDavid Herrmann <dh.herrmann@gmail.com>2013-10-29 09:35:55 +0100
commite4f32a1df3f6d5d360b18dc7cb50b46eb84d91fd (patch)
tree4e6d32c92dcc7a30e3698aa1d1f087a18b6cab4e
Import libuvt sourcesHEADmaster
Import libuvt files from kmscon repository. We currently don't build the library. We just provide the files here for anyone interested. We will bring it up to date once we really need it again. Signed-off-by: David Herrmann <dh.herrmann@gmail.com>
-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);
+}