summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDmitry Torokhov <dmitry.torokhov@gmail.com>2012-05-02 00:13:37 -0700
committerDmitry Torokhov <dmitry.torokhov@gmail.com>2012-05-02 00:23:58 -0700
commit2872a9b521ac936c7a8525a8c2bdfb9b4ccf5cfc (patch)
tree613f1456fe437419bbc014f84d7d3c2e73568743
parentdba4258068f822b7dafc78c28fe9c99c551eca7e (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>
-rw-r--r--drivers/input/evdev.c61
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 */