[PATCH v2 libevdev 2/4] Let libevdev manage the /dev/uinput node if required

Peter Hutterer peter.hutterer at who-t.net
Thu Aug 15 23:50:47 PDT 2013


Same API as before, but if the fd is LIBEVDEV_UINPUT_OPEN_MANAGED, libevdev
opens the uinput node itself. That value is intentionally different to -1, to avoid
code like the below from accidentally working:

	fd = open("/dev/uinput", O_RDWR); /* fails, fd is -1 */
	libevdev_uinput_create_from_device(dev, fd, &uidev); /* may hide the error */

Signed-off-by: Peter Hutterer <peter.hutterer at who-t.net>
---
Changes to v1:
- O_CLOEXEC
- actually do what the commit message says, and return -EBADF for neg file
  descriptors
- add a comment to explain -2
- add a test for invalid create

 libevdev/libevdev-uinput-int.h |  1 +
 libevdev/libevdev-uinput.c     | 21 +++++++++++
 libevdev/libevdev-uinput.h     | 20 ++++++++---
 test/test-uinput.c             | 80 ++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 118 insertions(+), 4 deletions(-)

diff --git a/libevdev/libevdev-uinput-int.h b/libevdev/libevdev-uinput-int.h
index 70fa1e1..f443984 100644
--- a/libevdev/libevdev-uinput-int.h
+++ b/libevdev/libevdev-uinput-int.h
@@ -23,6 +23,7 @@
 
 struct libevdev_uinput {
 	int fd; /**< file descriptor to uinput */
+	int fd_is_managed; /**< do we need to close it? */
 	char *name; /**< device name */
 	char *syspath; /**< /sys path */
 	time_t ctime[2]; /**< before/after UI_DEV_CREATE */
diff --git a/libevdev/libevdev-uinput.c b/libevdev/libevdev-uinput.c
index 9fd36b4..45330e1 100644
--- a/libevdev/libevdev-uinput.c
+++ b/libevdev/libevdev-uinput.c
@@ -139,6 +139,16 @@ set_props(const struct libevdev *dev, int fd, struct uinput_user_dev *uidev)
 	return rc;
 }
 
+static int
+open_uinput()
+{
+	int fd = open("/dev/uinput", O_RDWR|O_CLOEXEC);
+	if (fd < 0)
+		return -errno;
+
+	return fd;
+}
+
 int
 libevdev_uinput_create_from_device(const struct libevdev *dev, int fd, struct libevdev_uinput** uinput_dev)
 {
@@ -150,6 +160,15 @@ libevdev_uinput_create_from_device(const struct libevdev *dev, int fd, struct li
 	if (!new_device)
 		return -ENOMEM;
 
+	if (fd == LIBEVDEV_UINPUT_OPEN_MANAGED) {
+		fd = open_uinput();
+		if (fd < 0)
+			return fd;
+
+		new_device->fd_is_managed = 1;
+	} else if (fd < 0)
+		return -EBADF;
+
 	memset(&uidev, 0, sizeof(uidev));
 
 	strncpy(uidev.name, libevdev_get_name(dev), UINPUT_MAX_NAME_SIZE - 1);
@@ -199,6 +218,8 @@ error:
 void libevdev_uinput_destroy(struct libevdev_uinput *uinput_dev)
 {
 	ioctl(uinput_dev->fd, UI_DEV_DESTROY, NULL);
+	if (uinput_dev->fd_is_managed)
+		close(uinput_dev->fd);
 	free(uinput_dev->syspath);
 	free(uinput_dev->name);
 	free(uinput_dev);
diff --git a/libevdev/libevdev-uinput.h b/libevdev/libevdev-uinput.h
index 9e19ad2..a778845 100644
--- a/libevdev/libevdev-uinput.h
+++ b/libevdev/libevdev-uinput.h
@@ -99,6 +99,13 @@ struct libevdev_uinput;
  * @endcode
  */
 
+enum EvdevUInputOpenMode {
+	/* intentionally -2 to avoid to avoid code like the below from accidentally working:
+		fd = open("/dev/uinput", O_RDWR); // fails, fd is -1
+		libevdev_uinput_create_from_device(dev, fd, &uidev); // may hide the error */
+	LIBEVDEV_UINPUT_OPEN_MANAGED = -2,
+};
+
 /**
  * @ingroup uinput
  *
@@ -106,6 +113,11 @@ struct libevdev_uinput;
  * will be an exact copy of the libevdev device, minus the bits that uinput doesn't
  * allow to be set.
  *
+ * If uinput_fd is LIBEVDEV_UINPUT_OPEN_MANAGED, libevdev_uinput_create_from_device()
+ * will open @c /dev/uinput in read/write mode and manage the file descriptor.
+ * Otherwise, uinput_fd must be opened by the caller and opened with the
+ * appropriate permissions.
+ *
  * The device's lifetime is tied to the uinput file descriptor, closing it will
  * destroy the uinput device. You should call libevdev_uinput_destroy() before
  * closing the file descriptor to free allocated resources.
@@ -120,9 +132,7 @@ struct libevdev_uinput;
  * source device.
  *
  * @param dev The device to duplicate
- * @param uinput_fd A file descriptor to @c /dev/uinput, opened with the required
- * permissions to create a device. This fd may only be used once to create a
- * uinput device.
+ * @param uinput_fd LIBEVDEV_UINPUT_OPEN_MANAGED or a file descriptor to @c /dev/uinput,
  * @param[out] uinput_dev The newly created libevdev device.
  *
  * @return 0 on success or a negative errno on failure. On failure, the value of
@@ -139,7 +149,9 @@ int libevdev_uinput_create_from_device(const struct libevdev *dev,
  *
  * Destroy a previously created uinput device and free associated memory.
  *
- * @note libevdev_uinput_destroy() does not close the fd.
+ * If the device was opened with LIBEVDEV_UINPUT_OPEN_MANAGED, libevdev_uinput_destroy()
+ * also closes the file descriptor. Otherwise, the fd is left as-is and
+ * must be closed by the caller.
  *
  * @param uinput_dev A previously created uinput device.
  *
diff --git a/test/test-uinput.c b/test/test-uinput.c
index 9d7fa85..e7f6266 100644
--- a/test/test-uinput.c
+++ b/test/test-uinput.c
@@ -36,6 +36,84 @@ START_TEST(test_uinput_create_device)
 {
 	struct libevdev *dev, *dev2;
 	struct libevdev_uinput *uidev;
+	int fd, uinput_fd;
+	unsigned int type, code;
+	int rc;
+	char *devnode;
+
+	dev = libevdev_new();
+	ck_assert(dev != NULL);
+	libevdev_set_name(dev, TEST_DEVICE_NAME);
+	libevdev_enable_event_type(dev, EV_SYN);
+	libevdev_enable_event_type(dev, EV_REL);
+	libevdev_enable_event_code(dev, EV_REL, REL_X, NULL);
+	libevdev_enable_event_code(dev, EV_REL, REL_Y, NULL);
+
+	rc = libevdev_uinput_create_from_device(dev, LIBEVDEV_UINPUT_OPEN_MANAGED, &uidev);
+	ck_assert_int_eq(rc, 0);
+	ck_assert(uidev != NULL);
+
+	uinput_fd = libevdev_uinput_get_fd(uidev);
+	ck_assert_int_gt(uinput_fd, -1);
+
+	devnode = uinput_devnode_from_syspath(libevdev_uinput_get_syspath(uidev));
+	ck_assert(devnode != NULL);
+
+	fd = open(devnode, O_RDONLY);
+	ck_assert_int_gt(fd, -1);
+	rc = libevdev_new_from_fd(fd, &dev2);
+	ck_assert_int_eq(rc, 0);
+
+	for (type = 0; type < EV_MAX; type++) {
+		int max = libevdev_get_event_type_max(type);
+		if (max == -1)
+			continue;
+
+		for (code = 0; code < max; code++) {
+			ck_assert_int_eq(libevdev_has_event_code(dev, type, code),
+					 libevdev_has_event_code(dev2, type, code));
+		}
+	}
+
+	libevdev_free(dev);
+	libevdev_free(dev2);
+	libevdev_uinput_destroy(uidev);
+	close(fd);
+	free(devnode);
+
+	/* uinput fd is managed, so make sure it did get closed */
+	ck_assert_int_eq(close(uinput_fd), -1);
+	ck_assert_int_eq(errno, EBADF);
+
+}
+END_TEST
+
+START_TEST(test_uinput_create_device_invalid)
+{
+	struct libevdev *dev;
+	struct libevdev_uinput *uidev = NULL;
+	int rc;
+
+	dev = libevdev_new();
+	ck_assert(dev != NULL);
+	libevdev_set_name(dev, TEST_DEVICE_NAME);
+	libevdev_enable_event_type(dev, EV_SYN);
+	libevdev_enable_event_type(dev, EV_REL);
+	libevdev_enable_event_code(dev, EV_REL, REL_X, NULL);
+	libevdev_enable_event_code(dev, EV_REL, REL_Y, NULL);
+
+	rc = libevdev_uinput_create_from_device(dev, -1, &uidev);
+	ck_assert_int_eq(rc, -EBADF);
+	ck_assert(uidev == NULL);
+
+	libevdev_free(dev);
+}
+END_TEST
+
+START_TEST(test_uinput_create_device_from_fd)
+{
+	struct libevdev *dev, *dev2;
+	struct libevdev_uinput *uidev;
 	int fd, fd2;
 	unsigned int type, code;
 	int rc;
@@ -257,6 +335,8 @@ uinput_suite(void)
 
 	TCase *tc = tcase_create("device creation");
 	tcase_add_test(tc, test_uinput_create_device);
+	tcase_add_test(tc, test_uinput_create_device_invalid);
+	tcase_add_test(tc, test_uinput_create_device_from_fd);
 	tcase_add_test(tc, test_uinput_check_syspath_time);
 	tcase_add_test(tc, test_uinput_check_syspath_name);
 	suite_add_tcase(s, tc);
-- 
1.8.2.1



More information about the Input-tools mailing list