diff options
author | Dmitry Torokhov <dmitry.torokhov@gmail.com> | 2012-05-02 09:13:37 +0200 |
---|---|---|
committer | Dmitry Torokhov <dmitry.torokhov@gmail.com> | 2012-05-02 09:23:58 +0200 |
commit | 2872a9b521ac936c7a8525a8c2bdfb9b4ccf5cfc (patch) | |
tree | 613f1456fe437419bbc014f84d7d3c2e73568743 /drivers/input/evdev.c | |
parent | Input: evdev - properly access RCU-protected 'grab' data (diff) | |
download | linux-2872a9b521ac936c7a8525a8c2bdfb9b4ccf5cfc.tar.xz linux-2872a9b521ac936c7a8525a8c2bdfb9b4ccf5cfc.zip |
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/evdev.c')
-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, struct input_event event; int retval = 0; - if (count < input_event_size()) + if (count != 0 && count < input_event_size()) return -EINVAL; retval = mutex_lock_interruptible(&evdev->mutex); @@ -357,7 +357,8 @@ static ssize_t evdev_write(struct file *file, const char __user *buffer, goto out; } - do { + while (retval + input_event_size() <= count) { + if (input_event_from_user(buffer + retval, &event)) { retval = -EFAULT; goto out; @@ -366,7 +367,7 @@ static ssize_t evdev_write(struct file *file, const char __user *buffer, input_inject_event(&evdev->handle, event.type, event.code, event.value); - } while (retval + input_event_size() <= count); + } out: mutex_unlock(&evdev->mutex); @@ -397,35 +398,49 @@ static ssize_t evdev_read(struct file *file, char __user *buffer, struct evdev_client *client = file->private_data; struct evdev *evdev = client->evdev; struct input_event event; - int retval = 0; + size_t read = 0; + int error; - if (count < input_event_size()) + if (count != 0 && count < input_event_size()) return -EINVAL; - if (!(file->f_flags & O_NONBLOCK)) { - retval = wait_event_interruptible(evdev->wait, - client->packet_head != client->tail || - !evdev->exist); - if (retval) - return retval; - } + for (;;) { + if (!evdev->exist) + return -ENODEV; - if (!evdev->exist) - return -ENODEV; + if (client->packet_head == client->tail && + (file->f_flags & O_NONBLOCK)) + return -EAGAIN; + + /* + * count == 0 is special - no IO is done but we check + * for error conditions (see above). + */ + if (count == 0) + break; - while (retval + input_event_size() <= count && - evdev_fetch_next_event(client, &event)) { + while (read + input_event_size() <= count && + evdev_fetch_next_event(client, &event)) { - if (input_event_to_user(buffer + retval, &event)) - return -EFAULT; + if (input_event_to_user(buffer + read, &event)) + return -EFAULT; - retval += input_event_size(); - } + read += input_event_size(); + } - if (retval == 0 && (file->f_flags & O_NONBLOCK)) - return -EAGAIN; + if (read) + break; - return retval; + if (!(file->f_flags & O_NONBLOCK)) { + error = wait_event_interruptible(evdev->wait, + client->packet_head != client->tail || + !evdev->exist); + if (error) + return error; + } + } + + return read; } /* No kernel lock - fine */ |