diff options
author | David Herrmann <dh.herrmann@gmail.com> | 2014-08-27 18:38:01 +0200 |
---|---|---|
committer | David Herrmann <dh.herrmann@gmail.com> | 2014-08-27 18:42:29 +0200 |
commit | 8e9371905c743cf997b2e8fa7fe3238f81f741fe (patch) | |
tree | 993b31b57009602c02a2484d4a38b4ac0a84bfc1 | |
parent | e06cc7b07465369fb7c01c9778b84cf82c82fdcf (diff) |
terminal: add systemd-evcat input debugging tool
Like systemd-subterm, this new systemd-evcat tool should only be used to
debug libsystemd-terminal. systemd-evcat attaches to the running session
and pushes all evdev devices attached to the current session into an
idev-session. All events of the created idev-devices are then printed to
stdout for input-event debugging.
-rw-r--r-- | .gitignore | 1 | ||||
-rw-r--r-- | Makefile.am | 14 | ||||
-rw-r--r-- | src/libsystemd-terminal/evcat.c | 499 |
3 files changed, 514 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore index 8aed0b9ba6..f8650870a3 100644 --- a/.gitignore +++ b/.gitignore @@ -69,6 +69,7 @@ /systemd-detect-virt /systemd-efi-boot-generator /systemd-escape +/systemd-evcat /systemd-firstboot /systemd-fsck /systemd-fstab-generator diff --git a/Makefile.am b/Makefile.am index 35a4c44a9f..e091febc1f 100644 --- a/Makefile.am +++ b/Makefile.am @@ -2954,6 +2954,7 @@ noinst_LTLIBRARIES += \ libsystemd-terminal.la noinst_PROGRAMS += \ + systemd-evcat \ systemd-subterm unifontdatadir=$(datadir)/unifont @@ -2995,6 +2996,19 @@ libsystemd_terminal_la_LIBADD = \ libsystemd-shared.la \ $(TERMINAL_LIBS) +systemd_evcat_CFLAGS = \ + $(AM_CFLAGS) \ + $(TERMINAL_CFLAGS) + +systemd_evcat_SOURCES = \ + src/libsystemd-terminal/evcat.c + +systemd_evcat_LDADD = \ + libsystemd-terminal.la \ + libsystemd-internal.la \ + libsystemd-shared.la \ + $(TERMINAL_LIBS) + systemd_subterm_SOURCES = \ src/libsystemd-terminal/subterm.c diff --git a/src/libsystemd-terminal/evcat.c b/src/libsystemd-terminal/evcat.c new file mode 100644 index 0000000000..590a30d873 --- /dev/null +++ b/src/libsystemd-terminal/evcat.c @@ -0,0 +1,499 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright (C) 2014 David Herrmann <dh.herrmann@gmail.com> + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +/* + * Event Catenation + * The evcat tool catenates input events of all requested devices and prints + * them to standard-output. It's only meant for debugging of input-related + * problems. + */ + +#include <assert.h> +#include <errno.h> +#include <getopt.h> +#include <libevdev/libevdev.h> +#include <linux/kd.h> +#include <linux/vt.h> +#include <stdarg.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/ioctl.h> +#include <sys/stat.h> +#include <systemd/sd-bus.h> +#include <systemd/sd-event.h> +#include <systemd/sd-login.h> +#include <termios.h> +#include <unistd.h> +#include <xkbcommon/xkbcommon.h> +#include "build.h" +#include "bus-util.h" +#include "event-util.h" +#include "idev.h" +#include "macro.h" +#include "sysview.h" +#include "term-internal.h" +#include "util.h" + +typedef struct Evcat Evcat; + +struct Evcat { + char *session; + char *seat; + sd_event *event; + sd_bus *bus; + sysview_context *sysview; + idev_context *idev; + idev_session *idev_session; + + bool managed : 1; +}; + +static Evcat *evcat_free(Evcat *e) { + if (!e) + return NULL; + + e->idev_session = idev_session_free(e->idev_session); + e->idev = idev_context_unref(e->idev); + e->sysview = sysview_context_free(e->sysview); + e->bus = sd_bus_unref(e->bus); + e->event = sd_event_unref(e->event); + free(e->seat); + free(e->session); + free(e); + + tcflush(0, TCIOFLUSH); + + return NULL; +} + +DEFINE_TRIVIAL_CLEANUP_FUNC(Evcat*, evcat_free); + +static bool is_managed(const char *session) { + unsigned int vtnr; + struct stat st; + long mode; + int r; + + /* Using logind's Controller API is highly fragile if there is already + * a session controller running. If it is registered as controller + * itself, TakeControl will simply fail. But if its a legacy controller + * that does not use logind's controller API, we must never register + * our own controller. Otherwise, we really mess up the VT. Therefore, + * only run in managed mode if there's no-one else. */ + + if (geteuid() == 0) + return false; + + if (!isatty(1)) + return false; + + if (!session) + return false; + + r = sd_session_get_vt(session, &vtnr); + if (r < 0 || vtnr < 1 || vtnr > 63) + return false; + + mode = 0; + r = ioctl(1, KDGETMODE, &mode); + if (r < 0 || mode != KD_TEXT) + return false; + + r = fstat(1, &st); + if (r < 0 || minor(st.st_rdev) != vtnr) + return false; + + return true; +} + +static int evcat_new(Evcat **out) { + _cleanup_(evcat_freep) Evcat *e = NULL; + int r; + + assert(out); + + e = new0(Evcat, 1); + if (!e) + return log_oom(); + + r = sd_pid_get_session(getpid(), &e->session); + if (r < 0) { + log_error("Cannot retrieve logind session: %s", strerror(-r)); + return r; + } + + r = sd_session_get_seat(e->session, &e->seat); + if (r < 0) { + log_error("Cannot retrieve seat of logind session: %s", strerror(-r)); + return r; + } + + e->managed = is_managed(e->session); + + r = sd_event_default(&e->event); + if (r < 0) + return r; + + r = sd_bus_open_system(&e->bus); + if (r < 0) + return r; + + r = sd_bus_attach_event(e->bus, e->event, SD_EVENT_PRIORITY_NORMAL); + if (r < 0) + return r; + + r = sigprocmask_many(SIG_BLOCK, SIGTERM, SIGINT, -1); + if (r < 0) + return r; + + r = sd_event_add_signal(e->event, NULL, SIGTERM, NULL, NULL); + if (r < 0) + return r; + + r = sd_event_add_signal(e->event, NULL, SIGINT, NULL, NULL); + if (r < 0) + return r; + + r = sysview_context_new(&e->sysview, + SYSVIEW_CONTEXT_SCAN_LOGIND | + SYSVIEW_CONTEXT_SCAN_EVDEV, + e->event, + e->bus, + NULL); + if (r < 0) + return r; + + r = idev_context_new(&e->idev, e->event, e->bus); + if (r < 0) + return r; + + *out = e; + e = NULL; + return 0; +} + +static void kdata_print(idev_data *data) { + idev_data_keyboard *k = &data->keyboard; + char buf[128]; + uint32_t i, c; + int cwidth; + + /* Key-press state: UP/DOWN/REPEAT */ + printf(" %-6s", k->value == 0 ? "UP" : + k->value == 1 ? "DOWN" : + "REPEAT"); + + /* Keycode that triggered the event */ + printf(" | %5u", (unsigned)k->keycode); + + /* Well-known name of the keycode */ + printf(" | %-20s", libevdev_event_code_get_name(EV_KEY, k->keycode) ? : "<unknown>"); + + /* Well-known modifiers */ + printf(" | %-5s", (k->mods & IDEV_KBDMOD_SHIFT) ? "SHIFT" : ""); + printf(" %-4s", (k->mods & IDEV_KBDMOD_CTRL) ? "CTRL" : ""); + printf(" %-3s", (k->mods & IDEV_KBDMOD_ALT) ? "ALT" : ""); + printf(" %-5s", (k->mods & IDEV_KBDMOD_LINUX) ? "LINUX" : ""); + printf(" %-4s", (k->mods & IDEV_KBDMOD_CAPS) ? "CAPS" : ""); + + /* Resolved symbols */ + printf(" |"); + for (i = 0; i < k->n_syms; ++i) { + buf[0] = 0; + xkb_keysym_get_name(k->keysyms[i], buf, sizeof(buf)); + + if (is_locale_utf8()) { + c = k->codepoints[i]; + if (c < 0x110000 && c > 0x20 && (c < 0x7f || c > 0x9f)) { + /* "%4lc" doesn't work well, so hard-code it */ + cwidth = mk_wcwidth(c); + while (cwidth++ < 2) + printf(" "); + + printf(" '%lc':", (wchar_t)c); + } else { + printf(" "); + } + } + + printf(" XKB_KEY_%-30s", buf); + } + + printf("\n"); +} + +static bool kdata_is_exit(idev_data *data) { + idev_data_keyboard *k = &data->keyboard; + + if (k->value != 1) + return false; + if (k->n_syms != 1) + return false; + + return k->codepoints[0] == 'q'; +} + +static int evcat_idev_fn(idev_session *session, void *userdata, idev_event *ev) { + Evcat *e = userdata; + + switch (ev->type) { + case IDEV_EVENT_DEVICE_ADD: + idev_device_enable(ev->device_add.device); + break; + case IDEV_EVENT_DEVICE_REMOVE: + idev_device_disable(ev->device_remove.device); + break; + case IDEV_EVENT_DEVICE_DATA: + switch (ev->device_data.data.type) { + case IDEV_DATA_KEYBOARD: + if (kdata_is_exit(&ev->device_data.data)) + sd_event_exit(e->event, 0); + else + kdata_print(&ev->device_data.data); + + break; + } + + break; + } + + return 0; +} + +static int evcat_sysview_fn(sysview_context *c, void *userdata, sysview_event *ev) { + unsigned int flags, type; + Evcat *e = userdata; + sysview_device *d; + const char *name; + int r; + + switch (ev->type) { + case SYSVIEW_EVENT_SESSION_FILTER: + if (streq_ptr(e->session, ev->session_filter.id)) + return 1; + + break; + case SYSVIEW_EVENT_SESSION_ADD: + assert(!e->idev_session); + + name = sysview_session_get_name(ev->session_add.session); + flags = 0; + + if (e->managed) + flags |= IDEV_SESSION_MANAGED; + + r = idev_session_new(&e->idev_session, + e->idev, + flags, + name, + evcat_idev_fn, + e); + if (r < 0) { + log_error("Cannot create idev session: %s", strerror(-r)); + return r; + } + + idev_session_enable(e->idev_session); + + if (e->managed) { + r = sysview_session_take_control(ev->session_add.session); + if (r < 0) { + log_error("Cannot request session control: %s", strerror(-r)); + return r; + } + } + + break; + case SYSVIEW_EVENT_SESSION_REMOVE: + idev_session_disable(e->idev_session); + e->idev_session = idev_session_free(e->idev_session); + sd_event_exit(e->event, 0); + break; + case SYSVIEW_EVENT_SESSION_ATTACH: + d = ev->session_attach.device; + type = sysview_device_get_type(d); + if (type == SYSVIEW_DEVICE_EVDEV) { + r = idev_session_add_evdev(e->idev_session, sysview_device_get_ud(d)); + if (r < 0) { + log_error("Cannot add evdev device to idev: %s", strerror(-r)); + return r; + } + } + + break; + case SYSVIEW_EVENT_SESSION_DETACH: + d = ev->session_detach.device; + type = sysview_device_get_type(d); + if (type == SYSVIEW_DEVICE_EVDEV) { + r = idev_session_remove_evdev(e->idev_session, sysview_device_get_ud(d)); + if (r < 0) { + log_error("Cannot remove evdev device from idev: %s", strerror(-r)); + return r; + } + } + + break; + case SYSVIEW_EVENT_SESSION_CONTROL: + r = ev->session_control.error; + if (r < 0) { + log_error("Cannot acquire session control: %s", strerror(-r)); + return r; + } + + r = ioctl(1, KDSKBMODE, K_UNICODE); + if (r < 0) { + log_error("Cannot set K_UNICODE on stdout: %m"); + return -errno; + } + + r = ioctl(1, KDSETMODE, KD_TEXT); + if (r < 0) { + log_error("Cannot set KD_TEXT on stdout: %m"); + return -errno; + } + + printf("\n"); + + break; + } + + return 0; +} + +static int evcat_run(Evcat *e) { + struct termios in_attr, saved_attr; + int r; + + assert(e); + + if (!e->managed && geteuid() > 0) + log_warning("You run in unmanaged mode without being root. This is likely to produce no output.."); + + printf("evcat - Read and catenate events from selected input devices\n" + " Running on seat '%s' in user-session '%s'\n" + " Exit by pressing ^C or 'q'\n\n", + e->seat ? : "seat0", e->session ? : "<none>"); + + r = sysview_context_start(e->sysview, evcat_sysview_fn, e); + if (r < 0) + goto out; + + r = tcgetattr(0, &in_attr); + if (r < 0) { + r = -errno; + goto out; + } + + saved_attr = in_attr; + in_attr.c_lflag &= ~ECHO; + + r = tcsetattr(0, TCSANOW, &in_attr); + if (r < 0) { + r = -errno; + goto out; + } + + r = sd_event_loop(e->event); + tcsetattr(0, TCSANOW, &saved_attr); + printf("exiting..\n"); + +out: + sysview_context_stop(e->sysview); + return r; +} + +static int help(void) { + printf("%s [OPTIONS...]\n\n" + "Read and catenate events from selected input devices.\n\n" + " -h --help Show this help\n" + " --version Show package version\n" + , program_invocation_short_name); + + return 0; +} + +static int parse_argv(int argc, char *argv[]) { + enum { + ARG_VERSION = 0x100, + }; + static const struct option options[] = { + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, ARG_VERSION }, + {}, + }; + int c; + + assert(argc >= 0); + assert(argv); + + while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) + switch (c) { + case 'h': + help(); + return 0; + + case ARG_VERSION: + puts(PACKAGE_STRING); + puts(SYSTEMD_FEATURES); + return 0; + + case '?': + return -EINVAL; + + default: + assert_not_reached("Unhandled option"); + } + + if (argc > optind) { + log_error("Too many arguments"); + return -EINVAL; + } + + return 1; +} + +int main(int argc, char *argv[]) { + _cleanup_(evcat_freep) Evcat *e = NULL; + int r; + + log_set_target(LOG_TARGET_AUTO); + log_parse_environment(); + log_open(); + + setlocale(LC_ALL, ""); + if (!is_locale_utf8()) + log_warning("Locale is not set to UTF-8. Codepoints will not be printed!"); + + r = parse_argv(argc, argv); + if (r <= 0) + goto finish; + + r = evcat_new(&e); + if (r < 0) + goto finish; + + r = evcat_run(e); + +finish: + return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; +} |