From 0699cbd82b577a4358d199a627e9bc4497f436e7 Mon Sep 17 00:00:00 2001 From: Jari Vetoniemi Date: Wed, 4 Jul 2018 20:25:49 +0300 Subject: initial commit --- server/Makefile | 11 +++ server/uinputd.c | 203 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 214 insertions(+) create mode 100644 server/Makefile create mode 100644 server/uinputd.c (limited to 'server') diff --git a/server/Makefile b/server/Makefile new file mode 100644 index 0000000..09c37b9 --- /dev/null +++ b/server/Makefile @@ -0,0 +1,11 @@ +version := $(shell git describe --abbrev=0 --tags --match "[0-9]*\.[0-9]*") +override CFLAGS += -std=c11 +override CPPFLAGS += -D_DEFAULT_SOURCE -DUINPUTD_VERSION='"$(version)"' -I../ + +all: uinputd +uinputd: uinputd.c + +clean: + $(RM) uinputd + +.PHONY: clean diff --git a/server/uinputd.c b/server/uinputd.c new file mode 100644 index 0000000..f0a6e0a --- /dev/null +++ b/server/uinputd.c @@ -0,0 +1,203 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "common/packet.h" + +/** + * This is a proof of concept uinput daemon. + * Mainly what we've learnt here is that linux interfaces aren't really ABI + * compatible around different architectures, which is dissapointing as it means + * all the interfaces basically need to be wrapped around in network transparent containers + * which is a boring work, especially when the interfaces are not small. It also means underlying + * data now needs to be understood and we can't act as simply proxy, which means if interfaces + * are changed the wrappers need to be changed as well. + * + * The host and client will understand each other if stars are aligned right: + * - Host and client have same native word size. + * - Host and client were compiled with similar compiler that pads structures identically, + * even if architectures differ. + */ + +struct core { + int uinput; +}; + +static int +_ioctl(const int fd, const unsigned long request, void *arg) +{ + int ret; + do { + ret = ioctl(fd, request, arg); + } while (ret == -1 && (errno == EINTR || errno == EAGAIN)); + return ret; +} + +#define ioctl(...) error "Use _ioctl instead" + +static void +fd_close_or_exit(const int fd) +{ + if (fd >= 0 && close(fd) != 0) + err(EXIT_FAILURE, "close"); +} + +static void +core_ioctl(const struct core *core, const struct ioctl *ioctl, const size_t bytes, void *data) +{ + const uint32_t dir[] = { + _IOC_NONE, // IOCTL_NONE + _IOC_READ, // IOCTL_IOR + _IOC_WRITE, // IOCTL_IOW, + _IOC_READ | _IOC_WRITE, // IOCTL_IOWR + }; + + if (ioctl->dir > IOCTL_IOWR) + return; + + const unsigned long request = _IOC(dir[ioctl->dir], ioctl->type, ioctl->nr, bytes); + + if (_ioctl(core->uinput, request, data) == -1) + err(EXIT_FAILURE, "ioctl"); +} + +static void +core_write(const struct core *core, const size_t bytes, const uint8_t *data) +{ + if (write(core->uinput, data, bytes) != bytes) + err(EXIT_FAILURE, "write: failed to write %zu bytes", bytes); +} + +static void +core_process_ioctl_variant(struct core *core, const struct ioctl *ioctl, FILE *src) +{ + switch (ioctl->arg) { + case IOCTL_ARG_OTHER: + { + uint64_t bytes; + uint8_t arg[16*1000]; // ioctls have max 16kB arg size + if ((bytes = fread(arg, 1, ioctl->bytes, src)) != ioctl->bytes) + err(EXIT_FAILURE, "fread: ioctl failed to read %zu bytes, got %zu bytes", ioctl->bytes, bytes); + core_ioctl(core, ioctl, ioctl->bytes, arg); + } + break; + + case IOCTL_ARG_INT: + { + int arg = ioctl->integer; + core_ioctl(core, ioctl, sizeof(arg), (void*)(intptr_t)arg); + } + break; + } +} + +static void +core_process(struct core *core, const struct packet *packet, FILE *src) +{ + assert(core && packet); + + switch (packet->type) { + case PACKET_IOCTL: + core_process_ioctl_variant(core, &packet->ioctl, src); + break; + + case PACKET_WRITE: + { + uint64_t bytes; + uint8_t arg[16*1000]; + if ((bytes = fread(arg, 1, packet->write.bytes, src)) != packet->write.bytes) + errx(EXIT_FAILURE, "fread: write failed to read %zu bytes, got %zu bytes", packet->write.bytes, bytes); + core_write(core, packet->write.bytes, arg); + } + break; + } +} + +static void +core_release(struct core *core) +{ + if (!core) + return; + + _ioctl(core->uinput, UI_DEV_DESTROY, NULL); + fd_close_or_exit(core->uinput); + *core = (struct core){0}; +} + +static void +core_init(struct core *core) +{ + assert(core); + + if ((core->uinput = open("/dev/uinput", O_WRONLY | O_CLOEXEC)) < 0) + err(EXIT_FAILURE, "open(%s)", "/dev/uinput"); +} + +static void +core_on_exit(const int signal, void *core) +{ + assert(core); + warnx("core_on_exit (%d)", signal); + core_release(core); +} + +static void +info(void) +{ + const char *remote = getenv("TCPREMOTEIP"); + warnx("version %s", UINPUTD_VERSION); + warnx("remote ip: %s", (remote ? remote : "none")); + warnx("sizeof(struct packet) is %zu bytes", sizeof(struct packet)); + warnx("sizeof(struct input_event) is %zu bytes", sizeof(struct input_event)); + warnx("sizeof(struct uinput_user_dev) is %zu bytes", sizeof(struct uinput_user_dev)); +} + +static void +sigterm(const int signal) +{ + warnx("%s", (signal == SIGTERM ? "SIGTERM" : "SIGINT")); + exit(EXIT_SUCCESS); +} + +int +main(int argc, const char *argv[]) +{ + { + const struct sigaction action = { + .sa_handler = sigterm, + }; + + if (sigaction(SIGTERM, &action, NULL) != 0 || sigaction(SIGINT, &action, NULL) != 0) + err(EXIT_FAILURE, "sigaction"); + } + + info(); + struct core core = {0}; + on_exit(core_on_exit, &core); + core_init(&core); + + struct packet packet; + while (true) { + size_t bytes; + if ((bytes = fread(&packet, 1, sizeof(packet), stdin)) != sizeof(packet)) + errx(EXIT_FAILURE, "invalid packet size %zu, expected %zu", bytes, sizeof(packet)); + + core_process(&core, &packet, stdin); + } + + /** + * The core will be destroyed when main returns, thus we exit(); + * instead to avoid cleanup functions from causing SIGSEGV. + */ + exit(EXIT_SUCCESS); + return EXIT_SUCCESS; +} -- cgit v1.2.3