diff options
Diffstat (limited to 'src/uvt_cdev.c')
-rw-r--r-- | src/uvt_cdev.c | 485 |
1 files changed, 0 insertions, 485 deletions
diff --git a/src/uvt_cdev.c b/src/uvt_cdev.c deleted file mode 100644 index 291857b..0000000 --- a/src/uvt_cdev.c +++ /dev/null @@ -1,485 +0,0 @@ -/* - * UVT - Userspace Virtual Terminals - * - * Copyright (c) 2011-2013 David Herrmann <dh.herrmann@gmail.com> - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files - * (the "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be included - * in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. - * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY - * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, - * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE - * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -/* - * Character Devices - * This implements a VT character device entry point via the CUSE API. It does - * not implement the VT API on top of the character-device (cdev) but only - * provides the entry point. It is up to the user to bind open-files to VT and - * client objects. - */ - -#include <errno.h> -#include <fcntl.h> -#include <linux/major.h> -#include <stdio.h> -#include <stdlib.h> -#include <string.h> -#include <unistd.h> -#include "shl_dlist.h" -#include "shl_hook.h" -#include "shl_llog.h" -#include "uvt.h" -#include "uvt_internal.h" - -#include <fuse/fuse.h> -#include <fuse/fuse_common.h> -#include <fuse/fuse_lowlevel.h> -#include <fuse/fuse_opt.h> -#include <fuse/cuse_lowlevel.h> - -#define LLOG_SUBSYSTEM "uvt_cdev" - -/* - * FUSE low-level ops - * This implements all the file-system operations on the character-device. It - * is important that we handle interrupts correctly (ENOENT) and never loose - * any data. This is all single threaded as it is not performance critical at - * all. - * We simply dispatch each call to uvt_client as this implements all the - * client-session related operations. - */ - -static void ll_open(fuse_req_t req, struct fuse_file_info *fi) -{ - struct uvt_cdev *cdev = fuse_req_userdata(req); - struct uvt_client *client; - struct uvt_cdev_event ev; - int ret; - - ret = uvt_client_ll_open(&client, cdev, req, fi); - if (ret) - return; - - memset(&ev, 0, sizeof(ev)); - ev.type = UVT_CDEV_OPEN; - ev.client = client; - shl_hook_call(cdev->hook, cdev, &ev); -} - -static void ll_destroy(void *data) { - struct uvt_cdev *cdev = data; - struct uvt_client *client; - - /* on unexpected shutdown this kills all open clients */ - while (!shl_dlist_empty(&cdev->clients)) { - client = shl_dlist_entry(cdev->clients.next, - struct uvt_client, list); - uvt_client_kill(client); - uvt_client_unref(client); - } -} - -static const struct cuse_lowlevel_ops ll_ops = { - .init = NULL, - .destroy = ll_destroy, - .open = ll_open, - .release = uvt_client_ll_release, - .read = uvt_client_ll_read, - .write = uvt_client_ll_write, - .poll = uvt_client_ll_poll, - .ioctl = uvt_client_ll_ioctl, - .flush = NULL, - .fsync = NULL, -}; - -/* - * FUSE channel ops - * The connection to the FUSE kernel module is done via a file-descriptor. - * Writing to it is synchronous, so the commands that we write are - * _immediately_ executed and return the result to us. Furthermore, write() - * is always non-blocking and always succeeds so no reason to watch for - * EAGAIN. Reading from the FD, on the other hand, may block if there is no - * data available so we mark it as O_NONBLOCK. The kernel maintains - * an event-queue that we read from. So there may be pending events that we - * haven't read but which affect the calls that we write to the kernel. This - * is important when handling interrupts. - * chan_receive() and chan_send() handle I/O to the kernel module and are - * hooked up into a fuse-channel. - */ - -static int chan_receive(struct fuse_chan **chp, char *buf, size_t size) -{ - struct fuse_chan *ch = *chp; - struct uvt_cdev *cdev = fuse_chan_data(ch); - struct fuse_session *se = fuse_chan_session(ch); - int fd = fuse_chan_fd(ch); - ssize_t res; - - if (!se || !cdev) - return -EINVAL; - - if (!size) - return 0; - -restart: - if (fuse_session_exited(se)) - return 0; - - res = read(fd, buf, size); - if (!res) { - /* EOF on cuse file */ - llog_error(cdev, "fuse channel shut down on cdev %p", cdev); - fuse_session_exit(se); - return 0; - } else if (res < 0) { - /* ENOENT is returned if the operation was interrupted, it's - * safe to restart */ - if (errno == ENOENT) - goto restart; - - /* ENODEV is returned if the FS got unmounted. This shouldn't - * occur for CUSE devices. Anyway, exit if this happens. */ - if (errno == ENODEV) { - llog_error(cdev, "fuse channel unmounted on cdev %p", - cdev); - fuse_session_exit(se); - return 0; - } - - /* EINTR and EAGAIN are simply forwarded to the caller. */ - if (errno == EINTR || errno == EAGAIN) - return -errno; - - cdev->error = -errno; - llog_error(cdev, "fuse channel read error on cdev %p (%d): %m", - cdev, errno); - fuse_session_exit(se); - return cdev->error; - } - - return res; -} - -static int chan_send(struct fuse_chan *ch, const struct iovec iov[], - size_t count) -{ - struct uvt_cdev *cdev = fuse_chan_data(ch); - struct fuse_session *se = fuse_chan_session(ch); - int fd = fuse_chan_fd(ch); - int ret; - - if (!cdev || !se) - return -EINVAL; - if (!iov || !count) - return 0; - - ret = writev(fd, iov, count); - if (ret < 0) { - /* ENOENT is returned on interrupts */ - if (!fuse_session_exited(se) && errno != ENOENT) { - cdev->error = -errno; - llog_error(cdev, "cannot write to fuse-channel on cdev %p (%d): %m", - cdev, errno); - fuse_session_exit(se); - } - return cdev->error; - } - - return 0; -} - -static const struct fuse_chan_ops chan_ops = { - .receive = chan_receive, - .send = chan_send, - .destroy = NULL, -}; - -/* - * Character Device - * This creates the high-level character-device driver and registers a - * fake-session that is used to control each character file. - * channel_event() is a callback when I/O is possible on the FUSE FD and - * performs all outstanding tasks. - * On error, the fake-session is unregistered and deleted. This also stops all - * client sessions, obviously. - */ - -static void uvt_cdev_hup(struct uvt_cdev *cdev, int error) -{ - struct uvt_cdev_event ev; - - ev_eloop_rm_fd(cdev->efd); - cdev->efd = NULL; - cdev->error = error; - - memset(&ev, 0, sizeof(ev)); - ev.type = UVT_CDEV_HUP; - - shl_hook_call(cdev->hook, cdev, &ev); -} - -static void channel_event(struct ev_fd *fd, int mask, void *data) -{ - struct uvt_cdev *cdev = data; - int ret; - struct fuse_buf buf; - struct fuse_chan *ch; - struct shl_dlist *iter; - struct uvt_client *client; - - if (!(mask & EV_READABLE)) { - if (mask & (EV_HUP | EV_ERR)) { - llog_error(cdev, "HUP/ERR on fuse channel on cdev %p", - cdev); - uvt_cdev_hup(cdev, -EPIPE); - } - - return; - } - - memset(&buf, 0, sizeof(buf)); - buf.mem = cdev->buf; - buf.size = cdev->bufsize; - ch = cdev->channel; - ret = fuse_session_receive_buf(cdev->session, &buf, &ch); - if (ret == -EINTR || ret == -EAGAIN) { - return; - } else if (ret < 0) { - llog_error(cdev, "fuse channel read error on cdev %p: %d", - cdev, ret); - uvt_cdev_hup(cdev, ret); - return; - } - - fuse_session_process_buf(cdev->session, &buf, ch); - if (fuse_session_exited(cdev->session)) { - llog_error(cdev, "fuse session exited on cdev %p", cdev); - uvt_cdev_hup(cdev, cdev->error ? : -EFAULT); - return; - } - - /* Readers can get interrupted asynchronously. Due to heavy locking - * inside of FUSE, we cannot release them right away. So cleanup all - * killed readers after we processed all buffers. */ - shl_dlist_for_each(iter, &cdev->clients) { - client = shl_dlist_entry(iter, struct uvt_client, list); - uvt_client_cleanup(client); - } -} - -static int uvt_cdev_init(struct uvt_cdev *cdev, const char *name, - unsigned int major, unsigned int minor) -{ - const char *dev_info_argv[1]; - struct cuse_info ci; - size_t bufsize; - char *nparam; - int ret; - - /* TODO: libfuse makes sure that fd 0, 1 and 2 are available as - * standard streams, otherwise they fail. This is awkward and we - * should check whether this is really needed and _why_? - * If it is needed, fix upstream to stop that crazy! */ - - if (!major) - major = TTY_MAJOR; - - if (!major || major > 255) { - llog_error(cdev, "invalid major %u on cdev %p", - major, cdev); - return -EINVAL; - } - if (!minor) { - llog_error(cdev, "invalid minor %u on cdev %p", - minor, cdev); - return -EINVAL; - } - if (!name || !*name) { - llog_error(cdev, "empty name on cdev %p", - cdev); - return -EINVAL; - } - - llog_info(cdev, "creating device /dev/%s %u:%u on cdev %p", - name, major, minor, cdev); - - ret = asprintf(&nparam, "DEVNAME=%s", name); - if (ret <= 0) - return llog_ENOMEM(cdev); - - dev_info_argv[0] = nparam; - memset(&ci, 0, sizeof(ci)); - ci.dev_major = major; - ci.dev_minor = minor; - ci.dev_info_argc = 1; - ci.dev_info_argv = dev_info_argv; - ci.flags = CUSE_UNRESTRICTED_IOCTL; - - cdev->session = cuse_lowlevel_new(NULL, &ci, &ll_ops, cdev); - free(nparam); - - if (!cdev->session) { - llog_error(cdev, "cannot create fuse-ll session on cdev %p", - cdev); - return -ENOMEM; - } - - cdev->fd = open(cdev->ctx->cuse_file, O_RDWR | O_CLOEXEC | O_NONBLOCK); - if (cdev->fd < 0) { - llog_error(cdev, "cannot open cuse-file %s on cdev %p (%d): %m", - cdev->ctx->cuse_file, cdev, errno); - ret = -EFAULT; - goto err_session; - } - - bufsize = getpagesize() + 0x1000; - if (bufsize < 0x21000) - bufsize = 0x21000; - - cdev->bufsize = bufsize; - cdev->buf = malloc(bufsize); - if (!cdev->buf) { - ret = llog_ENOMEM(cdev); - goto err_fd; - } - - /* Argh! libfuse does not use "const" for the "chan_ops" pointer so we - * actually have to cast it. Their implementation does not write into it - * so we can safely use a constant storage for it. - * TODO: Fix libfuse upstream! */ - cdev->channel = fuse_chan_new((void*)&chan_ops, cdev->fd, bufsize, - cdev); - if (!cdev->channel) { - llog_error(cdev, "cannot allocate fuse-channel on cdev %p", - cdev); - ret = -ENOMEM; - goto err_buf; - } - - ret = ev_eloop_new_fd(cdev->ctx->eloop, &cdev->efd, cdev->fd, - EV_READABLE, channel_event, cdev); - if (ret) - goto err_chan; - - fuse_session_add_chan(cdev->session, cdev->channel); - return 0; - -err_chan: - fuse_chan_destroy(cdev->channel); -err_buf: - free(cdev->buf); -err_fd: - close(cdev->fd); -err_session: - fuse_session_destroy(cdev->session); - return ret; -} - -static void uvt_cdev_destroy(struct uvt_cdev *cdev) -{ - if (cdev->error) - llog_warning(cdev, "cdev %p failed with error %d", - cdev, cdev->error); - - fuse_session_destroy(cdev->session); - ev_eloop_rm_fd(cdev->efd); - free(cdev->buf); - close(cdev->fd); -} - -SHL_EXPORT -int uvt_cdev_new(struct uvt_cdev **out, struct uvt_ctx *ctx, - const char *name, unsigned int major, unsigned int minor) -{ - struct uvt_cdev *cdev; - int ret; - - if (!ctx) - return -EINVAL; - if (!out) - return llog_EINVAL(ctx); - - cdev = malloc(sizeof(*cdev)); - if (!cdev) - return llog_ENOMEM(ctx); - memset(cdev, 0, sizeof(*cdev)); - cdev->ref = 1; - cdev->ctx = ctx; - cdev->llog = ctx->llog; - cdev->llog_data = ctx->llog_data; - shl_dlist_init(&cdev->clients); - - llog_debug(cdev, "new cdev %p on ctx %p", cdev, cdev->ctx); - - ret = shl_hook_new(&cdev->hook); - if (ret) - goto err_free; - - ret = uvt_cdev_init(cdev, name, major, minor); - if (ret) - goto err_hook; - - uvt_ctx_ref(cdev->ctx); - *out = cdev; - return 0; - -err_hook: - shl_hook_free(cdev->hook); -err_free: - free(cdev); - return ret; -} - -SHL_EXPORT -void uvt_cdev_ref(struct uvt_cdev *cdev) -{ - if (!cdev || !cdev->ref) - return; - - ++cdev->ref; -} - -SHL_EXPORT -void uvt_cdev_unref(struct uvt_cdev *cdev) -{ - if (!cdev || !cdev->ref || --cdev->ref) - return; - - llog_debug(cdev, "free cdev %p", cdev); - - uvt_cdev_destroy(cdev); - shl_hook_free(cdev->hook); - uvt_ctx_unref(cdev->ctx); - free(cdev); -} - -SHL_EXPORT -int uvt_cdev_register_cb(struct uvt_cdev *cdev, uvt_cdev_cb cb, void *data) -{ - if (!cdev) - return -EINVAL; - - return shl_hook_add_cast(cdev->hook, cb, data, false); -} - -SHL_EXPORT -void uvt_cdev_unregister_cb(struct uvt_cdev *cdev, uvt_cdev_cb cb, void *data) -{ - if (!cdev) - return; - - shl_hook_rm_cast(cdev->hook, cb, data); -} |