/* * Copyright © 2018 Red Hat, Inc. * * 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. */ #include "config.h" /* This has the hallmarks of a library to make it re-usable from the tests * and from the list-quirks tool. It doesn't have all of the features from a * library you'd expect though */ #undef NDEBUG /* You don't get to disable asserts here */ #include #include #include #include #include #include "libinput-versionsort.h" #include "libinput-util.h" #include "libinput-private.h" #include "quirks.h" /* Custom logging so we can have detailed output for the tool but minimal * logging for libinput itself. */ #define qlog_debug(ctx_, ...) quirk_log_msg((ctx_), QLOG_NOISE, __VA_ARGS__) #define qlog_info(ctx_, ...) quirk_log_msg((ctx_), QLOG_INFO, __VA_ARGS__) #define qlog_error(ctx_, ...) quirk_log_msg((ctx_), QLOG_ERROR, __VA_ARGS__) #define qlog_parser(ctx_, ...) quirk_log_msg((ctx_), QLOG_PARSER_ERROR, __VA_ARGS__) enum property_type { PT_UINT, PT_INT, PT_STRING, PT_BOOL, PT_DIMENSION, PT_RANGE, PT_DOUBLE, }; /** * Generic value holder for the property types we support. The type * identifies which value in the union is defined and we expect callers to * already know which type yields which value. */ struct property { size_t refcount; struct list link; /* struct sections.properties */ enum quirk id; enum property_type type; union { bool b; uint32_t u; int32_t i; char *s; struct quirk_dimensions dim; struct quirk_range range; double d; } value; }; enum match_flags { M_NAME = (1 << 0), M_BUS = (1 << 1), M_VID = (1 << 2), M_PID = (1 << 3), M_DMI = (1 << 4), M_UDEV_TYPE = (1 << 5), M_DT = (1 << 6), M_VERSION = (1 << 7), M_LAST = M_VERSION, }; enum bustype { BT_UNKNOWN, BT_USB, BT_BLUETOOTH, BT_PS2, BT_RMI, BT_I2C, }; enum udev_type { UDEV_MOUSE = (1 << 1), UDEV_POINTINGSTICK = (1 << 2), UDEV_TOUCHPAD = (1 << 3), UDEV_TABLET = (1 << 4), UDEV_TABLET_PAD = (1 << 5), UDEV_JOYSTICK = (1 << 6), UDEV_KEYBOARD = (1 << 7), }; /** * Contains the combined set of matches for one section or the values for * one device. * * bits defines which fields are set, the rest is zero. */ struct match { uint32_t bits; char *name; enum bustype bus; uint32_t vendor; uint32_t product; uint32_t version; char *dmi; /* dmi modalias with preceding "dmi:" */ /* We can have more than one type set, so this is a bitfield */ uint32_t udev_type; char *dt; /* device tree compatible (first) string */ }; /** * Represents one section in the .quirks file. */ struct section { struct list link; bool has_match; /* to check for empty sections */ bool has_property; /* to check for empty sections */ char *name; /* the [Section Name] */ struct match match; struct list properties; }; /** * The struct returned to the caller. It contains the * properties for a given device. */ struct quirks { size_t refcount; struct list link; /* struct quirks_context.quirks */ /* These are not ref'd, just a collection of pointers */ struct property **properties; size_t nproperties; }; /** * Quirk matching context, initialized once with quirks_init_subsystem() */ struct quirks_context { size_t refcount; libinput_log_handler log_handler; enum quirks_log_type log_type; struct libinput *libinput; /* for logging */ char *dmi; char *dt; struct list sections; /* list of quirks handed to libinput, just for bookkeeping */ struct list quirks; }; LIBINPUT_ATTRIBUTE_PRINTF(3, 0) static inline void quirk_log_msg_va(struct quirks_context *ctx, enum quirks_log_priorities priority, const char *format, va_list args) { switch (priority) { /* We don't use this if we're logging through libinput */ default: case QLOG_NOISE: case QLOG_PARSER_ERROR: if (ctx->log_type == QLOG_LIBINPUT_LOGGING) return; break; case QLOG_DEBUG: /* These map straight to libinput priorities */ case QLOG_INFO: case QLOG_ERROR: break; } ctx->log_handler(ctx->libinput, (enum libinput_log_priority)priority, format, args); } LIBINPUT_ATTRIBUTE_PRINTF(3, 4) static inline void quirk_log_msg(struct quirks_context *ctx, enum quirks_log_priorities priority, const char *format, ...) { va_list args; va_start(args, format); quirk_log_msg_va(ctx, priority, format, args); va_end(args); } const char * quirk_get_name(enum quirk q) { switch(q) { case QUIRK_MODEL_ALPS_TOUCHPAD: return "ModelALPSTouchpad"; case QUIRK_MODEL_APPLE_TOUCHPAD: return "ModelAppleTouchpad"; case QUIRK_MODEL_APPLE_MAGICMOUSE: return "ModelAppleMagicMouse"; case QUIRK_MODEL_TABLET_NO_TILT: return "ModelTabletNoTilt"; case QUIRK_MODEL_APPLE_TOUCHPAD_ONEBUTTON: return "ModelAppleTouchpadOneButton"; case QUIRK_MODEL_TOUCHPAD_VISIBLE_MARKER: return "ModelTouchpadVisibleMarker"; case QUIRK_MODEL_CYBORG_RAT: return "ModelCyborgRat"; case QUIRK_MODEL_CHROMEBOOK: return "ModelChromebook"; case QUIRK_MODEL_HP6910_TOUCHPAD: return "ModelHP6910Touchpad"; case QUIRK_MODEL_HP8510_TOUCHPAD: return "ModelHP8510Touchpad"; case QUIRK_MODEL_HP_PAVILION_DM4_TOUCHPAD: return "ModelHPPavilionDM4Touchpad"; case QUIRK_MODEL_HP_STREAM11_TOUCHPAD: return "ModelHPStream11Touchpad"; case QUIRK_MODEL_HP_ZBOOK_STUDIO_G3: return "ModelHPZBookStudioG3"; case QUIRK_MODEL_TABLET_NO_PROXIMITY_OUT: return "ModelTabletNoProximityOut"; case QUIRK_MODEL_LENOVO_SCROLLPOINT: return "ModelLenovoScrollPoint"; case QUIRK_MODEL_LENOVO_X230: return "ModelLenovoX230"; case QUIRK_MODEL_LENOVO_T450_TOUCHPAD: return "ModelLenovoT450Touchpad"; case QUIRK_MODEL_TABLET_MODE_NO_SUSPEND: return "ModelTabletModeNoSuspend"; case QUIRK_MODEL_LENOVO_CARBON_X1_6TH: return "ModelLenovoCarbonX16th"; case QUIRK_MODEL_TRACKBALL: return "ModelTrackball"; case QUIRK_MODEL_LOGITECH_MARBLE_MOUSE: return "ModelLogitechMarbleMouse"; case QUIRK_MODEL_BOUNCING_KEYS: return "ModelBouncingKeys"; case QUIRK_MODEL_SYNAPTICS_SERIAL_TOUCHPAD: return "ModelSynapticsSerialTouchpad"; case QUIRK_MODEL_SYSTEM76_BONOBO: return "ModelSystem76Bonobo"; case QUIRK_MODEL_CLEVO_W740SU: return "ModelClevoW740SU"; case QUIRK_MODEL_SYSTEM76_GALAGO: return "ModelSystem76Galago"; case QUIRK_MODEL_SYSTEM76_KUDU: return "ModelSystem76Kudu"; case QUIRK_MODEL_WACOM_TOUCHPAD: return "ModelWacomTouchpad"; case QUIRK_ATTR_SIZE_HINT: return "AttrSizeHint"; case QUIRK_ATTR_TOUCH_SIZE_RANGE: return "AttrTouchSizeRange"; case QUIRK_ATTR_PALM_SIZE_THRESHOLD: return "AttrPalmSizeThreshold"; case QUIRK_ATTR_LID_SWITCH_RELIABILITY: return "AttrLidSwitchReliability"; case QUIRK_ATTR_KEYBOARD_INTEGRATION: return "AttrKeyboardIntegration"; case QUIRK_ATTR_TPKBCOMBO_LAYOUT: return "AttrTPKComboLayout"; case QUIRK_ATTR_PRESSURE_RANGE: return "AttrPressureRange"; case QUIRK_ATTR_PALM_PRESSURE_THRESHOLD: return "AttrPalmPressureThreshold"; case QUIRK_ATTR_RESOLUTION_HINT: return "AttrResolutionHint"; case QUIRK_ATTR_TRACKPOINT_MULTIPLIER: return "AttrTrackpointMultiplier"; case QUIRK_ATTR_THUMB_PRESSURE_THRESHOLD: return "AttrThumbPressureThreshold"; default: abort(); } } static inline const char * matchflagname(enum match_flags f) { switch(f) { case M_NAME: return "MatchName"; break; case M_BUS: return "MatchBus"; break; case M_VID: return "MatchVendor"; break; case M_PID: return "MatchProduct"; break; case M_VERSION: return "MatchVersion"; break; case M_DMI: return "MatchDMIModalias"; break; case M_UDEV_TYPE: return "MatchUdevType"; break; case M_DT: return "MatchDeviceTree"; break; default: abort(); } }; static inline struct property * property_new(void) { struct property *p; p = zalloc(sizeof *p); p->refcount = 1; list_init(&p->link); return p; } static inline struct property * property_ref(struct property *p) { assert(p->refcount > 0); p->refcount++; return p; }; static inline struct property * property_unref(struct property *p) { /* Note: we don't cleanup here, that is a separate call so we can abort if we haven't cleaned up correctly. */ assert(p->refcount > 0); p->refcount--; return NULL; }; /* Separate call so we can verify that the caller unrefs the property * before shutting down the subsystem. */ static inline void property_cleanup(struct property *p) { /* If we get here, the quirks must've been removed already */ property_unref(p); assert(p->refcount == 0); list_remove(&p->link); if (p->type == PT_STRING) free(p->value.s); free(p); } /** * Return the dmi modalias from the udev device. */ static inline char * init_dmi(void) { struct udev *udev; struct udev_device *udev_device; const char *modalias = NULL; char *copy = NULL; const char *syspath = "/sys/devices/virtual/dmi/id"; if (getenv("LIBINPUT_RUNNING_TEST_SUITE")) return safe_strdup("dmi:"); udev = udev_new(); if (!udev) return NULL; udev_device = udev_device_new_from_syspath(udev, syspath); if (udev_device) modalias = udev_device_get_property_value(udev_device, "MODALIAS"); /* Not sure whether this could ever really fail, if so we should * open the sysfs file directly. But then udev wouldn't have failed, * so... */ if (!modalias) modalias = "dmi:*"; copy = safe_strdup(modalias); udev_device_unref(udev_device); udev_unref(udev); return copy; } /** * Return the dt compatible string */ static inline char * init_dt(void) { char compatible[1024]; char *copy = NULL; const char *syspath = "/sys/firmware/devicetree/base/compatible"; FILE *fp; if (getenv("LIBINPUT_RUNNING_TEST_SUITE")) return safe_strdup(""); fp = fopen(syspath, "r"); if (!fp) return NULL; /* devicetree/base/compatible has multiple null-terminated entries but we only care about the first one here, so strdup is enough */ if (fgets(compatible, sizeof(compatible), fp)) { copy = safe_strdup(compatible); } fclose(fp); return copy; } static inline struct section * section_new(const char *path, const char *name) { struct section *s = zalloc(sizeof(*s)); xasprintf(&s->name, "%s (%s)", name, basename(path)); list_init(&s->link); list_init(&s->properties); return s; } static inline void section_destroy(struct section *s) { struct property *p, *tmp; free(s->name); free(s->match.name); free(s->match.dmi); free(s->match.dt); list_for_each_safe(p, tmp, &s->properties, link) property_cleanup(p); assert(list_empty(&s->properties)); list_remove(&s->link); free(s); } /** * Parse a MatchFooBar=banana line. * * @param section The section struct to be filled in * @param key The MatchFooBar part of the line * @param value The banana part of the line. * * @return true on success, false otherwise. */ static bool parse_match(struct quirks_context *ctx, struct section *s, const char *key, const char *value) { int rc = false; #define check_set_bit(s_, bit_) { \ if ((s_)->match.bits & (bit_)) goto out; \ (s_)->match.bits |= (bit_); \ } assert(strlen(value) >= 1); if (streq(key, "MatchName")) { check_set_bit(s, M_NAME); s->match.name = safe_strdup(value); } else if (streq(key, "MatchBus")) { check_set_bit(s, M_BUS); if (streq(value, "usb")) s->match.bus = BT_USB; else if (streq(value, "bluetooth")) s->match.bus = BT_BLUETOOTH; else if (streq(value, "ps2")) s->match.bus = BT_PS2; else if (streq(value, "rmi")) s->match.bus = BT_RMI; else if (streq(value, "i2c")) s->match.bus = BT_I2C; else goto out; } else if (streq(key, "MatchVendor")) { unsigned int vendor; check_set_bit(s, M_VID); if (!strneq(value, "0x", 2) || !safe_atou_base(value, &vendor, 16) || vendor > 0xFFFF) goto out; s->match.vendor = vendor; } else if (streq(key, "MatchProduct")) { unsigned int product; check_set_bit(s, M_PID); if (!strneq(value, "0x", 2) || !safe_atou_base(value, &product, 16) || product > 0xFFFF) goto out; s->match.product = product; } else if (streq(key, "MatchVersion")) { unsigned int version; check_set_bit(s, M_VERSION); if (!strneq(value, "0x", 2) || !safe_atou_base(value, &version, 16) || version > 0xFFFF) goto out; s->match.version = version; } else if (streq(key, "MatchDMIModalias")) { check_set_bit(s, M_DMI); if (!strneq(value, "dmi:", 4)) { qlog_parser(ctx, "%s: MatchDMIModalias must start with 'dmi:'\n", s->name); goto out; } s->match.dmi = safe_strdup(value); } else if (streq(key, "MatchUdevType")) { check_set_bit(s, M_UDEV_TYPE); if (streq(value, "touchpad")) s->match.udev_type = UDEV_TOUCHPAD; else if (streq(value, "mouse")) s->match.udev_type = UDEV_MOUSE; else if (streq(value, "pointingstick")) s->match.udev_type = UDEV_POINTINGSTICK; else if (streq(value, "keyboard")) s->match.udev_type = UDEV_KEYBOARD; else if (streq(value, "joystick")) s->match.udev_type = UDEV_JOYSTICK; else if (streq(value, "tablet")) s->match.udev_type = UDEV_TABLET; else if (streq(value, "tablet-pad")) s->match.udev_type = UDEV_TABLET_PAD; else goto out; } else if (streq(key, "MatchDeviceTree")) { check_set_bit(s, M_DT); s->match.dt = safe_strdup(value); } else { qlog_error(ctx, "Unknown match key '%s'\n", key); goto out; } #undef check_set_bit s->has_match = true; rc = true; out: return rc; } /** * Parse a ModelFooBar=1 line. * * @param section The section struct to be filled in * @param key The ModelFooBar part of the line * @param value The value after the =, must be 1 or 0. * * @return true on success, false otherwise. */ static bool parse_model(struct quirks_context *ctx, struct section *s, const char *key, const char *value) { enum quirk quirks[] = { QUIRK_MODEL_ALPS_TOUCHPAD, QUIRK_MODEL_APPLE_TOUCHPAD, QUIRK_MODEL_APPLE_MAGICMOUSE, QUIRK_MODEL_TABLET_NO_TILT, QUIRK_MODEL_APPLE_TOUCHPAD_ONEBUTTON, QUIRK_MODEL_TOUCHPAD_VISIBLE_MARKER, QUIRK_MODEL_CYBORG_RAT, QUIRK_MODEL_CHROMEBOOK, QUIRK_MODEL_HP6910_TOUCHPAD, QUIRK_MODEL_HP8510_TOUCHPAD, QUIRK_MODEL_HP_PAVILION_DM4_TOUCHPAD, QUIRK_MODEL_HP_STREAM11_TOUCHPAD, QUIRK_MODEL_HP_ZBOOK_STUDIO_G3, QUIRK_MODEL_TABLET_NO_PROXIMITY_OUT, QUIRK_MODEL_LENOVO_SCROLLPOINT, QUIRK_MODEL_LENOVO_X230, QUIRK_MODEL_LENOVO_T450_TOUCHPAD, QUIRK_MODEL_TABLET_MODE_NO_SUSPEND, QUIRK_MODEL_LENOVO_CARBON_X1_6TH, QUIRK_MODEL_TRACKBALL, QUIRK_MODEL_LOGITECH_MARBLE_MOUSE, QUIRK_MODEL_BOUNCING_KEYS, QUIRK_MODEL_SYNAPTICS_SERIAL_TOUCHPAD, QUIRK_MODEL_SYSTEM76_BONOBO, QUIRK_MODEL_CLEVO_W740SU, QUIRK_MODEL_SYSTEM76_GALAGO, QUIRK_MODEL_SYSTEM76_KUDU, QUIRK_MODEL_WACOM_TOUCHPAD, }; bool b; enum quirk *q; assert(strneq(key, "Model", 5)); if (streq(value, "1")) b = true; else if (streq(value, "0")) b = false; else return false; ARRAY_FOR_EACH(quirks, q) { if (streq(key, quirk_get_name(*q))) { struct property *p = property_new(); p->id = *q, p->type = PT_BOOL; p->value.b = b; list_append(&s->properties, &p->link); s->has_property = true; return true; } } qlog_error(ctx, "Unknown key %s in %s\n", key, s->name); return false; } /** * Parse a AttrFooBar=banana line. * * @param section The section struct to be filled in * @param key The AttrFooBar part of the line * @param value The banana part of the line. * * Value parsing depends on the attribute type. * * @return true on success, false otherwise. */ static inline bool parse_attr(struct quirks_context *ctx, struct section *s, const char *key, const char *value) { struct property *p = property_new(); bool rc = false; struct quirk_dimensions dim; struct quirk_range range; unsigned int v; double d; if (streq(key, quirk_get_name(QUIRK_ATTR_SIZE_HINT))) { p->id = QUIRK_ATTR_SIZE_HINT; if (!parse_dimension_property(value, &dim.x, &dim.y)) goto out; p->type = PT_DIMENSION; p->value.dim = dim; rc = true; } else if (streq(key, quirk_get_name(QUIRK_ATTR_TOUCH_SIZE_RANGE))) { p->id = QUIRK_ATTR_TOUCH_SIZE_RANGE; if (!parse_range_property(value, &range.upper, &range.lower)) goto out; p->type = PT_RANGE; p->value.range = range; rc = true; } else if (streq(key, quirk_get_name(QUIRK_ATTR_PALM_SIZE_THRESHOLD))) { p->id = QUIRK_ATTR_PALM_SIZE_THRESHOLD; if (!safe_atou(value, &v)) goto out; p->type = PT_UINT; p->value.u = v; rc = true; } else if (streq(key, quirk_get_name(QUIRK_ATTR_LID_SWITCH_RELIABILITY))) { p->id = QUIRK_ATTR_LID_SWITCH_RELIABILITY; if (!streq(value, "reliable") && !streq(value, "write_open")) goto out; p->type = PT_STRING; p->value.s = safe_strdup(value); rc = true; } else if (streq(key, quirk_get_name(QUIRK_ATTR_KEYBOARD_INTEGRATION))) { p->id = QUIRK_ATTR_KEYBOARD_INTEGRATION; if (!streq(value, "internal") && !streq(value, "external")) goto out; p->type = PT_STRING; p->value.s = safe_strdup(value); rc = true; } else if (streq(key, quirk_get_name(QUIRK_ATTR_TPKBCOMBO_LAYOUT))) { p->id = QUIRK_ATTR_TPKBCOMBO_LAYOUT; if (!streq(value, "below")) goto out; p->type = PT_STRING; p->value.s = safe_strdup(value); rc = true; } else if (streq(key, quirk_get_name(QUIRK_ATTR_PRESSURE_RANGE))) { p->id = QUIRK_ATTR_PRESSURE_RANGE; if (!parse_range_property(value, &range.upper, &range.lower)) goto out; p->type = PT_RANGE; p->value.range = range; rc = true; } else if (streq(key, quirk_get_name(QUIRK_ATTR_PALM_PRESSURE_THRESHOLD))) { p->id = QUIRK_ATTR_PALM_PRESSURE_THRESHOLD; if (!safe_atou(value, &v)) goto out; p->type = PT_UINT; p->value.u = v; rc = true; } else if (streq(key, quirk_get_name(QUIRK_ATTR_RESOLUTION_HINT))) { p->id = QUIRK_ATTR_RESOLUTION_HINT; if (!parse_dimension_property(value, &dim.x, &dim.y)) goto out; p->type = PT_DIMENSION; p->value.dim = dim; rc = true; } else if (streq(key, quirk_get_name(QUIRK_ATTR_TRACKPOINT_MULTIPLIER))) { p->id = QUIRK_ATTR_TRACKPOINT_MULTIPLIER; if (!safe_atod(value, &d)) goto out; p->type = PT_DOUBLE; p->value.d = d; rc = true; } else if (streq(key, quirk_get_name(QUIRK_ATTR_THUMB_PRESSURE_THRESHOLD))) { p->id = QUIRK_ATTR_THUMB_PRESSURE_THRESHOLD; if (!safe_atou(value, &v)) goto out; p->type = PT_UINT; p->value.u = v; rc = true; } else { qlog_error(ctx, "Unknown key %s in %s\n", key, s->name); } out: if (rc) { list_append(&s->properties, &p->link); s->has_property = true; } else { property_cleanup(p); } return rc; } /** * Parse a single line, expected to be in the format Key=value. Anything * else will be rejected with a failure. * * Our data files can only have Match, Model and Attr, so let's check for * those too. */ static bool parse_value_line(struct quirks_context *ctx, struct section *s, const char *line) { char **strv; const char *key, *value; bool rc = false; strv = strv_from_string(line, "="); if (strv[0] == NULL || strv[1] == NULL || strv[2] != NULL) { goto out; } key = strv[0]; value = strv[1]; if (strlen(key) == 0 || strlen(value) == 0) goto out; /* Whatever the value is, it's not supposed to be in quotes */ if (value[0] == '"') goto out; if (strneq(key, "Match", 5)) rc = parse_match(ctx, s, key, value); else if (strneq(key, "Model", 5)) rc = parse_model(ctx, s, key, value); else if (strneq(key, "Attr", 4)) rc = parse_attr(ctx, s, key, value); else qlog_error(ctx, "Unknown value prefix %s\n", line); out: strv_free(strv); return rc; } static inline bool parse_file(struct quirks_context *ctx, const char *path) { enum state { STATE_SECTION, STATE_MATCH, STATE_MATCH_OR_VALUE, STATE_VALUE_OR_SECTION, STATE_ANY, }; FILE *fp; char line[512]; bool rc = false; enum state state = STATE_SECTION; struct section *section = NULL; int lineno = -1; qlog_debug(ctx, "%s\n", path); /* Not using open_restricted here, if we can't access * our own data files, our installation is screwed up. */ fp = fopen(path, "r"); if (!fp) { /* If the file doesn't exist that's fine. Only way this can * happen is for the custom override file, all others are * provided by scandir so they do exist. Short of races we * don't care about. */ if (errno == ENOENT) return true; qlog_error(ctx, "%s: failed to open file\n", path); goto out; } while (fgets(line, sizeof(line), fp)) { char *comment; lineno++; comment = strstr(line, "#"); if (comment) { /* comment points to # but we need to remove the * preceding whitespaces too */ comment--; while (comment >= line) { if (*comment != ' ' && *comment != '\t') break; comment--; } *(comment + 1) = '\0'; } else { /* strip the trailing newline */ comment = strstr(line, "\n"); if (comment) *comment = '\0'; } if (strlen(line) == 0) continue; /* We don't use quotes for strings, so we really don't want * erroneous trailing whitespaces */ switch (line[strlen(line) - 1]) { case ' ': case '\t': qlog_parser(ctx, "%s:%d: Trailing whitespace '%s'\n", path, lineno, line); goto out; } switch (line[0]) { case '\0': case '\n': case '#': break; /* white space not allowed */ case ' ': case '\t': qlog_parser(ctx, "%s:%d: Preceding whitespace '%s'\n", path, lineno, line); goto out; /* section title */ case '[': if (line[strlen(line) - 1] != ']') { qlog_parser(ctx, "%s:%d: Closing ] missing '%s'\n", path, lineno, line); goto out; } if (state != STATE_SECTION && state != STATE_VALUE_OR_SECTION) { qlog_parser(ctx, "%s:%d: expected section before %s\n", path, lineno, line); goto out; } if (section && (!section->has_match || !section->has_property)) { qlog_parser(ctx, "%s:%d: previous section %s was empty\n", path, lineno, section->name); goto out; /* Previous section was empty */ } state = STATE_MATCH; section = section_new(path, line); list_append(&ctx->sections, §ion->link); break; /* entries must start with A-Z */ case 'A'...'Z': switch (state) { case STATE_SECTION: qlog_parser(ctx, "%s:%d: expected [Section], got %s\n", path, lineno, line); goto out; case STATE_MATCH: if (!strneq(line, "Match", 5)) { qlog_parser(ctx, "%s:%d: expected MatchFoo=bar, have %s\n", path, lineno, line); goto out; } state = STATE_MATCH_OR_VALUE; break; case STATE_MATCH_OR_VALUE: if (!strneq(line, "Match", 5)) state = STATE_VALUE_OR_SECTION; break; case STATE_VALUE_OR_SECTION: if (strneq(line, "Match", 5)) { qlog_parser(ctx, "%s:%d: expected value or [Section], have %s\n", path, lineno, line); goto out; } break; case STATE_ANY: break; } if (!parse_value_line(ctx, section, line)) { qlog_parser(ctx, "%s:%d: failed to parse %s\n", path, lineno, line); goto out; } break; default: qlog_parser(ctx, "%s:%d: Unexpected line %s\n", path, lineno, line); goto out; } } if (!section) { qlog_parser(ctx, "%s: is an empty file\n", path); goto out; } if ((!section->has_match || !section->has_property)) { qlog_parser(ctx, "%s:%d: previous section %s was empty\n", path, lineno, section->name); goto out; /* Previous section was empty */ } rc = true; out: if (fp) fclose(fp); return rc; } static int is_data_file(const struct dirent *dir) { const char *suffix = ".quirks"; const int slen = strlen(suffix); int offset; offset = strlen(dir->d_name) - slen; if (offset <= 0) return 0; return strneq(&dir->d_name[offset], suffix, slen); } static inline bool parse_files(struct quirks_context *ctx, const char *data_path) { struct dirent **namelist; int ndev = -1; int idx = 0; ndev = scandir(data_path, &namelist, is_data_file, versionsort); if (ndev <= 0) { qlog_error(ctx, "%s: failed to find data files\n", data_path); return false; } for (idx = 0; idx < ndev; idx++) { char path[PATH_MAX]; snprintf(path, sizeof(path), "%s/%s", data_path, namelist[idx]->d_name); if (!parse_file(ctx, path)) break; } for (int i = 0; i < ndev; i++) free(namelist[i]); free(namelist); return idx == ndev; } struct quirks_context * quirks_init_subsystem(const char *data_path, const char *override_file, libinput_log_handler log_handler, struct libinput *libinput, enum quirks_log_type log_type) { struct quirks_context *ctx = zalloc(sizeof *ctx); assert(data_path); ctx->refcount = 1; ctx->log_handler = log_handler; ctx->log_type = log_type; ctx->libinput = libinput; list_init(&ctx->quirks); list_init(&ctx->sections); qlog_debug(ctx, "%s is data root\n", data_path); ctx->dmi = init_dmi(); ctx->dt = init_dt(); if (!ctx->dmi && !ctx->dt) goto error; if (!parse_files(ctx, data_path)) goto error; if (override_file && !parse_file(ctx, override_file)) goto error; return ctx; error: quirks_context_unref(ctx); return NULL; } struct quirks_context * quirks_context_ref(struct quirks_context *ctx) { assert(ctx->refcount > 0); ctx->refcount++; return ctx; } struct quirks_context * quirks_context_unref(struct quirks_context *ctx) { struct section *s, *tmp; if (!ctx) return NULL; assert(ctx->refcount >= 1); ctx->refcount--; if (ctx->refcount > 0) return NULL; /* Caller needs to clean up before calling this */ assert(list_empty(&ctx->quirks)); list_for_each_safe(s, tmp, &ctx->sections, link) { section_destroy(s); } free(ctx->dmi); free(ctx->dt); free(ctx); return NULL; } static struct quirks * quirks_new(void) { struct quirks *q; q = zalloc(sizeof *q); q->refcount = 1; q->nproperties = 0; list_init(&q->link); return q; } struct quirks * quirks_unref(struct quirks *q) { if (!q) return NULL; /* We don't really refcount, but might * as well have the API in place */ assert(q->refcount == 1); for (size_t i = 0; i < q->nproperties; i++) { property_unref(q->properties[i]); } list_remove(&q->link); free(q->properties); free(q); return NULL; } /** * Searches for the udev property on this device and its parent devices. * * @return the value of the property or NULL */ static const char * udev_prop(struct udev_device *device, const char *prop) { struct udev_device *d = device; const char *value = NULL; do { value = udev_device_get_property_value(d, prop); d = udev_device_get_parent(d); } while (value == NULL && d != NULL); return value; } static inline void match_fill_name(struct match *m, struct udev_device *device) { const char *str = udev_prop(device, "NAME"); size_t slen; if (!str) return; /* udev NAME is in quotes, strip them */ if (str[0] == '"') str++; m->name = safe_strdup(str); slen = strlen(m->name); if (slen > 1 && m->name[slen - 1] == '"') m->name[slen - 1] = '\0'; m->bits |= M_NAME; } static inline void match_fill_bus_vid_pid(struct match *m, struct udev_device *device) { const char *str; unsigned int product, vendor, bus, version; str = udev_prop(device, "PRODUCT"); if (!str) return; /* ID_VENDOR_ID/ID_PRODUCT_ID/ID_BUS aren't filled in for virtual * devices so we have to resort to PRODUCT */ if (sscanf(str, "%x/%x/%x/%x", &bus, &vendor, &product, &version) != 4) return; m->product = product; m->vendor = vendor; m->version = version; m->bits |= M_PID|M_VID|M_VERSION; switch (bus) { case BUS_USB: m->bus = BT_USB; m->bits |= M_BUS; break; case BUS_BLUETOOTH: m->bus = BT_BLUETOOTH; m->bits |= M_BUS; break; case BUS_I8042: m->bus = BT_PS2; m->bits |= M_BUS; break; default: break; } } static inline void match_fill_udev_type(struct match *m, struct udev_device *device) { struct ut_map { const char *prop; enum udev_type flag; } mappings[] = { { "ID_INPUT_MOUSE", UDEV_MOUSE }, { "ID_INPUT_POINTINGSTICK", UDEV_POINTINGSTICK }, { "ID_INPUT_TOUCHPAD", UDEV_TOUCHPAD }, { "ID_INPUT_TABLET", UDEV_TABLET }, { "ID_INPUT_TABLET_PAD", UDEV_TABLET_PAD }, { "ID_INPUT_JOYSTICK", UDEV_JOYSTICK }, { "ID_INPUT_KEYBOARD", UDEV_KEYBOARD }, }; struct ut_map *map; ARRAY_FOR_EACH(mappings, map) { if (udev_prop(device, map->prop)) m->udev_type |= map->flag; } m->bits |= M_UDEV_TYPE; } static inline void match_fill_dmi_dt(struct match *m, char *dmi, char *dt) { if (dmi) { m->dmi = dmi; m->bits |= M_DMI; } if (dt) { m->dt = dt; m->bits |= M_DT; } } static struct match * match_new(struct udev_device *device, char *dmi, char *dt) { struct match *m = zalloc(sizeof *m); match_fill_name(m, device); match_fill_bus_vid_pid(m, device); match_fill_dmi_dt(m, dmi, dt); match_fill_udev_type(m, device); return m; } static void match_free(struct match *m) { /* dmi and dt are global */ free(m->name); free(m); } static void quirk_apply_section(struct quirks_context *ctx, struct quirks *q, const struct section *s) { struct property *p; size_t nprops = 0; void *tmp; list_for_each(p, &s->properties, link) { nprops++; } nprops += q->nproperties; tmp = realloc(q->properties, nprops * sizeof(p)); if (!tmp) return; q->properties = tmp; list_for_each(p, &s->properties, link) { qlog_debug(ctx, "property added: %s from %s\n", quirk_get_name(p->id), s->name); q->properties[q->nproperties++] = property_ref(p); } } static bool quirk_match_section(struct quirks_context *ctx, struct quirks *q, struct section *s, struct match *m, struct udev_device *device) { uint32_t matched_flags = 0x0; for (uint32_t flag = 0x1; flag <= M_LAST; flag <<= 1) { uint32_t prev_matched_flags = matched_flags; /* section doesn't have this bit set, continue */ if ((s->match.bits & flag) == 0) continue; /* Couldn't fill in this bit for the match, so we * do not match on it */ if ((m->bits & flag) == 0) { qlog_debug(ctx, "%s wants %s but we don't have that\n", s->name, matchflagname(flag)); continue; } /* now check the actual matching bit */ switch (flag) { case M_NAME: if (fnmatch(s->match.name, m->name, 0) == 0) matched_flags |= flag; break; case M_BUS: if (m->bus == s->match.bus) matched_flags |= flag; break; case M_VID: if (m->vendor == s->match.vendor) matched_flags |= flag; break; case M_PID: if (m->product == s->match.product) matched_flags |= flag; break; case M_VERSION: if (m->version == s->match.version) matched_flags |= flag; break; case M_DMI: if (fnmatch(s->match.dmi, m->dmi, 0) == 0) matched_flags |= flag; break; case M_DT: if (fnmatch(s->match.dt, m->dt, 0) == 0) matched_flags |= flag; break; case M_UDEV_TYPE: if (s->match.udev_type & m->udev_type) matched_flags |= flag; break; default: abort(); } if (prev_matched_flags != matched_flags) { qlog_debug(ctx, "%s matches for %s\n", s->name, matchflagname(flag)); } } if (s->match.bits == matched_flags) { qlog_debug(ctx, "%s is full match\n", s->name); quirk_apply_section(ctx, q, s); } return true; } struct quirks * quirks_fetch_for_device(struct quirks_context *ctx, struct udev_device *udev_device) { struct quirks *q = NULL; struct section *s; struct match *m; if (!ctx) return NULL; qlog_debug(ctx, "%s: fetching quirks\n", udev_device_get_devnode(udev_device)); q = quirks_new(); m = match_new(udev_device, ctx->dmi, ctx->dt); list_for_each(s, &ctx->sections, link) { quirk_match_section(ctx, q, s, m, udev_device); } match_free(m); if (q->nproperties == 0) { quirks_unref(q); return NULL; } list_insert(&ctx->quirks, &q->link); return q; } static inline struct property * quirk_find_prop(struct quirks *q, enum quirk which) { /* Run backwards to only handle the last one assigned */ for (ssize_t i = q->nproperties - 1; i >= 0; i--) { struct property *p = q->properties[i]; if (p->id == which) return p; } return NULL; } bool quirks_has_quirk(struct quirks *q, enum quirk which) { return quirk_find_prop(q, which) != NULL; } bool quirks_get_int32(struct quirks *q, enum quirk which, int32_t *val) { struct property *p; if (!q) return false; p = quirk_find_prop(q, which); if (!p) return false; assert(p->type == PT_INT); *val = p->value.i; return true; } bool quirks_get_uint32(struct quirks *q, enum quirk which, uint32_t *val) { struct property *p; if (!q) return false; p = quirk_find_prop(q, which); if (!p) return false; assert(p->type == PT_UINT); *val = p->value.u; return true; } bool quirks_get_double(struct quirks *q, enum quirk which, double *val) { struct property *p; if (!q) return false; p = quirk_find_prop(q, which); if (!p) return false; assert(p->type == PT_DOUBLE); *val = p->value.d; return true; } bool quirks_get_string(struct quirks *q, enum quirk which, char **val) { struct property *p; if (!q) return false; p = quirk_find_prop(q, which); if (!p) return false; assert(p->type == PT_STRING); *val = p->value.s; return true; } bool quirks_get_bool(struct quirks *q, enum quirk which, bool *val) { struct property *p; if (!q) return false; p = quirk_find_prop(q, which); if (!p) return false; assert(p->type == PT_BOOL); *val = p->value.b; return true; } bool quirks_get_dimensions(struct quirks *q, enum quirk which, struct quirk_dimensions *val) { struct property *p; if (!q) return false; p = quirk_find_prop(q, which); if (!p) return false; assert(p->type == PT_DIMENSION); *val = p->value.dim; return true; } bool quirks_get_range(struct quirks *q, enum quirk which, struct quirk_range *val) { struct property *p; if (!q) return false; p = quirk_find_prop(q, which); if (!p) return false; assert(p->type == PT_RANGE); *val = p->value.range; return true; }