diff options
| author | Jari Vetoniemi <mailroxas@gmail.com> | 2018-07-04 20:25:49 +0300 | 
|---|---|---|
| committer | Jari Vetoniemi <mailroxas@gmail.com> | 2018-07-04 20:56:04 +0300 | 
| commit | 0699cbd82b577a4358d199a627e9bc4497f436e7 (patch) | |
| tree | 3ebd8effe6ce2100077052622ceb8678070ebe9e /server | |
initial commit
Diffstat (limited to 'server')
| -rw-r--r-- | server/Makefile | 11 | ||||
| -rw-r--r-- | server/uinputd.c | 203 | 
2 files changed, 214 insertions, 0 deletions
| 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 <stdbool.h> +#include <stdint.h> +#include <stdlib.h> +#include <signal.h> +#include <unistd.h> +#include <assert.h> +#include <errno.h> +#include <stdio.h> +#include <fcntl.h> +#include <err.h> + +#include <linux/uinput.h> + +#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; +} | 
