#define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include #include #include "utils.h" #include "linux-input-keycodes.h" #include "rcm-server-main.h" #include "rcm-server-keymap.h" enum ini_section { INI_SECTION_UNDEFINED, INI_SECTION_DESCRIPTION, INI_SECTION_KEYMAP, INI_SECTION_LAYOUT, }; struct inis { const char *name; enum ini_section value; }; struct inis iniss[] = { { .name = "Description", .value = INI_SECTION_DESCRIPTION }, { .name = "Keymap", .value = INI_SECTION_KEYMAP }, { .name = "Layout", .value = INI_SECTION_LAYOUT }, { .name = NULL, .value = INI_SECTION_UNDEFINED }, }; static int strtol_strict(const char *str, int *result) { char *end; long val; errno = 0; val = strtol(str, &end, 10); if (errno == ERANGE && (val == LONG_MAX || val == LONG_MIN)) return -EINVAL; if (errno != 0 && val == 0) return -EINVAL; if (end == str) return -EINVAL; if (*end != '\0') return -EINVAL; if (val < 1 || val > UINT16_MAX) return -EINVAL; *result = val; return 0; } static int strtoull_strict(const char *str, uint64_t *result) { char *end; unsigned long long val; errno = 0; val = strtoull(str, &end, 16); if (errno == ERANGE && (val == ULLONG_MAX || val == 0)) return -EINVAL; if (errno != 0 && val == 0) return -EINVAL; if (end == str) return -EINVAL; if (*end != '\0') return -EINVAL; if (val > UINT64_MAX) return -EINVAL; *result = val; return 0; } static int keymap_parse(FILE *fp, char **line, size_t *buf_size, struct keymap *keymap, uint16_t *rows_return, uint16_t *cols_return, unsigned *keycode_count_return) { unsigned keycode_count = 0; unsigned layout_count = 0; int rows = -1; int cols = -1; unsigned i; enum ini_section current_section = INI_SECTION_UNDEFINED; int r; rewind(fp); while (getline(line, buf_size, fp) != -1) { char *p, *tmp; p = strchr(*line, '\n'); if (!p) continue; *p = '\0'; p = *line; while (isspace(*p)) p++; if (*p == '\0' || *p == ';' || *p == '#') continue; if (*p == '[') { p++; tmp = strchr(p, ']'); if (!tmp) continue; *tmp = '\0'; for (i = 0; iniss[i].name; i++) { if (strcasecmp(iniss[i].name, p)) continue; current_section = iniss[i].value; break; } if (!iniss[i].name) current_section = INI_SECTION_UNDEFINED; continue; } tmp = strchr(p, '='); if (!tmp) continue; *tmp++ = '\0'; while (isspace(*tmp)) tmp++; if (*tmp == '\0') continue; /* FIXME: eat RHS whitespace for p */ switch (current_section) { case INI_SECTION_DESCRIPTION: if (!strcasecmp(p, "Rows")) { if (rows >= 0) { fprintf(stderr, "Multiple rows specifications\n"); return -EINVAL; } r = strtol_strict(tmp, &rows); if (r < 0) { fprintf(stderr, "Invalid rows specification\n"); return -EINVAL; } } else if (!strcasecmp(p, "Cols")) { if (cols >= 0) { fprintf(stderr, "Multiple cols specifications\n"); return -EINVAL; } r = strtol_strict(tmp, &cols); if (r < 0) { fprintf(stderr, "Invalid cols specification\n"); return -EINVAL; } } else if (!strcasecmp(p, "Name")) { if (keymap) { keymap->name = strdup(tmp); if (!keymap->name) return -ENOMEM; } } else if (!strcasecmp(p, "Description")) { if (keymap) { keymap->description = strdup(tmp); if (!keymap->description) return -ENOMEM; } } break; case INI_SECTION_KEYMAP: if (!strcasecmp(p, "Map")) { char *protocol; char *scanstr; char *keycode; char *end; uint64_t scancode; protocol = strtok(tmp, " :"); scanstr = strtok(NULL, " :"); keycode = strtok(NULL, " :"); end = strtok(NULL, " :"); if (!protocol || !scancode || !keycode || end) { fprintf(stderr, "Invalid map directive\n"); return -ENOMEM; } r = strtoull_strict(scanstr, &scancode); if (r < 0) { fprintf(stderr, "Invalid scancode value\n"); return r; } for (i = 0; linux_input_keycodes[i].name; i++) { if (!strcmp(keycode, linux_input_keycodes[i].name)) break; } if (!linux_input_keycodes[i].name || linux_input_keycodes[i].value < 1) { fprintf(stderr, "Invalid keycode name: %s\n", keycode); return -EINVAL; } if (keymap) { /* FIXME: leaks */ keymap->keycodes[keycode_count].protocol = strdup(protocol); keymap->keycodes[keycode_count].scancode = scancode; keymap->keycodes[keycode_count].keycode = strdup(keycode); } keycode_count++; } else { fprintf(stderr, "Unknown parameter: %s\n", p); } break; case INI_SECTION_LAYOUT: if (!strcasecmp(p, "Button")) { if (keymap) { if (!strcasecmp(tmp, "Blank")) keymap->layout[layout_count] = NULL; else { for (i = 0; i < keycode_count; i++) if (!strcasecmp(tmp, keymap->keycodes[i].keycode)) break; if (i >= keycode_count) { fprintf(stderr, "Invalid button keycode: %s\n", tmp); return -EINVAL; } keymap->layout[layout_count] = &keymap->keycodes[i]; } } layout_count++; } else { fprintf(stderr, "Unknown parameter: %s\n", p); } break; case INI_SECTION_UNDEFINED: break; default: printf("Line: %i - %s = %s\n", current_section, p, tmp); break; } } if (rows < 0) { fprintf(stderr, "Rows specification missing\n"); return -EINVAL; } if (cols < 0) { fprintf(stderr, "Cols specification missing\n"); return -EINVAL; } if (keycode_count < 1) { fprintf(stderr, "No keycodes found\n"); return -EINVAL; } if (layout_count < 1) { fprintf(stderr, "No layout found\n"); return -EINVAL; } if (rows * cols != layout_count) { fprintf(stderr, "Layout (%u) does not match rows x cols (%ux%u)\n", layout_count, rows, cols); return -EINVAL; } if (keymap) { keymap->rows = rows; keymap->cols = cols; keymap->keycode_count = keycode_count; } if (rows_return) *rows_return = rows; if (cols_return) *cols_return = cols; if (keycode_count_return) *keycode_count_return = keycode_count; return 0; } static struct keymap * keymap_read(int dfd, const char *path, const char *filename) { _cleanup_close_ int fd = -1; _cleanup_fclose_ FILE *fp = NULL; _cleanup_free_ char *line = NULL; struct keymap *keymap = NULL; uint16_t cols, rows; unsigned keycode_count; size_t buf_size = 0; char id[strlen(path) + strlen("/") + strlen(filename) + 1]; int r; if (dfd < 0 || !filename) goto out; fd = openat(dfd, filename, O_RDONLY); if (fd < 0) goto out; fp = fdopen(fd, "r"); if (!fp) goto out; fd = -1; sprintf(id, "%s/%s", path, filename); printf("Parsing keymap %s...\n", id); r = keymap_parse(fp, &line, &buf_size, NULL, &rows, &cols, &keycode_count); if (r < 0) goto out; keymap = zmalloc(sizeof(*keymap) + sizeof(struct keycode) * keycode_count); if (!keymap) goto out; keymap->id = strdup(id); if (!keymap->id) goto out; keymap->layout = zmalloc(sizeof(struct keycode *) * cols * rows); if (!keymap->layout) goto out; r = keymap_parse(fp, &line, &buf_size, keymap, NULL, NULL, NULL); if (r < 0) goto out; printf("\tKeymap added\n"); printf("\tID: %s\n", keymap->id); printf("\tName: %s\n", keymap->name); printf("\tDescription: %s\n", keymap->description); printf("\tMappings: %u entries\n", keycode_count); printf("\tLayout: %u rows x %u cols = %u entries\n", rows, cols, rows * cols); return keymap; out: if (keymap) keymap_free(keymap); return NULL; } static int keymaps_load_dir(struct device *device, int pfd, const char *path) { _cleanup_closedir_ DIR *dir = NULL; int dfd; struct dirent *dent; struct keymap *keymap; dfd = openat(pfd, path, O_RDONLY | O_DIRECTORY); if (dfd < 0) return -errno; dir = fdopendir(dfd); if (!dir) { close(dfd); return -errno; } while ((dent = readdir(dir))) { switch (dent->d_type) { case DT_REG: case DT_LNK: case DT_UNKNOWN: keymap = find_keymap_by_id(device, dent->d_name); if (keymap) continue; keymap = keymap_read(dfd, path, dent->d_name); if (keymap) list_add(&keymap->list, &device->keymaps); break; default: break; } } return 0; } int keymaps_load(struct device *device) { char pdpath[strlen("per-device/") + strlen(device->name) + 1]; _cleanup_close_ int dfd; sprintf(pdpath, "per-device/%s", device->name); /* FIXME: This is just a temporary location */ dfd = open("./keymaps", O_PATH | O_DIRECTORY); if (dfd < 0) return -errno; printf("Loading per-device keymaps...\n"); keymaps_load_dir(device, dfd, pdpath); printf("Loading default keymaps...\n"); keymaps_load_dir(device, dfd, "default"); return 0; } int keymap_write(struct keymap *keymap) { _cleanup_close_ int dfd = -1; _cleanup_close_ int fd = -1; _cleanup_fclose_ FILE *file = NULL; char *separator; unsigned i; separator = strrchr(keymap->id, '/'); if (!separator) return -EINVAL; /* FIXME: This is just a temporary location */ dfd = open("./keymaps", O_PATH | O_DIRECTORY); if (dfd < 0) return -errno; *separator = '\0'; fd = openat(dfd, keymap->id, O_TMPFILE | O_WRONLY, 0644); *separator = '/'; if (fd < 0) return -errno; /* https://sourceware.org/bugzilla/show_bug.cgi?id=17523 */ if (fchmod(fd, 0644) < 0) return -errno; int fdpath_len; fdpath_len = snprintf(NULL, 0, "/proc/self/fd/%i", fd); if (fdpath_len < 0) return -errno; char fdpath[fdpath_len + 1]; sprintf(fdpath, "/proc/self/fd/%d", fd); file = fdopen(fd, "w"); if (!file) return -errno; else fd = -1; fprintf(file, "[Description]\n"); fprintf(file, "Name=%s\n", keymap->name); fprintf(file, "Description=%s\n", keymap->description); fprintf(file, "Rows=%u\n", keymap->rows); fprintf(file, "Cols=%u\n", keymap->cols); fprintf(file, "\n"); fprintf(file, "[Keymap]\n"); for (i = 0; i < keymap->keycode_count; i++) { fprintf(file, "Map=%s:0x%08" PRIx64 ":%s\n", keymap->keycodes[i].protocol, keymap->keycodes[i].scancode, keymap->keycodes[i].keycode); } fprintf(file, "\n"); fprintf(file, "[Layout]\n"); for (i = 0; i < (keymap->rows * keymap->cols); i++) { if (keymap->layout[i]) fprintf(file, "Button=%s\n", keymap->layout[i]->keycode); else fprintf(file, "Button=Blank\n"); } fflush(file); if (ferror(file)) return errno ? -errno : -EIO; char tmppath[strlen(keymap->id) + strlen(".tmp") + 1]; sprintf(tmppath, "%s.tmp", keymap->id); if (linkat(AT_FDCWD, fdpath, dfd, tmppath, AT_SYMLINK_FOLLOW) < 0) { printf("linkat failed: %i - %s\n", errno, strerror(errno)); return -errno; } if (renameat(dfd, tmppath, dfd, keymap->id) < 0) { int r = -errno; unlinkat(dfd, tmppath, 0); return r; } return 0; } void keymap_free(struct keymap *keymap) { if (!keymap) return; free(keymap->layout); free(keymap->name); free(keymap->description); free(keymap->id); free(keymap); } struct keymap * find_keymap_by_id(struct device *dev, const char *id) { struct keymap *keymap; list_for_each_entry(keymap, &dev->keymaps, list) if (!strcmp(keymap->id, id)) return keymap; return NULL; }