[PATCH libinput] evdev: drain any pending evdev events on a device

Hans de Goede hdegoede at redhat.com
Thu Dec 17 02:56:09 PST 2015


Hi,

On 17-12-15 02:11, Peter Hutterer wrote:
> open_restricted() doesn't always mean 'open the fd'. When the X server uses
> systemd-logind, the fd is opened once before PreInit and then kept open across
> devices being disabled and enabled through the protocol.
>
> When the device is re-enabled and libinput_path_add_device is called for the
> device, we may have events pending on the fd, leaking information that we
> should just ignore.
>
> There's also the potential of inconsistent state. The kernel updates the
> device state whenever it processes an event, the evdev ioctls return that
> state. If events are pending, the state we see is newer than the events we
> process immediately after initialization. That can lead to confusion.
>
> Signed-off-by: Peter Hutterer <peter.hutterer at who-t.net>

Patch looks good to me:

Reviewed-by: Hans de Goede <hdegoede at redhat.com>

Regards,

Hans


> ---
>   src/evdev.c | 15 +++++++++++
>   test/misc.c | 83 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
>   2 files changed, 98 insertions(+)
>
> diff --git a/src/evdev.c b/src/evdev.c
> index 9fecdc4..3708072 100644
> --- a/src/evdev.c
> +++ b/src/evdev.c
> @@ -2202,6 +2202,17 @@ evdev_set_device_group(struct evdev_device *device,
>   	return 0;
>   }
>
> +static inline void
> +evdev_drain_fd(int fd)
> +{
> +	struct input_event ev[24];
> +	size_t sz = sizeof ev;
> +
> +	while (read(fd, &ev, sz) == (int)sz) {
> +		/* discard all pending events */
> +	}
> +}
> +
>   struct evdev_device *
>   evdev_device_create(struct libinput_seat *seat,
>   		    struct udev_device *udev_device)
> @@ -2235,6 +2246,8 @@ evdev_device_create(struct libinput_seat *seat,
>   	libinput_device_init(&device->base, seat);
>   	libinput_seat_ref(seat);
>
> +	evdev_drain_fd(fd);
> +
>   	rc = libevdev_new_from_fd(fd, &device->evdev);
>   	if (rc != 0)
>   		goto err;
> @@ -2682,6 +2695,8 @@ evdev_device_resume(struct evdev_device *device)
>   		return -ENODEV;
>   	}
>
> +	evdev_drain_fd(fd);
> +
>   	device->fd = fd;
>
>   	if (evdev_need_mtdev(device)) {
> diff --git a/test/misc.c b/test/misc.c
> index 89edb14..b962cc5 100644
> --- a/test/misc.c
> +++ b/test/misc.c
> @@ -693,6 +693,87 @@ START_TEST(time_conversion)
>   }
>   END_TEST
>
> +static int open_restricted_leak(const char *path, int flags, void *data)
> +{
> +	return *(int*)data;
> +}
> +
> +static void close_restricted_leak(int fd, void *data)
> +{
> +	/* noop */
> +}
> +
> +const struct libinput_interface leak_interface = {
> +	.open_restricted = open_restricted_leak,
> +	.close_restricted = close_restricted_leak,
> +};
> +
> +static void
> +simple_log_handler(struct libinput *libinput,
> +		   enum libinput_log_priority priority,
> +		   const char *format,
> +		   va_list args)
> +{
> +	vfprintf(stderr, format, args);
> +}
> +
> +START_TEST(fd_no_event_leak)
> +{
> +	struct libevdev_uinput *uinput;
> +	struct libinput *li;
> +	struct libinput_device *device;
> +	int fd = -1;
> +	const char *path;
> +	struct libinput_event *event;
> +
> +	uinput = create_simple_test_device("litest test device",
> +					   EV_REL, REL_X,
> +					   EV_REL, REL_Y,
> +					   EV_KEY, BTN_LEFT,
> +					   EV_KEY, BTN_MIDDLE,
> +					   EV_KEY, BTN_LEFT,
> +					   -1, -1);
> +	path = libevdev_uinput_get_devnode(uinput);
> +
> +	fd = open(path, O_RDWR | O_NONBLOCK | O_CLOEXEC);
> +	ck_assert_int_gt(fd, -1);
> +
> +	li = libinput_path_create_context(&leak_interface, &fd);
> +	libinput_log_set_priority(li, LIBINPUT_LOG_PRIORITY_DEBUG);
> +	libinput_log_set_handler(li, simple_log_handler);
> +
> +	/* Add the device, trigger an event, then remove it again.
> +	 * Without it, we get a SYN_DROPPED immediately and no events.
> +	 */
> +	device = libinput_path_add_device(li, path);
> +	libevdev_uinput_write_event(uinput, EV_REL, REL_X, 1);
> +	libevdev_uinput_write_event(uinput, EV_SYN, SYN_REPORT, 0);
> +	libinput_path_remove_device(device);
> +	libinput_dispatch(li);
> +	litest_drain_events(li);
> +
> +	/* Device is removed, but fd is still open. Queue an event, add a
> +	 * new device with the same fd, the queued event must be discarded
> +	 * by libinput */
> +	libevdev_uinput_write_event(uinput, EV_REL, REL_Y, 1);
> +	libevdev_uinput_write_event(uinput, EV_SYN, SYN_REPORT, 0);
> +	libinput_dispatch(li);
> +
> +	libinput_path_add_device(li, path);
> +	libinput_dispatch(li);
> +	event = libinput_get_event(li);
> +	ck_assert_int_eq(libinput_event_get_type(event),
> +			 LIBINPUT_EVENT_DEVICE_ADDED);
> +	libinput_event_destroy(event);
> +
> +	litest_assert_empty_queue(li);
> +
> +	close(fd);
> +	libinput_unref(li);
> +	libevdev_uinput_destroy(uinput);
> +}
> +END_TEST
> +
>   void
>   litest_setup_tests(void)
>   {
> @@ -714,4 +795,6 @@ litest_setup_tests(void)
>   	litest_add_no_device("misc:parser", trackpoint_accel_parser);
>   	litest_add_no_device("misc:parser", dimension_prop_parser);
>   	litest_add_no_device("misc:time", time_conversion);
> +
> +	litest_add_no_device("misc:fd", fd_no_event_leak);
>   }
>


More information about the wayland-devel mailing list