diff --git a/meson.build b/meson.build index 6dda1926..9f800daa 100644 --- a/meson.build +++ b/meson.build @@ -149,8 +149,16 @@ executable('libinput-device-group', include_directories : [includes_src, includes_include], install : true, install_dir : dir_udev_callouts) -executable('libinput-fuzz-override', - 'udev/libinput-fuzz-override.c', +executable('libinput-fuzz-extract', + 'udev/libinput-fuzz-extract.c', + 'src/util-strings.c', + 'src/util-prop-parsers.c', + dependencies : [dep_udev, dep_libevdev, dep_lm], + include_directories : [includes_src, includes_include], + install : true, + install_dir : dir_udev_callouts) +executable('libinput-fuzz-to-zero', + 'udev/libinput-fuzz-to-zero.c', dependencies : [dep_udev, dep_libevdev], include_directories : [includes_src, includes_include], install : true, diff --git a/src/util-prop-parsers.c b/src/util-prop-parsers.c index 4a4425a4..35f70b63 100644 --- a/src/util-prop-parsers.c +++ b/src/util-prop-parsers.c @@ -401,3 +401,70 @@ out: strv_free(strv); return rc; } + +/** + * Parse the property value for the EVDEV_ABS_00 properties. Spec is + * EVDEV_ABS_00=min:max:res:fuzz:flat + * where any element may be empty and subsequent elements may not be + * present. So we have to parse + * EVDEV_ABS_00=min:max:res + * EVDEV_ABS_00=::res + * EVDEV_ABS_00=::res:fuzz: + * + * Returns a mask of the bits set and the absinfo struct with the values. + * The abs value for an unset bit is undefined. + */ +uint32_t +parse_evdev_abs_prop(const char *prop, struct input_absinfo *abs) +{ + char *str = strdup(prop); + char *current, *next; + uint32_t mask = 0; + int bit = ABS_MASK_MIN; + int *val; + int values[5]; + + /* basic sanity check: 5 digits for min/max, 3 for resolution, fuzz, + * flat and the colons. That's plenty, anything over is garbage */ + if (strlen(prop) > 24) + goto out; + + current = str; + val = values; + while (current && *current != '\0' && bit <= ABS_MASK_FLAT) { + if (*current != ':') { + int v; + next = index(current, ':'); + if (next) + *next = '\0'; + + if (!safe_atoi(current, &v)) { + mask = 0; + goto out; + } + *val = v; + mask |= bit; + current = next ? ++next : NULL; + } else { + current++; + } + bit <<= 1; + val++; + } + + if (mask & ABS_MASK_MIN) + abs->minimum = values[0]; + if (mask & ABS_MASK_MAX) + abs->maximum = values[1]; + if (mask & ABS_MASK_RES) + abs->resolution = values[2]; + if (mask & ABS_MASK_FUZZ) + abs->fuzz = values[3]; + if (mask & ABS_MASK_FLAT) + abs->flat = values[4]; + +out: + free(str); + + return mask; +} diff --git a/src/util-prop-parsers.h b/src/util-prop-parsers.h index 6fcbb426..7ed136a9 100644 --- a/src/util-prop-parsers.h +++ b/src/util-prop-parsers.h @@ -28,6 +28,7 @@ #include #include #include +#include int parse_mouse_dpi_property(const char *prop); int parse_mouse_wheel_click_angle_property(const char *prop); @@ -54,3 +55,13 @@ enum switch_reliability { bool parse_switch_reliability_property(const char *prop, enum switch_reliability *reliability); + +enum { + ABS_MASK_MIN = 0x1, + ABS_MASK_MAX = 0x2, + ABS_MASK_RES = 0x4, + ABS_MASK_FUZZ = 0x8, + ABS_MASK_FLAT = 0x10, +}; + +uint32_t parse_evdev_abs_prop(const char *prop, struct input_absinfo *abs); diff --git a/test/test-utils.c b/test/test-utils.c index 46355a59..27091a01 100644 --- a/test/test-utils.c +++ b/test/test-utils.c @@ -546,6 +546,83 @@ START_TEST(evcode_prop_parser) } END_TEST +START_TEST(evdev_abs_parser) +{ + struct test { + uint32_t which; + const char *prop; + int min, max, res, fuzz, flat; + + } tests[] = { + { .which = (ABS_MASK_MIN|ABS_MASK_MAX), + .prop = "1:2", + .min = 1, .max = 2 }, + { .which = (ABS_MASK_MIN|ABS_MASK_MAX), + .prop = "1:2:", + .min = 1, .max = 2 }, + { .which = (ABS_MASK_MIN|ABS_MASK_MAX|ABS_MASK_RES), + .prop = "10:20:30", + .min = 10, .max = 20, .res = 30 }, + { .which = (ABS_MASK_RES), + .prop = "::100", + .res = 100 }, + { .which = (ABS_MASK_MIN), + .prop = "10:", + .min = 10 }, + { .which = (ABS_MASK_MAX|ABS_MASK_RES), + .prop = ":10:1001", + .max = 10, .res = 1001 }, + { .which = (ABS_MASK_MIN|ABS_MASK_MAX|ABS_MASK_RES|ABS_MASK_FUZZ), + .prop = "1:2:3:4", + .min = 1, .max = 2, .res = 3, .fuzz = 4}, + { .which = (ABS_MASK_MIN|ABS_MASK_MAX|ABS_MASK_RES|ABS_MASK_FUZZ|ABS_MASK_FLAT), + .prop = "1:2:3:4:5", + .min = 1, .max = 2, .res = 3, .fuzz = 4, .flat = 5}, + { .which = (ABS_MASK_MIN|ABS_MASK_RES|ABS_MASK_FUZZ|ABS_MASK_FLAT), + .prop = "1::3:4:50", + .min = 1, .res = 3, .fuzz = 4, .flat = 50}, + { .which = ABS_MASK_FUZZ|ABS_MASK_FLAT, + .prop = ":::5:60", + .fuzz = 5, .flat = 60}, + { .which = ABS_MASK_FUZZ, + .prop = ":::5:", + .fuzz = 5 }, + { .which = ABS_MASK_RES, .prop = "::12::", + .res = 12 }, + /* Malformed property but parsing this one makes us more + * future proof */ + { .which = (ABS_MASK_RES|ABS_MASK_FUZZ|ABS_MASK_FLAT), + .prop = "::12:1:2:3:4:5:6", + .res = 12, .fuzz = 1, .flat = 2 }, + { .which = 0, .prop = ":::::" }, + { .which = 0, .prop = ":" }, + { .which = 0, .prop = "" }, + { .which = 0, .prop = ":asb::::" }, + { .which = 0, .prop = "foo" }, + }; + struct test *t; + + ARRAY_FOR_EACH(tests, t) { + struct input_absinfo abs; + uint32_t mask; + + mask = parse_evdev_abs_prop(t->prop, &abs); + ck_assert_int_eq(mask, t->which); + + if (t->which & ABS_MASK_MIN) + ck_assert_int_eq(abs.minimum, t->min); + if (t->which & ABS_MASK_MAX) + ck_assert_int_eq(abs.maximum, t->max); + if (t->which & ABS_MASK_RES) + ck_assert_int_eq(abs.resolution, t->res); + if (t->which & ABS_MASK_FUZZ) + ck_assert_int_eq(abs.fuzz, t->fuzz); + if (t->which & ABS_MASK_FLAT) + ck_assert_int_eq(abs.flat, t->flat); + } +} +END_TEST + START_TEST(time_conversion) { ck_assert_int_eq(us(10), 10); @@ -1050,6 +1127,7 @@ litest_utils_suite(void) tcase_add_test(tc, calibration_prop_parser); tcase_add_test(tc, range_prop_parser); tcase_add_test(tc, evcode_prop_parser); + tcase_add_test(tc, evdev_abs_parser); tcase_add_test(tc, safe_atoi_test); tcase_add_test(tc, safe_atoi_base_16_test); tcase_add_test(tc, safe_atoi_base_8_test); diff --git a/udev/90-libinput-fuzz-override.rules.in b/udev/90-libinput-fuzz-override.rules.in index e3d8e537..bcee4638 100644 --- a/udev/90-libinput-fuzz-override.rules.in +++ b/udev/90-libinput-fuzz-override.rules.in @@ -6,15 +6,22 @@ ACTION!="add|change", GOTO="libinput_fuzz_override_end" KERNEL!="event*", GOTO="libinput_fuzz_override_end" -# libinput-fuzz-override must only be called once per device, otherwise -# we'll lose the fuzz information +# Two-step process: fuzz-extract sets the LIBINPUT_FUZZ property and +# fuzz-to-zero sets the kernel fuzz to zero. They must be in IMPORT and RUN, +# respectively, to correctly interact with the 60-evdev.hwdb +# +# Drawback: if this rule is triggered more than once, we'll lose the fuzz +# information (because the kernel fuzz will then be zero). Nothing we can do +# about that. ATTRS{capabilities/abs}!="0", \ ENV{ID_INPUT_TOUCHPAD}=="1", \ - IMPORT{program}="@UDEV_TEST_PATH@libinput-fuzz-override %S%p", \ + IMPORT{program}="@UDEV_TEST_PATH@libinput-fuzz-extract %S%p", \ + RUN{program}="@UDEV_TEST_PATH@libinput-fuzz-to-zero %S%p", \ GOTO="libinput_fuzz_override_end" ATTRS{capabilities/abs}!="0", \ ENV{ID_INPUT_TOUCHSCREEN}=="1", \ - IMPORT{program}="@UDEV_TEST_PATH@libinput-fuzz-override %S%p", \ + IMPORT{program}="@UDEV_TEST_PATH@libinput-fuzz-extract %S%p", \ + RUN{program}="@UDEV_TEST_PATH@libinput-fuzz-to-zero %S%p", \ GOTO="libinput_fuzz_override_end" LABEL="libinput_fuzz_override_end" diff --git a/udev/libinput-fuzz-extract.c b/udev/libinput-fuzz-extract.c new file mode 100644 index 00000000..192b4700 --- /dev/null +++ b/udev/libinput-fuzz-extract.c @@ -0,0 +1,150 @@ +/* + * Copyright © 2015 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" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "util-prop-parsers.h" +#include "util-macros.h" + +/** + * For a non-zero fuzz on the x/y axes, print that fuzz as property and + * reset the kernel's fuzz to 0. + * https://bugs.freedesktop.org/show_bug.cgi?id=105202 + */ +static void +handle_absfuzz(struct udev_device *device) +{ + const char *devnode; + struct libevdev *evdev = NULL; + int fd = -1; + int rc; + unsigned int *code; + unsigned int axes[] = {ABS_X, + ABS_Y, + ABS_MT_POSITION_X, + ABS_MT_POSITION_Y}; + + devnode = udev_device_get_devnode(device); + if (!devnode) + goto out; + + fd = open(devnode, O_RDONLY); + if (fd < 0) + goto out; + + rc = libevdev_new_from_fd(fd, &evdev); + if (rc != 0) + goto out; + + if (!libevdev_has_event_type(evdev, EV_ABS)) + goto out; + + ARRAY_FOR_EACH(axes, code) { + int fuzz; + + fuzz = libevdev_get_abs_fuzz(evdev, *code); + if (fuzz) + printf("LIBINPUT_FUZZ_%02x=%d\n", *code, fuzz); + } + +out: + close(fd); + libevdev_free(evdev); +} + +/** + * Where a device has EVDEV_ABS_... set with a fuzz, that fuzz hasn't been + * applied to the kernel yet. So we need to extract it ourselves **and** + * update the property so the kernel won't actually set it later. + */ +static void +handle_evdev_abs(struct udev_device *device) +{ + unsigned int *code; + unsigned int axes[] = {ABS_X, + ABS_Y, + ABS_MT_POSITION_X, + ABS_MT_POSITION_Y}; + + ARRAY_FOR_EACH(axes, code) { + const char *prop; + char name[64]; + uint32_t mask; + struct input_absinfo abs; + + snprintf(name, sizeof(name), "EVDEV_ABS_%02X", *code); + prop = udev_device_get_property_value(device, name); + if (!prop) + continue; + + mask = parse_evdev_abs_prop(prop, &abs); + if (mask & ABS_MASK_FUZZ) + printf("LIBINPUT_FUZZ_%02x=%d\n", *code, abs.fuzz); + } +} + +int main(int argc, char **argv) +{ + int rc = 1; + struct udev *udev = NULL; + struct udev_device *device = NULL; + const char *syspath; + + if (argc != 2) + return 1; + + syspath = argv[1]; + + udev = udev_new(); + if (!udev) + goto out; + + device = udev_device_new_from_syspath(udev, syspath); + if (!device) + goto out; + + handle_absfuzz(device); + handle_evdev_abs(device); + + rc = 0; + +out: + if (device) + udev_device_unref(device); + if (udev) + udev_unref(udev); + + return rc; +} diff --git a/udev/libinput-fuzz-override.c b/udev/libinput-fuzz-to-zero.c similarity index 87% rename from udev/libinput-fuzz-override.c rename to udev/libinput-fuzz-to-zero.c index bb05886c..758b1ae2 100644 --- a/udev/libinput-fuzz-override.c +++ b/udev/libinput-fuzz-to-zero.c @@ -27,21 +27,19 @@ #include #include #include +#include #include #include #include #include #include -#include "libinput-util.h" +#include "util-strings.h" +#include "util-macros.h" +#include "util-bits.h" -/** - * For a non-zero fuzz on the x/y axes, print that fuzz as property and - * reset the kernel's fuzz to 0. - * https://bugs.freedesktop.org/show_bug.cgi?id=105202 - */ static void -handle_absfuzz(struct udev_device *device) +reset_absfuzz_to_zero(struct udev_device *device) { const char *devnode; struct libevdev *evdev = NULL; @@ -58,8 +56,6 @@ handle_absfuzz(struct udev_device *device) goto out; fd = open(devnode, O_RDWR); - if (fd == -1 && errno == EACCES) - fd = open(devnode, O_RDONLY); if (fd < 0) goto out; @@ -81,8 +77,6 @@ handle_absfuzz(struct udev_device *device) abs = *libevdev_get_abs_info(evdev, *code); abs.fuzz = 0; libevdev_kernel_set_abs_info(evdev, *code, &abs); - - printf("LIBINPUT_FUZZ_%02x=%d\n", *code, fuzz); } out: @@ -110,7 +104,7 @@ int main(int argc, char **argv) if (!device) goto out; - handle_absfuzz(device); + reset_absfuzz_to_zero(device); rc = 0; diff --git a/udev/test-libinput-fuzz-extract.c b/udev/test-libinput-fuzz-extract.c new file mode 100644 index 00000000..05d9f39d --- /dev/null +++ b/udev/test-libinput-fuzz-extract.c @@ -0,0 +1,126 @@ +/* + * Copyright © 2019 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" + +#include + +/* remove the main() from the included program so we can define our own */ +#define main __disabled +int main(int argc, char **argv); +#include "libinput-fuzz-extract.c" +#undef main + +START_TEST(test_parse_ev_abs) +{ + struct test { + uint32_t which; + const char *prop; + int min, max, res, fuzz, flat; + + } tests[] = { + { .which = (MIN|MAX), + .prop = "1:2", + .min = 1, .max = 2 }, + { .which = (MIN|MAX), + .prop = "1:2:", + .min = 1, .max = 2 }, + { .which = (MIN|MAX|RES), + .prop = "10:20:30", + .min = 10, .max = 20, .res = 30 }, + { .which = (RES), + .prop = "::100", + .res = 100 }, + { .which = (MIN), + .prop = "10:", + .min = 10 }, + { .which = (MAX|RES), + .prop = ":10:1001", + .max = 10, .res = 1001 }, + { .which = (MIN|MAX|RES|FUZZ), + .prop = "1:2:3:4", + .min = 1, .max = 2, .res = 3, .fuzz = 4}, + { .which = (MIN|MAX|RES|FUZZ|FLAT), + .prop = "1:2:3:4:5", + .min = 1, .max = 2, .res = 3, .fuzz = 4, .flat = 5}, + { .which = (MIN|RES|FUZZ|FLAT), + .prop = "1::3:4:50", + .min = 1, .res = 3, .fuzz = 4, .flat = 50}, + { .which = FUZZ|FLAT, + .prop = ":::5:60", + .fuzz = 5, .flat = 60}, + { .which = FUZZ, + .prop = ":::5:", + .fuzz = 5 }, + { .which = RES, .prop = "::12::", + .res = 12 }, + /* Malformed property but parsing this one makes us more + * future proof */ + { .which = (RES|FUZZ|FLAT), .prop = "::12:1:2:3:4:5:6", + .res = 12, .fuzz = 1, .flat = 2 }, + { .which = 0, .prop = ":::::" }, + { .which = 0, .prop = ":" }, + { .which = 0, .prop = "" }, + { .which = 0, .prop = ":asb::::" }, + { .which = 0, .prop = "foo" }, + }; + struct test *t; + + ARRAY_FOR_EACH(tests, t) { + struct input_absinfo abs; + uint32_t mask; + + mask = parse_ev_abs_prop(t->prop, &abs); + ck_assert_int_eq(mask, t->which); + + if (t->which & MIN) + ck_assert_int_eq(abs.minimum, t->min); + if (t->which & MAX) + ck_assert_int_eq(abs.maximum, t->max); + if (t->which & RES) + ck_assert_int_eq(abs.resolution, t->res); + if (t->which & FUZZ) + ck_assert_int_eq(abs.fuzz, t->fuzz); + if (t->which & FLAT) + ck_assert_int_eq(abs.flat, t->flat); + } +} +END_TEST + +int main(int argc, char **argv) { + + SRunner *sr = srunner_create(NULL); + Suite *s = suite_create("fuzz-override"); + TCase *tc = tcase_create("parser"); + int nfailed; + + tcase_add_test(tc, test_parse_ev_abs); + suite_add_tcase(s, tc); + srunner_add_suite(sr, s); + + srunner_run_all(sr, CK_NORMAL); + nfailed = srunner_ntests_failed(sr); + srunner_free(sr); + + return nfailed; +}