diff options
author | David Herrmann <dh.herrmann@gmail.com> | 2013-10-23 15:22:28 +0200 |
---|---|---|
committer | David Herrmann <dh.herrmann@gmail.com> | 2013-10-23 15:22:28 +0200 |
commit | 972ec107cf7a69c32752bab3076af1275239d317 (patch) | |
tree | e94721b6018ac5d16710ad7e28a4bf64be31cb52 /src | |
parent | bcfb1d2ab5a2b5177c8d1ca2947fa80ce5d99803 (diff) |
kmscon: remove cdev sessions
cdev sessions are outdated. Use libuvt instead. Remove all references to
cdev-sessions and clean up the build chain.
Signed-off-by: David Herrmann <dh.herrmann@gmail.com>
Diffstat (limited to 'src')
-rw-r--r-- | src/kmscon_cdev.c | 1431 | ||||
-rw-r--r-- | src/kmscon_cdev.h | 52 | ||||
-rw-r--r-- | src/kmscon_conf.c | 10 | ||||
-rw-r--r-- | src/kmscon_conf.h | 2 | ||||
-rw-r--r-- | src/kmscon_seat.c | 9 |
5 files changed, 1 insertions, 1503 deletions
diff --git a/src/kmscon_cdev.c b/src/kmscon_cdev.c deleted file mode 100644 index d26629a..0000000 --- a/src/kmscon_cdev.c +++ /dev/null @@ -1,1431 +0,0 @@ -/* - * kmscon - Character-Device Session - * - * Copyright (c) 2012 David Herrmann <dh.herrmann@googlemail.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-Device Session - */ - -#define FUSE_USE_VERSION 29 - -#include <errno.h> -#include <fcntl.h> -#include <linux/kd.h> -#include <linux/major.h> -#include <linux/vt.h> -#include <pthread.h> -#include <signal.h> -#include <stdlib.h> -#include <string.h> -#include <sys/epoll.h> -#include <termio.h> -#include <termios.h> -#include <unistd.h> -#include "kmscon_cdev.h" -#include "kmscon_seat.h" -#include "shl_array.h" -#include "shl_dlist.h" -#include "shl_log.h" -#include "shl_ring.h" -#include "tsm_screen.h" -#include "tsm_vte.h" -#include "uterm_input.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 LOG_SUBSYSTEM "cdev" - -struct kmscon_cdev { - struct kmscon_seat *seat; - struct ev_eloop *eloop; - struct uterm_input *input; - struct kmscon_session *s; - struct ev_fd *efd; - unsigned int minor; - - struct fuse_session *session; - int fd; - struct fuse_chan *channel; - - size_t bufsize; - char *buf; - - struct shl_dlist clients; - int error; -}; - -struct cdev_client { - struct shl_dlist list; - struct kmscon_cdev *cdev; - bool dead; - - struct tsm_screen *screen; - struct tsm_vte *vte; - - bool active; - struct kmscon_session *s; - - struct fuse_pollhandle *ph; - struct shl_ring *ring; - struct shl_dlist readers; - - long kdmode; - long kbmode; - - struct vt_mode vtmode; - struct fuse_ctx user; - bool pending_switch; - - struct shl_dlist waiters; -}; - -struct cdev_reader { - struct shl_dlist list; - bool killed; - fuse_req_t req; - size_t len; -}; - -struct cdev_waiter { - struct shl_dlist list; - bool killed; - fuse_req_t req; -}; - -static pthread_mutex_t cdev_lock = PTHREAD_MUTEX_INITIALIZER; -static struct shl_array *cdev_ids = NULL; - -static int cdev_allocate_id(void) -{ - static const bool init = true; - int ret, len, i; - - pthread_mutex_lock(&cdev_lock); - - if (!cdev_ids) { - ret = shl_array_new(&cdev_ids, sizeof(bool), 4); - if (ret) - goto err_unlock; - } - - len = shl_array_get_length(cdev_ids); - for (i = 0; i < len; ++i) - if (!*SHL_ARRAY_AT(cdev_ids, bool, i)) - break; - - if (i >= len) { - ret = shl_array_push(cdev_ids, &init); - if (ret) - goto err_unlock; - ret = len; - } else { - *SHL_ARRAY_AT(cdev_ids, bool, i) = true; - ret = i; - } - -err_unlock: - pthread_mutex_unlock(&cdev_lock); - return (ret < 0) ? ret : (ret + 16384); -} - -/* - * Cdev Clients - * As opposed to kernel VTs, we only provide one single char-dev per seat and - * each client that opens it is managed separately. Hence, it is not possible to - * use such a VT shared by two clients except if you pass the FD between the - * clients. This has several advantages and avoids many bugs in the kernel VT - * implementation. - * For every user opening a /dev/ttyF<seat> device, a separate cdev_client is - * created. A cdev_client emulates a single kernel VT but is managed as a - * dedicated kmscon_session on its seat. We start a kmscon_terminal as backend - * so you actually can run "agetty" on this fake-VT. When set into graphical - * mode, the terminal is suspended and you can run an XServer on it. We emulate - * the VT-switching signal-API, too. We release all DRM devices if a fake-VT is - * active and reacquire them afterwards. This allows the clients to actually - * implement graphical terminals. However, if you fail to release the DRM - * devices, we actually try to kill the fake-VT so we can get access again. - */ - -static void reader_interrupt(fuse_req_t req, void *data) -{ - struct cdev_reader *reader = data; - - if (!reader) - return; - - reader->killed = true; -} - -static int reader_new(struct cdev_reader **out, struct cdev_client *client, - fuse_req_t req) -{ - struct cdev_reader *reader; - - if (fuse_req_interrupted(req)) - return -ENOENT; - - reader = malloc(sizeof(*reader)); - if (!reader) - return -ENOMEM; - memset(reader, 0, sizeof(*reader)); - reader->req = req; - fuse_req_interrupt_func(req, reader_interrupt, reader); - if (reader->killed) { - fuse_req_interrupt_func(req, NULL, NULL); - free(reader); - return -ENOENT; - } - - shl_dlist_link_tail(&client->readers, &reader->list); - *out = reader; - return 0; -} - -static void reader_free(struct cdev_reader *reader, int error) -{ - shl_dlist_unlink(&reader->list); - if (reader->req) { - fuse_req_interrupt_func(reader->req, NULL, NULL); - fuse_reply_err(reader->req, -error); - } - free(reader); -} - -static int reader_release(struct cdev_reader *reader, const char *buf, - size_t len) -{ - int ret; - - fuse_req_interrupt_func(reader->req, NULL, NULL); - ret = fuse_reply_buf(reader->req, buf, len); - reader->req = NULL; - reader_free(reader, 0); - return ret; -} - -static void waiter_interrupt(fuse_req_t req, void *data) -{ - struct cdev_waiter *waiter = data; - - if (!waiter) - return; - - waiter->killed = true; -} - -static int waiter_new(struct cdev_waiter **out, struct cdev_client *client, - fuse_req_t req) -{ - struct cdev_waiter *waiter; - - if (fuse_req_interrupted(req)) - return -ENOENT; - - waiter = malloc(sizeof(*waiter)); - if (!waiter) - return -ENOMEM; - memset(waiter, 0, sizeof(*waiter)); - waiter->req = req; - fuse_req_interrupt_func(req, waiter_interrupt, waiter); - if (waiter->killed) { - fuse_req_interrupt_func(req, NULL, NULL); - free(waiter); - return -ENOENT; - } - - shl_dlist_link_tail(&client->waiters, &waiter->list); - *out = waiter; - return 0; -} - -static void waiter_free(struct cdev_waiter *waiter, int error) -{ - shl_dlist_unlink(&waiter->list); - if (waiter->req) { - fuse_req_interrupt_func(waiter->req, NULL, NULL); - fuse_reply_err(waiter->req, -error); - } - free(waiter); -} - -static int waiter_release(struct cdev_waiter *waiter) -{ - int ret; - - fuse_req_interrupt_func(waiter->req, NULL, NULL); - ret = fuse_reply_ioctl(waiter->req, 0, NULL, 0); - waiter->req = NULL; - waiter_free(waiter, 0); - return ret; -} - -static void client_vte_event(struct tsm_vte *vte, const char *u8, size_t len, - void *data) -{ - struct cdev_client *client = data; - struct cdev_reader *reader; - int ret; - bool was_empty; - const char *buf; - size_t size; - - /* TODO: we should have a maximum buffer size here */ - was_empty = shl_ring_is_empty(client->ring); - ret = shl_ring_write(client->ring, u8, len); - if (ret) - log_warning("cannot resize buffer for cdev client: %d", ret); - - if (shl_ring_is_empty(client->ring)) - return; - - if (was_empty && client->ph) { - fuse_notify_poll(client->ph); - fuse_pollhandle_destroy(client->ph); - client->ph = NULL; - } - - while (!shl_dlist_empty(&client->readers)) { - reader = shl_dlist_entry(client->readers.next, - struct cdev_reader, list); - if (reader->killed) - continue; - - /* TODO: fix filling the whole buffer instead of returning - * partial buffers when the ring data is split */ - buf = shl_ring_peek(client->ring, &size, 0); - if (!size) - break; - if (size > reader->len) - size = reader->len; - - ret = reader_release(reader, buf, size); - if (ret < 0) - continue; - shl_ring_drop(client->ring, size); - } -} - -static void client_input_event(struct uterm_input *input, - struct uterm_input_event *ev, - void *data) -{ - struct cdev_client *client = data; - - if (!client->active || ev->handled) - return; - - /* we drop all input in K_OFF mode */ - if (client->kbmode == K_OFF) - return; - - /* TODO: see kmscon_terminal on how this is special. We need to fix this - * when xkbcommon provides the first multi-sym events. */ - if (ev->num_syms > 1) - return; - - if (tsm_vte_handle_keyboard(client->vte, ev->keysyms[0], ev->ascii, - ev->mods, ev->codepoints[0])) { - tsm_screen_sb_reset(client->screen); - ev->handled = true; - } -} - -static int client_activate(struct cdev_client *client) -{ - int ret; - struct cdev_waiter *waiter; - - /* TODO: Check whether we have CAP_KILL capability during startup */ - if (client->vtmode.mode == VT_PROCESS && client->vtmode.acqsig) { - ret = kill(client->user.pid, client->vtmode.acqsig); - if (ret) - log_warning("cannot send activation signal to process %d of cdev client %p (%d): %m", - client->user.pid, client, errno); - } - - while (!shl_dlist_empty(&client->waiters)) { - waiter = shl_dlist_entry(client->waiters.next, - struct cdev_waiter, list); - if (waiter->killed) - waiter_free(waiter, 0); - else - waiter_release(waiter); - } - - client->active = true; - return 0; -} - -static int client_deactivate(struct cdev_client *client) -{ - int ret; - - if (client->vtmode.mode == VT_PROCESS && client->vtmode.relsig) { - ret = kill(client->user.pid, client->vtmode.relsig); - if (ret) - log_warning("cannot send deactivation signal to process %d of cdev client %p (%d): %m", - client->user.pid, client, errno); - client->pending_switch = true; - return -EINPROGRESS; - } - - client->active = false; - return 0; -} - -static void client_kill(struct cdev_client *client) -{ - struct cdev_reader *reader; - struct cdev_waiter *waiter; - - if (client->dead) { - log_error("killing already dead client"); - return; - } - - log_debug("kill fake TTY client %p", client); - - client->dead = true; - - if (client->ph) { - fuse_notify_poll(client->ph); - fuse_pollhandle_destroy(client->ph); - } - - while (!shl_dlist_empty(&client->readers)) { - reader = shl_dlist_entry(client->readers.next, - struct cdev_reader, list); - reader_free(reader, -EPIPE); - } - - while (!shl_dlist_empty(&client->waiters)) { - waiter = shl_dlist_entry(client->waiters.next, - struct cdev_waiter, list); - waiter_free(waiter, -EPIPE); - } - - uterm_input_unregister_cb(client->cdev->input, client_input_event, - client); - tsm_vte_unref(client->vte); - tsm_screen_unref(client->screen); - shl_ring_free(client->ring); -} - -static int client_session_event(struct kmscon_session *s, - struct kmscon_session_event *ev, - void *data) -{ - struct cdev_client *client = data; - - switch (ev->type) { - case KMSCON_SESSION_ACTIVATE: - return client_activate(client); - case KMSCON_SESSION_DEACTIVATE: - return client_deactivate(client); - case KMSCON_SESSION_UNREGISTER: - client_kill(client); - break; - } - - return 0; -} - -static int client_new(struct cdev_client **out, struct kmscon_cdev *cdev) -{ - struct cdev_client *client; - int ret; - - client = malloc(sizeof(*client)); - if (!client) - return -ENOMEM; - memset(client, 0, sizeof(*client)); - client->cdev = cdev; - client->kdmode = KD_TEXT; - client->kbmode = K_UNICODE; - client->vtmode.mode = VT_AUTO; - shl_dlist_init(&client->readers); - shl_dlist_init(&client->waiters); - - log_debug("new fake TTY client %p", client); - - ret = shl_ring_new(&client->ring); - if (ret) { - log_error("cannot create ring buffer for new cdev client: %d", - ret); - goto err_free; - } - - /* TODO: Share the terminal-handling with the terminal-session. We - * currently just create the screen/vte objects here to get meaningful - * parsers. However, we should also correctly handled the terminal as is - * and draw it to the screen if in text-mode. - * This is nearly identical to the terminal-session so we should share - * the implementation between both instead of doing everything ourself - * here. */ - - ret = tsm_screen_new(&client->screen, log_llog, NULL); - if (ret) { - log_error("cannot create TSM screen for new cdev client: %d", - ret); - goto err_ring; - } - - ret = tsm_vte_new(&client->vte, client->screen, client_vte_event, - client, log_llog, NULL); - if (ret) { - log_error("cannot create TSM VTE for new cdev client: %d", - ret); - goto err_screen; - } - - ret = uterm_input_register_cb(cdev->input, client_input_event, client); - if (ret) { - log_error("cannot register input callback for cdev client: %d", - ret); - goto err_vte; - } - - ret = kmscon_seat_register_session(cdev->seat, &client->s, - client_session_event, client); - if (ret) { - log_error("cannot register session for cdev client: %d", ret); - goto err_input; - } - - shl_dlist_link(&cdev->clients, &client->list); - *out = client; - return 0; - -err_input: - uterm_input_unregister_cb(cdev->input, client_input_event, client); -err_vte: - tsm_vte_unref(client->vte); -err_screen: - tsm_screen_unref(client->screen); -err_ring: - shl_ring_free(client->ring); -err_free: - free(client); - return ret; -} - -static void client_destroy(struct cdev_client *client) -{ - log_debug("destroy client %p", client); - - if (!client->dead) - kmscon_session_unregister(client->s); - shl_dlist_unlink(&client->list); - 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. */ -static void client_cleanup(struct cdev_client *client) -{ - struct shl_dlist *i, *tmp; - struct cdev_reader *reader; - struct cdev_waiter *waiter; - - shl_dlist_for_each_safe(i, tmp, &client->readers) { - reader = shl_dlist_entry(i, struct cdev_reader, list); - if (reader->killed) - reader_free(reader, -ENOENT); - } - - shl_dlist_for_each_safe(i, tmp, &client->waiters) { - waiter = shl_dlist_entry(i, struct cdev_waiter, list); - if (waiter->killed) - waiter_free(waiter, -ENOENT); - } -} - -/* - * 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. - */ - -static void ll_open(fuse_req_t req, struct fuse_file_info *fi) -{ - struct kmscon_cdev *cdev = fuse_req_userdata(req); - struct cdev_client *client; - int ret; - - ret = client_new(&client, cdev); - if (ret) - goto err_out; - - fi->fh = (long)client; - fi->nonseekable = 1; - fi->direct_io = 1; - ret = fuse_reply_open(req, fi); - if (ret < 0) - client_destroy(client); - else - kmscon_session_enable(client->s); - - return; - -err_out: - fuse_reply_err(req, -ret); -} - -static void ll_release(fuse_req_t req, struct fuse_file_info *fi) -{ - struct cdev_client *client = (void*)fi->fh; - - if (!client) { - fuse_reply_err(req, EINVAL); - return; - } - - client_destroy(client); - fuse_reply_err(req, 0); -} - -static void ll_read(fuse_req_t req, size_t size, off_t off, - struct fuse_file_info *fi) -{ - struct cdev_client *client = (void*)fi->fh; - struct cdev_reader *reader; - const char *buf; - size_t len; - int ret; - - if (!client) { - fuse_reply_err(req, EINVAL); - return; - } - - if (client->dead) { - fuse_reply_err(req, EPIPE); - return; - } - - if (off != 0) { - fuse_reply_err(req, EINVAL); - return; - } - - if (!size) { - fuse_reply_buf(req, "", 0); - return; - } - - /* TODO: use a proper intermediate buffer as this might return only - * partial data */ - buf = shl_ring_peek(client->ring, &len, 0); - if (!len) { - if (fi->flags & O_NONBLOCK) { - fuse_reply_err(req, EAGAIN); - return; - } - - ret = reader_new(&reader, client, req); - if (ret) { - fuse_reply_err(req, -ret); - return; - } - - reader->len = size; - return; - } - - if (len > size) - len = size; - ret = fuse_reply_buf(req, buf, len); - if (ret < 0) - return; - shl_ring_drop(client->ring, len); -} - -static void ll_write(fuse_req_t req, const char *buf, size_t size, off_t off, - struct fuse_file_info *fi) -{ - struct cdev_client *client = (void*)fi->fh; - int ret; - - if (!client) { - fuse_reply_err(req, EINVAL); - return; - } - - if (client->dead) { - fuse_reply_err(req, EPIPE); - return; - } - - ret = fuse_reply_write(req, size); - if (ret < 0) - return; - tsm_vte_input(client->vte, buf, size); -} - -static void ll_poll(fuse_req_t req, struct fuse_file_info *fi, - struct fuse_pollhandle *ph) -{ - struct cdev_client *client = (void*)fi->fh; - unsigned int flags; - - if (!client) { - fuse_reply_err(req, EINVAL); - return; - } - - if (client->dead) { - 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; - - flags = EPOLLOUT | EPOLLWRNORM; - if (!shl_ring_is_empty(client->ring)) - flags |= EPOLLIN | EPOLLRDNORM; - - fuse_reply_poll(req, flags); -} - -static void ioctl_TCFLSH(struct cdev_client *client, fuse_req_t req, int val) -{ - switch (val) { - case TCIFLUSH: - shl_ring_flush(client->ring); - break; - case TCIOFLUSH: - shl_ring_flush(client->ring); - /* fallthrough */ - case TCOFLUSH: - /* nothing to do; we have no output queue */ - break; - default: - fuse_reply_err(req, EINVAL); - return; - } - - fuse_reply_ioctl(req, 0, NULL, 0); -} - -static void ioctl_VT_ACTIVATE(struct cdev_client *client, fuse_req_t req, - int val) -{ - unsigned short target, id; - - id = client->cdev->minor; - target = val; - - if (id == target) { - kmscon_session_schedule(client->s); - } else { - kmscon_seat_schedule(client->cdev->seat, target); - } - - fuse_reply_ioctl(req, 0, NULL, 0); -} - -static void ioctl_VT_WAITACTIVE(struct cdev_client *client, fuse_req_t req, - int val) -{ - int ret; - struct cdev_waiter *waiter; - - if (client->active) { - fuse_reply_ioctl(req, 0, NULL, 0); - return; - } - - ret = waiter_new(&waiter, client, req); - if (ret) { - fuse_reply_err(req, -ret); - return; - } -} - -static void ioctl_VT_GETSTATE(struct cdev_client *client, fuse_req_t req) -{ - struct vt_stat buf; - unsigned short id; - - id = client->cdev->minor; - if (id == 0 || id == 1) - id = 2; - - memset(&buf, 0, sizeof(buf)); - buf.v_active = client->active ? id : 1; - buf.v_signal = 0; - buf.v_state = ~0; - - fuse_reply_ioctl(req, 0, &buf, sizeof(buf)); -} - -static void ioctl_VT_GETMODE(struct cdev_client *client, fuse_req_t req) -{ - fuse_reply_ioctl(req, 0, &client->vtmode, sizeof(client->vtmode)); -} - -static void ioctl_VT_SETMODE(struct cdev_client *client, fuse_req_t req, - struct vt_mode *mode) -{ - bool proc; - - proc = mode->mode == VT_PROCESS; - - /* TODO: implement "waitv" logic */ - if (mode->waitv) { - fuse_reply_err(req, EINVAL); - return; - } - - if (mode->frsig) - log_debug("cdev client uses non-zero 'frsig' in VT_SETMODE: %d", - mode->frsig); - - if (mode->mode != VT_AUTO && mode->mode != VT_PROCESS) { - fuse_reply_err(req, EINVAL); - return; - } - - if (proc && (mode->relsig > SIGRTMAX || mode->acqsig > SIGRTMAX || - mode->relsig < 0 || mode->acqsig < 0)) { - fuse_reply_err(req, EINVAL); - return; - } - - memcpy(&client->vtmode, mode, sizeof(*mode)); - memcpy(&client->user, fuse_req_ctx(req), sizeof(client->user)); - fuse_reply_ioctl(req, 0, NULL, 0); -} - -static void ioctl_VT_RELDISP(struct cdev_client *client, fuse_req_t req, - int val) -{ - if (client->pending_switch) { - client->pending_switch = false; - if (val > 0) { - client->active = false; - kmscon_session_notify_deactivated(client->s); - } - } - - fuse_reply_ioctl(req, 0, NULL, 0); -} - -static void ioctl_KDGETMODE(struct cdev_client *client, fuse_req_t req) -{ - fuse_reply_ioctl(req, 0, &client->kdmode, sizeof(long)); -} - -static void ioctl_KDSETMODE(struct cdev_client *client, fuse_req_t req, - long val) -{ - int ret; - - switch (val) { - case KD_TEXT: - ret = kmscon_session_set_foreground(client->s); - if (ret) { - fuse_reply_err(req, -ret); - return; - } - client->kdmode = KD_TEXT; - break; - case KD_GRAPHICS: - ret = kmscon_session_set_background(client->s); - if (ret) { - fuse_reply_err(req, -ret); - return; - } - client->kdmode = KD_GRAPHICS; - break; - default: - fuse_reply_err(req, EINVAL); - return; - } - - fuse_reply_ioctl(req, 0, NULL, 0); -} - -static void ioctl_KDGKBMODE(struct cdev_client *client, fuse_req_t req) -{ - fuse_reply_ioctl(req, 0, &client->kbmode, sizeof(long)); -} - -static void ioctl_KDSKBMODE(struct cdev_client *client, fuse_req_t req, - long val) -{ - switch (val) { - case K_RAW: - case K_UNICODE: - case K_OFF: - /* TODO: we handle K_RAW/K_UNICODE the same way as it is unclear - * what K_RAW should do? */ - client->kbmode = val; - fuse_reply_ioctl(req, 0, NULL, 0); - break; - case K_XLATE: - case K_MEDIUMRAW: - /* TODO: what do these do? */ - fuse_reply_err(req, EOPNOTSUPP); - break; - default: - fuse_reply_err(req, EINVAL); - break; - } -} - -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; -} - -static void 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 cdev_client *client = (void*)fi->fh; - bool compat; - - if (!client) { - fuse_reply_err(req, EINVAL); - return; - } - - if (client->dead) { - fuse_reply_err(req, EPIPE); - return; - } - - /* TODO: fix compat-ioctls */ - compat = !!(flags & FUSE_IOCTL_COMPAT); - if (compat) { - fuse_reply_err(req, EOPNOTSUPP); - return; - } - - switch (cmd) { - case TCFLSH: - if (ioctl_param(req, arg, 0, in_bufsz, 0, out_bufsz)) - return; - ioctl_TCFLSH(client, req, (long)arg); - break; - case VT_ACTIVATE: - if (ioctl_param(req, arg, 0, in_bufsz, 0, out_bufsz)) - return; - ioctl_VT_ACTIVATE(client, req, (long)arg); - break; - case VT_WAITACTIVE: - if (ioctl_param(req, arg, 0, in_bufsz, 0, out_bufsz)) - return; - ioctl_VT_WAITACTIVE(client, req, (long)arg); - break; - case VT_GETSTATE: - if (ioctl_param(req, arg, 0, in_bufsz, - sizeof(struct vt_stat), out_bufsz)) - return; - ioctl_VT_GETSTATE(client, req); - break; - case VT_OPENQRY: - if (ioctl_param(req, arg, 0, in_bufsz, - sizeof(int), out_bufsz)) - return; - fuse_reply_err(req, EOPNOTSUPP); - break; - case VT_GETMODE: - if (ioctl_param(req, arg, 0, in_bufsz, - sizeof(struct vt_mode), out_bufsz)) - return; - ioctl_VT_GETMODE(client, req); - break; - case VT_SETMODE: - if (ioctl_param(req, arg, sizeof(struct vt_mode), in_bufsz, - 0, out_bufsz)) - return; - ioctl_VT_SETMODE(client, req, (struct vt_mode*)in_buf); - break; - case VT_RELDISP: - if (ioctl_param(req, arg, 0, in_bufsz, 0, out_bufsz)) - return; - ioctl_VT_RELDISP(client, req, (long)arg); - break; - case KDGETMODE: - if (ioctl_param(req, arg, 0, in_bufsz, - sizeof(long), out_bufsz)) - return; - ioctl_KDGETMODE(client, req); - break; - case KDSETMODE: - if (ioctl_param(req, arg, 0, in_bufsz, 0, out_bufsz)) - return; - ioctl_KDSETMODE(client, req, (long)arg); - break; - case KDGKBMODE: - if (ioctl_param(req, arg, 0, in_bufsz, - sizeof(long), out_bufsz)) - return; - ioctl_KDGKBMODE(client, req); - break; - case KDSKBMODE: - if (ioctl_param(req, arg, 0, in_bufsz, 0, out_bufsz)) - return; - ioctl_KDSKBMODE(client, req, (long)arg); - break; - case TCGETS: - if (ioctl_param(req, arg, 0, in_bufsz, - sizeof(struct termios), out_bufsz)) - return; - fuse_reply_err(req, EOPNOTSUPP); - break; - case TCSETS: - if (ioctl_param(req, arg, sizeof(struct termios), in_bufsz, - 0, out_bufsz)) - return; - fuse_reply_err(req, EOPNOTSUPP); - break; - case TCSETSW: - if (ioctl_param(req, arg, sizeof(struct termios), in_bufsz, - 0, out_bufsz)) - return; - fuse_reply_err(req, EOPNOTSUPP); - break; - case TCSETSF: - if (ioctl_param(req, arg, sizeof(struct termios), in_bufsz, - 0, out_bufsz)) - return; - fuse_reply_err(req, EOPNOTSUPP); - break; - default: - fuse_reply_err(req, EINVAL); - break; - } -} - -static void ll_destroy(void *data) { - struct kmscon_cdev *cdev = data; - struct cdev_client *client; - - /* on unexpected shutdown this releases all currently open clients */ - while (!shl_dlist_empty(&cdev->clients)) { - client = shl_dlist_entry(cdev->clients.next, - struct cdev_client, list); - client_destroy(client); - } -} - -static const struct cuse_lowlevel_ops ll_ops = { - .init = NULL, - .destroy = ll_destroy, - .open = ll_open, - .release = ll_release, - .read = ll_read, - .write = ll_write, - .poll = ll_poll, - .ioctl = 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. However, we only read if the FD was signaled readable so we can - * use a blocking FD to avoid any side-effects. 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 kmscon_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 */ - log_error("fuse channel shut down"); - 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) { - 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; - log_error("fuse channel read error (%d): %m", 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 kmscon_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 interruptions */ - if (!fuse_session_exited(se) && errno != ENOENT) { - cdev->error = -errno; - log_error("cannot write to fuse-channel (%d): %m", - 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 fake-VT session. - * 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 which also destroys - * _all_ client fake-sessions. - */ - -static void channel_event(struct ev_fd *fd, int mask, void *data) -{ - struct kmscon_cdev *cdev = data; - int ret; - struct fuse_buf buf; - struct fuse_chan *ch; - struct shl_dlist *i; - struct cdev_client *client; - - if (mask & (EV_HUP | EV_ERR)) { - log_error("HUP/ERR on fuse channel"); - cdev->error = -EPIPE; - kmscon_session_unregister(cdev->s); - return; - } - - if (!(mask & EV_READABLE)) - 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) { - log_error("fuse channel read error: %d", ret); - cdev->error = ret; - kmscon_session_unregister(cdev->s); - return; - } - - fuse_session_process_buf(cdev->session, &buf, ch); - if (fuse_session_exited(cdev->session)) { - log_error("fuse session exited"); - if (!cdev->error) - cdev->error = -EFAULT; - kmscon_session_unregister(cdev->s); - 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(i, &cdev->clients) { - client = shl_dlist_entry(i, struct cdev_client, list); - client_cleanup(client); - } -} - -static int kmscon_cdev_init(struct kmscon_cdev *cdev) -{ - static const char prefix[] = "DEVNAME="; - static const char fname[] = "/dev/cuse"; - int ret, id; - size_t bufsize; - struct cuse_info ci; - const char *dev_info_argv[1]; - char *name; - - /* 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! */ - - shl_dlist_init(&cdev->clients); - - ret = asprintf(&name, "%sttyF%s", prefix, - kmscon_seat_get_name(cdev->seat)); - if (ret <= 0) { - log_error("cannot allocate memory for fuse-devname"); - return -ENOMEM; - } - - log_info("initializing fake VT TTY device /dev/%s", - &name[sizeof(prefix) - 1]); - - id = cdev_allocate_id(); - if (id < 0) { - log_error("cannot allocate new cdev TTY id: %d", id); - free(name); - return id; - } - cdev->minor = id; - - dev_info_argv[0] = name; - memset(&ci, 0, sizeof(ci)); - ci.dev_major = TTY_MAJOR; - ci.dev_minor = cdev->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(name); - - if (!cdev->session) { - log_error("cannot create fuse-ll session"); - return -ENOMEM; - } - - cdev->fd = open(fname, O_RDWR | O_CLOEXEC); - if (cdev->fd < 0) { - log_error("cannot open %s (%d): %m", fname, errno); - ret = -EFAULT; - goto err_session; - } - - bufsize = getpagesize() + 0x1000; - if (bufsize < 0x21000) - bufsize = 0x21000; - - cdev->bufsize = bufsize; - cdev->buf = malloc(bufsize); - if (!cdev->buf) { - log_error("cannot allocate memory for buffer of size %zu", - bufsize); - ret = -ENOMEM; - 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) { - log_error("cannot allocate fuse-channel"); - ret = -ENOMEM; - goto err_buf; - } - - ret = ev_eloop_new_fd(cdev->eloop, &cdev->efd, cdev->fd, EV_READABLE, - channel_event, cdev); - if (ret) { - log_error("cannot create fd-object in eloop: %d", 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; -} - -void kmscon_cdev_destroy(struct kmscon_cdev *cdev) -{ - if (!cdev) - return; - - if (cdev->error) - log_warning("cdev module failed with error %d (maybe another kmscon process is already running?)", - cdev->error); - - fuse_session_destroy(cdev->session); - ev_eloop_rm_fd(cdev->efd); - free(cdev->buf); - close(cdev->fd); -} - -static int session_event(struct kmscon_session *session, - struct kmscon_session_event *ev, void *data) -{ - struct kmscon_cdev *cdev = data; - - switch (ev->type) { - case KMSCON_SESSION_UNREGISTER: - log_debug("destroy cdev session"); - kmscon_cdev_destroy(cdev); - free(cdev); - break; - } - - return 0; -} - -int kmscon_cdev_register(struct kmscon_session **out, - struct kmscon_seat *seat) -{ - struct kmscon_cdev *cdev; - int ret; - - if (!out || !seat) - return -EINVAL; - - cdev = malloc(sizeof(*cdev)); - if (!cdev) - return -ENOMEM; - memset(cdev, 0, sizeof(*cdev)); - cdev->seat = seat; - cdev->eloop = kmscon_seat_get_eloop(seat); - cdev->input = kmscon_seat_get_input(seat); - - ret = kmscon_cdev_init(cdev); - if (ret) - goto err_free; - - ret = kmscon_seat_register_session(seat, &cdev->s, session_event, cdev); - if (ret) { - log_error("cannot register session for cdev: %d", ret); - goto err_cdev; - } - - *out = cdev->s; - return 0; - -err_cdev: - kmscon_cdev_destroy(cdev); -err_free: - free(cdev); - return ret; -} diff --git a/src/kmscon_cdev.h b/src/kmscon_cdev.h deleted file mode 100644 index 4586481..0000000 --- a/src/kmscon_cdev.h +++ /dev/null @@ -1,52 +0,0 @@ -/* - * kmscon - Character-Device Session - * - * Copyright (c) 2012 David Herrmann <dh.herrmann@googlemail.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-Device Session - */ - -#ifndef KMSCON_CDEV_H -#define KMSCON_CDEV_H - -#include <errno.h> -#include <stdlib.h> -#include "kmscon_seat.h" - -#ifdef BUILD_ENABLE_SESSION_CDEV - -int kmscon_cdev_register(struct kmscon_session **out, - struct kmscon_seat *seat); - -#else /* !BUILD_ENABLE_SESSION_CDEV */ - -static inline int kmscon_cdev_register(struct kmscon_session **out, - struct kmscon_seat *seat) -{ - return -EOPNOTSUPP; -} - -#endif /* BUILD_ENABLE_SESSION_CDEV */ - -#endif /* KMSCON_CDEV_H */ diff --git a/src/kmscon_conf.c b/src/kmscon_conf.c index 27df14a..2173b44 100644 --- a/src/kmscon_conf.c +++ b/src/kmscon_conf.c @@ -79,7 +79,6 @@ static void print_help() "\t --session-max <max> [50] Maximum number of sessions\n" "\t --session-control [off] Allow keyboard session-control\n" "\t --terminal-session [on] Enable terminal session\n" - "\t --cdev-session [off] Enable kernel VT emulation session\n" "\n" "Terminal Options:\n" "\t-l, --login [/bin/login -p]\n" @@ -483,17 +482,11 @@ static int aftercheck_listen(struct conf_option *opt, int argc, char **argv, int idx) { struct kmscon_conf_t *conf = KMSCON_CONF_FROM_FIELD(opt->mem, listen); - int ret = -EFAULT; if (conf->listen) return 0; - if (conf->cdev_session) - log_error("you can use --cdev-session only in combination with --listen"); - else - ret = 0; - - return ret; + return 0; } /* @@ -569,7 +562,6 @@ int kmscon_conf_new(struct conf_ctx **out) CONF_OPTION_UINT(0, "session-max", &conf->session_max, 50), CONF_OPTION_BOOL(0, "session-control", &conf->session_control, false), CONF_OPTION_BOOL(0, "terminal-session", &conf->terminal_session, true), - CONF_OPTION_BOOL(0, "cdev-session", &conf->cdev_session, false), /* Terminal Options */ CONF_OPTION(0, 'l', "login", &conf_login, aftercheck_login, NULL, file_login, &conf->login, false), diff --git a/src/kmscon_conf.h b/src/kmscon_conf.h index 113ea1a..afca331 100644 --- a/src/kmscon_conf.h +++ b/src/kmscon_conf.h @@ -78,8 +78,6 @@ struct kmscon_conf_t { bool session_control; /* run terminal session */ bool terminal_session; - /* cdev session */ - bool cdev_session; /* Terminal Options */ /* custom login process */ diff --git a/src/kmscon_seat.c b/src/kmscon_seat.c index 5efa17b..2bde083 100644 --- a/src/kmscon_seat.c +++ b/src/kmscon_seat.c @@ -34,7 +34,6 @@ #include <string.h> #include "conf.h" #include "eloop.h" -#include "kmscon_cdev.h" #include "kmscon_conf.h" #include "kmscon_dummy.h" #include "kmscon_seat.h" @@ -824,14 +823,6 @@ void kmscon_seat_startup(struct kmscon_seat *seat) kmscon_session_enable(s); } - if (seat->conf->cdev_session) { - ret = kmscon_cdev_register(&s, seat); - if (ret == -EOPNOTSUPP) - log_notice("cdev sessions not compiled in"); - else if (ret) - log_error("cannot register cdev session"); - } - if (seat->conf->switchvt || uterm_vt_get_type(seat->vt) == UTERM_VT_FAKE) uterm_vt_activate(seat->vt); |