diff options
author | Dmitry Torokhov <dmitry.torokhov@gmail.com> | 2012-05-02 03:13:37 -0400 |
---|---|---|
committer | Dmitry Torokhov <dmitry.torokhov@gmail.com> | 2012-05-02 03:23:58 -0400 |
commit | 2872a9b521ac936c7a8525a8c2bdfb9b4ccf5cfc (patch) | |
tree | 613f1456fe437419bbc014f84d7d3c2e73568743 /drivers/input | |
parent | dba4258068f822b7dafc78c28fe9c99c551eca7e (diff) |
Input: evdev - properly handle read/write with count 0
According to the standard count 0 is special - no IO should happen but we
can check error conditions (device gone away, etc), and return 0 if there
are no errors. We used to return -EINVAL instead and we also could return 0
if an event was "stolen" by another thread.
Signed-off-by: Dmitry Torokhov <dtor@mail.ru>
Diffstat (limited to 'drivers/input')
-rw-r--r-- | drivers/input/evdev.c | 61 |
1 files changed, 38 insertions, 23 deletions
diff --git a/drivers/input/evdev.c b/drivers/input/evdev.c index 9226b4d9118f..6c58bfff01a3 100644 --- a/drivers/input/evdev.c +++ b/drivers/input/evdev.c | |||
@@ -345,7 +345,7 @@ static ssize_t evdev_write(struct file *file, const char __user *buffer, | |||
345 | struct input_event event; | 345 | struct input_event event; |
346 | int retval = 0; | 346 | int retval = 0; |
347 | 347 | ||
348 | if (count < input_event_size()) | 348 | if (count != 0 && count < input_event_size()) |
349 | return -EINVAL; | 349 | return -EINVAL; |
350 | 350 | ||
351 | retval = mutex_lock_interruptible(&evdev->mutex); | 351 | retval = mutex_lock_interruptible(&evdev->mutex); |
@@ -357,7 +357,8 @@ static ssize_t evdev_write(struct file *file, const char __user *buffer, | |||
357 | goto out; | 357 | goto out; |
358 | } | 358 | } |
359 | 359 | ||
360 | do { | 360 | while (retval + input_event_size() <= count) { |
361 | |||
361 | if (input_event_from_user(buffer + retval, &event)) { | 362 | if (input_event_from_user(buffer + retval, &event)) { |
362 | retval = -EFAULT; | 363 | retval = -EFAULT; |
363 | goto out; | 364 | goto out; |
@@ -366,7 +367,7 @@ static ssize_t evdev_write(struct file *file, const char __user *buffer, | |||
366 | 367 | ||
367 | input_inject_event(&evdev->handle, | 368 | input_inject_event(&evdev->handle, |
368 | event.type, event.code, event.value); | 369 | event.type, event.code, event.value); |
369 | } while (retval + input_event_size() <= count); | 370 | } |
370 | 371 | ||
371 | out: | 372 | out: |
372 | mutex_unlock(&evdev->mutex); | 373 | mutex_unlock(&evdev->mutex); |
@@ -397,35 +398,49 @@ static ssize_t evdev_read(struct file *file, char __user *buffer, | |||
397 | struct evdev_client *client = file->private_data; | 398 | struct evdev_client *client = file->private_data; |
398 | struct evdev *evdev = client->evdev; | 399 | struct evdev *evdev = client->evdev; |
399 | struct input_event event; | 400 | struct input_event event; |
400 | int retval = 0; | 401 | size_t read = 0; |
402 | int error; | ||
401 | 403 | ||
402 | if (count < input_event_size()) | 404 | if (count != 0 && count < input_event_size()) |
403 | return -EINVAL; | 405 | return -EINVAL; |
404 | 406 | ||
405 | if (!(file->f_flags & O_NONBLOCK)) { | 407 | for (;;) { |
406 | retval = wait_event_interruptible(evdev->wait, | 408 | if (!evdev->exist) |
407 | client->packet_head != client->tail || | 409 | return -ENODEV; |
408 | !evdev->exist); | ||
409 | if (retval) | ||
410 | return retval; | ||
411 | } | ||
412 | 410 | ||
413 | if (!evdev->exist) | 411 | if (client->packet_head == client->tail && |
414 | return -ENODEV; | 412 | (file->f_flags & O_NONBLOCK)) |
413 | return -EAGAIN; | ||
414 | |||
415 | /* | ||
416 | * count == 0 is special - no IO is done but we check | ||
417 | * for error conditions (see above). | ||
418 | */ | ||
419 | if (count == 0) | ||
420 | break; | ||
415 | 421 | ||
416 | while (retval + input_event_size() <= count && | 422 | while (read + input_event_size() <= count && |
417 | evdev_fetch_next_event(client, &event)) { | 423 | evdev_fetch_next_event(client, &event)) { |
418 | 424 | ||
419 | if (input_event_to_user(buffer + retval, &event)) | 425 | if (input_event_to_user(buffer + read, &event)) |
420 | return -EFAULT; | 426 | return -EFAULT; |
421 | 427 | ||
422 | retval += input_event_size(); | 428 | read += input_event_size(); |
423 | } | 429 | } |
424 | 430 | ||
425 | if (retval == 0 && (file->f_flags & O_NONBLOCK)) | 431 | if (read) |
426 | return -EAGAIN; | 432 | break; |
427 | 433 | ||
428 | return retval; | 434 | if (!(file->f_flags & O_NONBLOCK)) { |
435 | error = wait_event_interruptible(evdev->wait, | ||
436 | client->packet_head != client->tail || | ||
437 | !evdev->exist); | ||
438 | if (error) | ||
439 | return error; | ||
440 | } | ||
441 | } | ||
442 | |||
443 | return read; | ||
429 | } | 444 | } |
430 | 445 | ||
431 | /* No kernel lock - fine */ | 446 | /* No kernel lock - fine */ |