--- config/devd.c.orig 2015-01-12 10:47:19.000000000 +0100 +++ config/devd.c 2015-01-13 12:02:22.000000000 +0100 @@ -0,0 +1,574 @@ +/* + * Copyright (c) 2012 Baptiste Daroussin + * Copyright (c) 2013, 2014 Alex Kozlov + * Copyright (c) 2014 Robert Millan + * Copyright (c) 2014 Jean-Sebastien Pedron + * + * 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 (including the next + * paragraph) 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. + * + * Author: Baptiste Daroussin + */ + +#ifdef HAVE_DIX_CONFIG_H +#include +#endif + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "input.h" +#include "inputstr.h" +#include "hotplug.h" +#include "config-backends.h" +#include "os.h" + +#define DEVD_SOCK_PATH "/var/run/devd.pipe" + +#define RECONNECT_DELAY (5 * 1000) + +static int sock_devd; +static bool is_kbdmux = false; +OsTimerPtr rtimer; + +struct hw_type { + const char *driver; + int flag; + const char *xdriver; + const char *sysctldesc; +}; + +static const struct hw_type hw_types0[] = { + { _PATH_DEV "sysmouse", ATTR_POINTER, "mouse", NULL }, + { NULL, 0, NULL, NULL }, +}; + +static const struct hw_type hw_types1[] = { + { _PATH_DEV "ukbd%d", ATTR_KEYBOARD, "kbd", "dev.ukbd.%d.%%desc" }, + { _PATH_DEV "atkbd%d", ATTR_KEYBOARD, "kbd", "dev.atkbd.%d.%%desc" }, + { _PATH_DEV "kbdmux%d", ATTR_KEYBOARD, "kbd", NULL }, + { _PATH_DEV "ums%d", ATTR_POINTER, "mouse", "dev.ums.%d.%%desc" }, + { _PATH_DEV "psm%d", ATTR_POINTER, "mouse", "dev.psm.%d.%%desc" }, + { _PATH_DEV "joy%d", ATTR_JOYSTICK, "mouse", "dev.joy.%d.%%desc" }, + { _PATH_DEV "atp%d", ATTR_TOUCHPAD, "mouse", "dev.atp.%d.%%desc" }, + { _PATH_DEV "wsp%d", ATTR_TOUCHPAD, "mouse", "dev.wsp.%d.%%desc" }, + { _PATH_DEV "uep%d", ATTR_TOUCHSCREEN, "mouse", "dev.uep.%d.%%desc" }, + { _PATH_DEV "input/event%d", ATTR_TOUCHPAD, "evdev", NULL}, + { NULL, 0, NULL, NULL }, +}; + +static void +get_usb_id(char **pptr, int fd) +{ + unsigned short vendor; + unsigned short product; + unsigned int speed; +#define WEBCAMD_IOCTL_GET_USB_VENDOR_ID _IOR('q', 250, unsigned short) +#define WEBCAMD_IOCTL_GET_USB_PRODUCT_ID _IOR('q', 251, unsigned short) +#define WEBCAMD_IOCTL_GET_USB_SPEED _IOR('q', 252, unsigned int) + if (ioctl(fd, WEBCAMD_IOCTL_GET_USB_VENDOR_ID, &vendor) == 0 && + ioctl(fd, WEBCAMD_IOCTL_GET_USB_PRODUCT_ID, &product) == 0 && + ioctl(fd, WEBCAMD_IOCTL_GET_USB_SPEED, &speed) == 0) { + if (asprintf(pptr, "%04x:%04x", vendor, product) == -1) + *pptr = NULL; + } +} + +static const char * +skip_path_dev(const char *ptr) +{ + if (strstr(ptr, _PATH_DEV) == ptr) + ptr += strlen(_PATH_DEV); + return (ptr); +} + +static char * +sysctl_get_str(const char *sysctlname) +{ + char *dest = NULL; + size_t len; + + if (sysctlname == NULL) + return NULL; + + if (sysctlbyname(sysctlname, NULL, &len, NULL, 0) == 0) { + dest = malloc(len + 1); + if (dest) { + if (sysctlbyname(sysctlname, dest, &len, NULL, 0) == 0) + dest[len] = '\0'; + else { + free(dest); + dest = NULL; + } + } + } + + return dest; +} + +static void +device_added(const char *devicename, bool allow_no_device) +{ + InputAttributes attrs = { }; + InputOption *options = NULL; + char *config_info = NULL; + DeviceIntPtr dev = NULL; + struct hw_type hw_type; + char *product = NULL; + char sysctlname[64]; + char *vendor = NULL; + int unit = 0; + int fd = -1; + char *walk; + int i; + + for (i = 0; hw_types0[i].driver != NULL; i++) { + if (strcmp(devicename, hw_types0[i].driver) == 0) { + hw_type = hw_types0[i]; + goto found; + } + } + for (i = 0; hw_types1[i].driver != NULL; i++) { + if (sscanf(devicename, hw_types1[i].driver, &unit) == 1) { + hw_type = hw_types1[i]; + goto found; + } + } + goto ignore; + +found: + if (hw_type.xdriver == NULL) + goto ignore; + + if (strcmp(hw_type.xdriver, "kbd") == 0) { + bool match = (strstr(hw_type.driver, + _PATH_DEV "kbdmux") == hw_type.driver); + + if (is_kbdmux) { + if (!match) + goto ignore; + } else { + if (match) + goto ignore; + } + } + + options = input_option_new(NULL, "_source", "server/devd"); + if (options == NULL) + goto error; + + if (hw_type.sysctldesc != NULL) { + snprintf(sysctlname, sizeof(sysctlname), + hw_type.sysctldesc, unit); + vendor = sysctl_get_str(sysctlname); + } + + if (vendor == NULL) { + options = input_option_new(options, "name", + skip_path_dev(devicename)); + } else { + if ((walk = strchr(vendor, ' ')) != NULL) { + walk[0] = '\0'; + walk++; + product = walk; + if ((walk = strchr(product, ',')) != NULL) + walk[0] = '\0'; + } + + attrs.vendor = strdup(vendor); + if (product != NULL) { + attrs.product = strdup(product); + options = input_option_new(options, + "name", product); + } else { + options = input_option_new(options, + "name", "Unknown"); + } + } + attrs.device = strdup(devicename); + + fd = open(devicename, O_RDONLY); + if (fd > -1) { + get_usb_id(&attrs.usb_id, fd); + close(fd); + options = input_option_new(options, "device", devicename); + if (options == NULL) + goto error; + } else if (allow_no_device) { + /* + * Don't pass "device" option if the keyboard is + * already attached to the console (ie. open() fails). + * This would activate a special logic in + * xf86-input-keyboard. Prevent any other attached to + * console keyboards being processed. There can be + * only one such device. + */ + } else { + goto ignore; + } + + options = input_option_new(options, "driver", hw_type.xdriver); + if (options == NULL) + goto error; + + if (asprintf(&config_info, "devd:%s", + skip_path_dev(devicename)) == -1) { + config_info = NULL; + goto error; + } + + if (device_is_duplicate(config_info)) + goto duplicate; + + options = input_option_new(options, "config_info", config_info); + if (options == NULL) + goto error; + + LogMessage(X_INFO, "config/devd: adding input device '%s'\n", + devicename); + + NewInputDeviceRequest(options, &attrs, &dev); + goto done; + +duplicate: + LogMessage(X_WARNING, "config/devd: device '%s' already " + "added. Ignoring\n", devicename); + goto done; + +error: + LogMessage(X_INFO, "config/devd: error adding device '%s'\n", + devicename); + goto done; + +ignore: + LogMessage(X_INFO, "config/devd: ignoring device '%s'\n", + devicename); + goto done; + +done: + free(config_info); + input_option_free_list(&options); + free(attrs.usb_id); + free(attrs.product); + free(attrs.device); + free(attrs.vendor); + free(vendor); +} + +static void +devpath_scan_sub(char *path, int off, int rem) +{ + struct dirent *entry; + DIR *dp; + + if ((dp = opendir(path)) == NULL) { + LogMessage(X_INFO, "Cannot open directory '%s'\n", path); + return; + } + while ((entry = readdir(dp)) != NULL) { + int len = strlen(entry->d_name); + if (len > rem) + continue; + strcpy(path + off, entry->d_name); + off += len; + rem -= len; + switch (entry->d_type) { + case DT_DIR: + if (strcmp(entry->d_name, ".") == 0 || + strcmp(entry->d_name, "..") == 0) + break; + if (rem < 1) + break; + path[off] = '/'; + path[off+1] = '\0'; + off++; + rem--; + /* recurse */ + devpath_scan_sub(path, off, rem); + off--; + rem++; + break; + case DT_SOCK: + case DT_FIFO: + case DT_LNK: + case DT_CHR: + /* add device, if any */ + device_added(path, false); + break; + default: + break; + } + off -= len; + rem += len; + } + closedir(dp); +} + +static void +devpath_scan(void) +{ + char path[PATH_MAX + 1]; + + strlcpy(path, _PATH_DEV, sizeof(path)); + + devpath_scan_sub(path, strlen(path), PATH_MAX - strlen(path)); +} + +static void +device_removed(char *devicename) +{ + char *config_info; + + if (asprintf(&config_info, "devd:%s", + skip_path_dev(devicename)) == -1) + return; + + if (device_is_duplicate(config_info)) { + LogMessage(X_INFO, "config/devd: removing input device '%s'\n", + devicename); + } + remove_devices("devd", config_info); + + free(config_info); +} + +static bool is_kbdmux_enabled(void) +{ + /* Xorg uses /dev/ttyv0 as a console device */ + static const char device[]= { _PATH_DEV "ttyv0" }; + keyboard_info_t info; + int fd; + + fd = open(device, O_RDONLY); + + if (fd < 0) + return false; + + if (ioctl(fd, KDGKBINFO, &info) == -1) { + close(fd); + return false; + } + + close(fd); + + if (!strncmp(info.kb_name, "kbdmux", 6)) + return true; + + return false; +} + +static void +disconnect_devd(int sock) +{ + if (sock >= 0) { + RemoveGeneralSocket(sock); + close(sock); + } +} + +static int +connect_devd(void) +{ + struct sockaddr_un devd; + int sock; + + sock = socket(AF_UNIX, SOCK_STREAM, 0); + if (sock < 0) { + LogMessage(X_ERROR, "config/devd: fail opening stream socket\n"); + return -1; + } + + devd.sun_family = AF_UNIX; + strlcpy(devd.sun_path, DEVD_SOCK_PATH, sizeof(devd.sun_path)); + + if (connect(sock, (struct sockaddr *) &devd, sizeof(devd)) < 0) { + close(sock); + LogMessage(X_ERROR, "config/devd: fail to connect to devd\n"); + return -1; + } + + AddGeneralSocket(sock); + + return sock; +} + +static CARD32 +reconnect_handler(OsTimerPtr timer, CARD32 time, pointer arg) +{ + int newsock; + + if ((newsock = connect_devd()) > 0) { + sock_devd = newsock; + TimerFree(rtimer); + rtimer = NULL; + LogMessage(X_INFO, "config/devd: reopening devd socket\n"); + return 0; + } + + /* Try again after RECONNECT_DELAY */ + return RECONNECT_DELAY; +} + +static ssize_t +socket_getline(int fd, char **out) +{ + char *buf, *newbuf; + ssize_t ret, cap, sz = 0; + char c; + + cap = 1024; + buf = malloc(cap * sizeof(char)); + if (!buf) + return -1; + + for (;;) { + ret = read(sock_devd, &c, 1); + if (ret < 0) { + if (errno == EINTR) + continue; + free(buf); + return -1; + /* EOF - devd socket is lost */ + } else if (ret == 0) { + disconnect_devd(sock_devd); + rtimer = TimerSet(NULL, 0, 1, reconnect_handler, NULL); + LogMessage(X_WARNING, "config/devd: devd socket is lost\n"); + return -1; + } + if (c == '\n') + break; + + if (sz + 1 >= cap) { + cap *= 2; + newbuf = realloc(buf, cap * sizeof(char)); + if (!newbuf) { + free(buf); + return -1; + } + buf = newbuf; + } + buf[sz] = c; + sz++; + } + + buf[sz] = '\0'; + if (sz >= 0) + *out = buf; + else + free(buf); + + /* Number of bytes in the line, not counting the line break */ + return sz; +} + +static void +wakeup_handler(void *data, int err, void *read_mask) +{ + static const char cdev_create[] = { "!system=DEVFS subsystem=CDEV type=CREATE cdev=" }; + static const char cdev_destroy[] = { "!system=DEVFS subsystem=CDEV type=DESTROY cdev=" }; + static const char cdev_path[] = { _PATH_DEV }; + char *line = NULL; + char *devicename; + char *walk; + + if (err < 0) + return; + + if (FD_ISSET(sock_devd, (fd_set *) read_mask)) { + if (socket_getline(sock_devd, &line) < 0) + return; + if (strstr(line, cdev_create) == line) { + devicename = line + strlen(cdev_create) - strlen(cdev_path); + memcpy(devicename, cdev_path, strlen(cdev_path)); + walk = strchr(devicename, ' '); + if (walk != NULL) + walk[0] = '\0'; + device_added(devicename, false); + } else if (strstr(line, cdev_destroy) == line) { + devicename = line + strlen(cdev_destroy) - strlen(cdev_path); + memcpy(devicename, cdev_path, strlen(cdev_path)); + walk = strchr(devicename, ' '); + if (walk != NULL) + walk[0] = '\0'; + device_removed(devicename); + } + free(line); + } +} + +static void +block_handler(void *data, struct timeval **tv, void *read_mask) +{ +} + +int +config_devd_init(void) +{ + LogMessage(X_INFO, "config/devd: probing input devices...\n"); + + /* Check if kbdmux is enabled */ + is_kbdmux = is_kbdmux_enabled(); + + /* Try to add kbdmux device first */ + if (is_kbdmux) + device_added(_PATH_DEV "kbdmux0", true); + + /* Connect to devd, so that we don't loose any events */ + if ((sock_devd = connect_devd()) < 0) + return 0; + + /* Scan what is currently connected */ + devpath_scan(); + + /* Register wakeup handler */ + RegisterBlockAndWakeupHandlers(block_handler, + wakeup_handler, NULL); + + return 1; +} + +void +config_devd_fini(void) +{ + LogMessage(X_INFO, "config/devd: terminating backend...\n"); + + if (rtimer) { + TimerFree(rtimer); + rtimer = NULL; + } + + disconnect_devd(sock_devd); + + RemoveBlockAndWakeupHandlers(block_handler, + wakeup_handler, NULL); +}