summaryrefslogtreecommitdiff
path: root/server/uinputd.c
diff options
context:
space:
mode:
Diffstat (limited to 'server/uinputd.c')
-rw-r--r--server/uinputd.c203
1 files changed, 203 insertions, 0 deletions
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;
+}