blob: 3e0345397bf29edefcac2cc90142f4bd114047b1 [file] [log] [blame]
// Copyright 2023 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <dirent.h>
#include <fcntl.h>
#include <ftw.h>
#include <stdio.h>
#include <string.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <cerrno>
#include <cstddef>
#include <cstdint>
#include <cstdio>
#include <set>
#include <string>
#include <vector>
#include <gtest/gtest.h>
#include <linux/input.h>
#include <linux/uinput.h>
#include "src/starnix/tests/syscalls/cpp/test_helper.h"
namespace {
class UinputTest : public ::testing::Test {
public:
void SetUp() override {
// TODO(https://fxbug.dev/317285180) don't skip on baseline
if (getuid() != 0) {
GTEST_SKIP() << "Can only be run as root.";
}
uinput_fd_ = fbl::unique_fd(open("/dev/uinput", O_RDWR));
ASSERT_TRUE(uinput_fd_.is_valid())
<< "open(\"/dev/uinput\") failed: " << strerror(errno) << "(" << errno << ")";
}
protected:
fbl::unique_fd uinput_fd_;
};
std::set<std::string> lsDir(const char* dir) {
DIR* d = opendir(dir);
std::set<std::string> name_set;
dirent* e;
while ((e = readdir(d)) != nullptr) {
name_set.insert(e->d_name);
}
closedir(d);
return name_set;
}
// return files in the first ls result but not in the second ls result.
std::vector<std::string> lsDiff(std::set<std::string>& s1, std::set<std::string>& s2) {
std::vector<std::string> diff;
for (const auto& it : s1) {
if (s2.find(it) == s2.end()) {
diff.push_back(it);
}
}
return diff;
}
const uint16_t GOOGLE_VENDOR_ID = 0x18d1;
TEST_F(UinputTest, UiGetVersion) {
// Pass null to UI_GET_VERSION expect EFAULT.
int res = ioctl(uinput_fd_.get(), UI_GET_VERSION, nullptr);
EXPECT_EQ(res, -1);
EXPECT_EQ(errno, EFAULT);
int version;
res = ioctl(uinput_fd_.get(), UI_GET_VERSION, &version);
EXPECT_EQ(res, 0);
EXPECT_EQ(version, 5);
}
TEST_F(UinputTest, UiSetEvbit) {
int res = ioctl(uinput_fd_.get(), UI_SET_EVBIT, EV_KEY);
EXPECT_EQ(res, 0);
res = ioctl(uinput_fd_.get(), UI_SET_EVBIT, EV_ABS);
EXPECT_EQ(res, 0);
res = ioctl(uinput_fd_.get(), UI_SET_EVBIT, EV_REL);
EXPECT_EQ(res, -1);
}
TEST_F(UinputTest, UiSetKeybit) {
int res = ioctl(uinput_fd_.get(), UI_SET_KEYBIT, KEY_SPACE);
EXPECT_EQ(res, 0);
res = ioctl(uinput_fd_.get(), UI_SET_KEYBIT, KEY_A);
EXPECT_EQ(res, 0);
}
TEST_F(UinputTest, UiSetPropbit) {
int res = ioctl(uinput_fd_.get(), UI_SET_PROPBIT, INPUT_PROP_DIRECT);
EXPECT_EQ(res, 0);
}
TEST_F(UinputTest, UiSetAbsbit) {
int res = ioctl(uinput_fd_.get(), UI_SET_ABSBIT, ABS_MT_SLOT);
EXPECT_EQ(res, 0);
res = ioctl(uinput_fd_.get(), UI_SET_ABSBIT, ABS_MT_TOUCH_MAJOR);
EXPECT_EQ(res, 0);
}
TEST_F(UinputTest, UiSetPhys) {
char name[] = "mouse0";
int res = ioctl(uinput_fd_.get(), UI_SET_PHYS, &name);
EXPECT_EQ(res, 0);
}
TEST_F(UinputTest, UiDevSetup) {
uinput_setup usetup{.id = {.bustype = BUS_USB, .vendor = GOOGLE_VENDOR_ID, .product = 1}};
strcpy(usetup.name, "Example device");
int res = ioctl(uinput_fd_.get(), UI_DEV_SETUP, &usetup);
EXPECT_EQ(res, 0);
}
TEST_F(UinputTest, UiDevSetupNull) {
int res = ioctl(uinput_fd_.get(), UI_DEV_SETUP, nullptr);
EXPECT_EQ(res, -1);
EXPECT_EQ(errno, EFAULT);
}
TEST_F(UinputTest, UiDevCreateFailedWithoutDevSetup) {
int res = ioctl(uinput_fd_.get(), UI_DEV_CREATE);
EXPECT_EQ(res, -1);
EXPECT_EQ(errno, EINVAL);
}
TEST_F(UinputTest, UiDevCreateKeyboard) {
uinput_setup usetup{.id = {.bustype = BUS_USB, .vendor = GOOGLE_VENDOR_ID, .product = 2}};
strcpy(usetup.name, "Example device");
int res = ioctl(uinput_fd_.get(), UI_DEV_SETUP, &usetup);
ASSERT_EQ(res, 0);
res = ioctl(uinput_fd_.get(), UI_DEV_CREATE);
EXPECT_EQ(res, 0);
}
TEST_F(UinputTest, UiDevCreateTouchscreen) {
int res = ioctl(uinput_fd_.get(), UI_SET_EVBIT, EV_ABS);
ASSERT_EQ(res, 0);
uinput_setup usetup{.id = {.bustype = BUS_USB, .vendor = GOOGLE_VENDOR_ID, .product = 3}};
strcpy(usetup.name, "Example device");
res = ioctl(uinput_fd_.get(), UI_DEV_SETUP, &usetup);
ASSERT_EQ(res, 0);
res = ioctl(uinput_fd_.get(), UI_DEV_CREATE);
EXPECT_EQ(res, 0);
}
TEST_F(UinputTest, UiDevCreateTouchscreenWithSize) {
int res = ioctl(uinput_fd_.get(), UI_SET_EVBIT, EV_ABS);
ASSERT_EQ(res, 0);
uinput_abs_setup uabs_x{};
uabs_x.code = ABS_MT_POSITION_X;
uabs_x.absinfo.maximum = 1001;
res = ioctl(uinput_fd_.get(), UI_ABS_SETUP, &uabs_x);
ASSERT_EQ(res, 0);
uinput_abs_setup uabs_y{};
uabs_y.code = ABS_MT_POSITION_Y;
uabs_y.absinfo.maximum = 1002;
res = ioctl(uinput_fd_.get(), UI_ABS_SETUP, &uabs_y);
ASSERT_EQ(res, 0);
uinput_setup usetup{.id = {.bustype = BUS_USB, .vendor = GOOGLE_VENDOR_ID, .product = 3}};
strcpy(usetup.name, "Example device");
res = ioctl(uinput_fd_.get(), UI_DEV_SETUP, &usetup);
ASSERT_EQ(res, 0);
auto ls_before = lsDir("/dev/input");
res = ioctl(uinput_fd_.get(), UI_DEV_CREATE);
EXPECT_EQ(res, 0);
auto ls_after = lsDir("/dev/input");
auto diff = lsDiff(ls_after, ls_before);
ASSERT_EQ(diff.size(), 1u);
auto new_device_name = diff[0];
EXPECT_EQ(new_device_name.substr(0, std::string("event").length()), "event");
auto new_device_fd = fbl::unique_fd(open(("/dev/input/" + new_device_name).c_str(), O_RDWR));
ASSERT_TRUE(new_device_fd.is_valid());
{
input_absinfo buf{};
ASSERT_EQ(0, ioctl(new_device_fd.get(), EVIOCGABS(ABS_MT_POSITION_X), &buf))
<< "get x-axis info failed: " << strerror(errno);
ASSERT_EQ(0.0, buf.minimum);
EXPECT_NEAR(buf.maximum, 1001.0, 0.1);
}
{
input_absinfo buf{};
ASSERT_EQ(0, ioctl(new_device_fd.get(), EVIOCGABS(ABS_MT_POSITION_Y), &buf))
<< "get y-axis info failed: " << strerror(errno);
ASSERT_EQ(0.0, buf.minimum);
EXPECT_NEAR(buf.maximum, 1002.0, 0.1);
}
}
TEST_F(UinputTest, UiDevCreateDestroyTouchscreenEvIoGid) {
auto ls_before = lsDir("/dev/input");
int res = ioctl(uinput_fd_.get(), UI_SET_EVBIT, EV_ABS);
ASSERT_EQ(res, 0);
uinput_setup usetup{.id = {.bustype = BUS_USB, .vendor = GOOGLE_VENDOR_ID, .product = 4}};
strcpy(usetup.name, "Example device");
res = ioctl(uinput_fd_.get(), UI_DEV_SETUP, &usetup);
ASSERT_EQ(res, 0);
res = ioctl(uinput_fd_.get(), UI_DEV_CREATE);
ASSERT_EQ(res, 0);
auto ls_after = lsDir("/dev/input");
auto diff = lsDiff(ls_after, ls_before);
ASSERT_EQ(diff.size(), 1u);
auto new_device_name = diff[0];
EXPECT_EQ(new_device_name.substr(0, std::string("event").length()), "event");
{
auto new_device_fd = fbl::unique_fd(open(("/dev/input/" + new_device_name).c_str(), O_RDWR));
ASSERT_TRUE(new_device_fd.is_valid());
input_id got_input_id;
res = ioctl(new_device_fd.get(), EVIOCGID, &got_input_id);
EXPECT_EQ(res, 0);
EXPECT_EQ(got_input_id.bustype, BUS_USB);
EXPECT_EQ(got_input_id.vendor, GOOGLE_VENDOR_ID);
EXPECT_EQ(got_input_id.product, usetup.id.product);
}
res = ioctl(uinput_fd_.get(), UI_DEV_DESTROY);
ASSERT_EQ(res, 0);
// Check that the device file no longer exits. Open should fail.
int fd = open(("/dev/input/" + new_device_name).c_str(), O_RDWR);
EXPECT_EQ(fd, -1);
EXPECT_EQ(errno, ENOENT);
}
TEST_F(UinputTest, UiDevCreateDestroyKeyboardEvIoGid) {
auto ls_before = lsDir("/dev/input");
uinput_setup usetup{.id = {.bustype = BUS_USB, .vendor = GOOGLE_VENDOR_ID, .product = 5}};
strcpy(usetup.name, "Example device");
int res = ioctl(uinput_fd_.get(), UI_DEV_SETUP, &usetup);
ASSERT_EQ(res, 0);
res = ioctl(uinput_fd_.get(), UI_DEV_CREATE);
ASSERT_EQ(res, 0);
auto ls_after = lsDir("/dev/input");
auto diff = lsDiff(ls_after, ls_before);
ASSERT_EQ(diff.size(), 1u);
auto new_device_name = diff[0];
EXPECT_EQ(new_device_name.substr(0, std::string("event").length()), "event");
{
auto new_device_fd = fbl::unique_fd(open(("/dev/input/" + new_device_name).c_str(), O_RDWR));
ASSERT_TRUE(new_device_fd.is_valid());
input_id got_input_id;
res = ioctl(new_device_fd.get(), EVIOCGID, &got_input_id);
EXPECT_EQ(res, 0);
EXPECT_EQ(got_input_id.bustype, BUS_USB);
EXPECT_EQ(got_input_id.vendor, GOOGLE_VENDOR_ID);
EXPECT_EQ(got_input_id.product, usetup.id.product);
}
res = ioctl(uinput_fd_.get(), UI_DEV_DESTROY);
ASSERT_EQ(res, 0);
// Check that the device file no longer exits. Open should fail.
int fd = open(("/dev/input/" + new_device_name).c_str(), O_RDWR);
EXPECT_EQ(fd, -1);
EXPECT_EQ(errno, ENOENT);
}
TEST_F(UinputTest, UiDevDestroy) {
uinput_setup usetup{.id = {.bustype = BUS_USB, .vendor = GOOGLE_VENDOR_ID, .product = 5}};
strcpy(usetup.name, "Example device");
int res = ioctl(uinput_fd_.get(), UI_DEV_SETUP, &usetup);
ASSERT_EQ(res, 0);
res = ioctl(uinput_fd_.get(), UI_DEV_CREATE);
ASSERT_EQ(res, 0);
res = ioctl(uinput_fd_.get(), UI_DEV_DESTROY);
EXPECT_EQ(res, 0);
}
TEST_F(UinputTest, WriteEVKEY) {
// Need to create Keyboard device first
uinput_setup usetup{.id = {.bustype = BUS_USB, .vendor = GOOGLE_VENDOR_ID, .product = 2}};
strcpy(usetup.name, "Example device");
int r = ioctl(uinput_fd_.get(), UI_DEV_SETUP, &usetup);
ASSERT_EQ(r, 0);
r = ioctl(uinput_fd_.get(), UI_DEV_CREATE);
EXPECT_EQ(r, 0);
/* timestamp values are ignored */
struct timeval t = {.tv_sec = 0, .tv_usec = 0};
// Key press
struct input_event press_e = {.time = t, .type = EV_KEY, .code = KEY_SPACE, .value = 1};
auto res = write(uinput_fd_.get(), &press_e, sizeof(press_e));
EXPECT_EQ(res, static_cast<ssize_t>(sizeof(press_e)));
// Report the event
struct input_event sync_e = {.time = t, .type = EV_SYN, .code = SYN_REPORT, .value = 0};
res = write(uinput_fd_.get(), &sync_e, sizeof(sync_e));
EXPECT_EQ(res, static_cast<ssize_t>(sizeof(sync_e)));
// Key release
struct input_event release_e = {.time = t, .type = EV_KEY, .code = KEY_SPACE, .value = 0};
res = write(uinput_fd_.get(), &release_e, sizeof(release_e));
EXPECT_EQ(res, static_cast<ssize_t>(sizeof(release_e)));
// Report the event
res = write(uinput_fd_.get(), &sync_e, sizeof(sync_e));
EXPECT_EQ(res, static_cast<ssize_t>(sizeof(sync_e)));
}
TEST_F(UinputTest, WriteEVABS) {
// Need to create Touchscreen device first
int r = ioctl(uinput_fd_.get(), UI_SET_EVBIT, EV_ABS);
ASSERT_EQ(r, 0);
uinput_setup usetup{.id = {.bustype = BUS_USB, .vendor = GOOGLE_VENDOR_ID, .product = 2}};
strcpy(usetup.name, "Example device");
r = ioctl(uinput_fd_.get(), UI_DEV_SETUP, &usetup);
ASSERT_EQ(r, 0);
r = ioctl(uinput_fd_.get(), UI_DEV_CREATE);
EXPECT_EQ(r, 0);
/* timestamp values are ignored */
struct timeval t = {.tv_sec = 0, .tv_usec = 0};
// Touch contact
struct input_event ev_slot = {.time = t, .type = EV_ABS, .code = ABS_MT_SLOT, .value = 0};
struct input_event ev_tracking_id = {
.time = t, .type = EV_ABS, .code = ABS_MT_TRACKING_ID, .value = 0};
struct input_event ev_pos_x = {.time = t, .type = EV_ABS, .code = ABS_MT_POSITION_X, .value = 10};
struct input_event ev_pos_y = {.time = t, .type = EV_ABS, .code = ABS_MT_POSITION_Y, .value = 10};
auto res = write(uinput_fd_.get(), &ev_slot, sizeof(ev_slot));
EXPECT_EQ(res, static_cast<ssize_t>(sizeof(ev_slot)));
res = write(uinput_fd_.get(), &ev_tracking_id, sizeof(ev_tracking_id));
EXPECT_EQ(res, static_cast<ssize_t>(sizeof(ev_tracking_id)));
res = write(uinput_fd_.get(), &ev_pos_x, sizeof(ev_pos_x));
EXPECT_EQ(res, static_cast<ssize_t>(sizeof(ev_pos_x)));
res = write(uinput_fd_.get(), &ev_pos_y, sizeof(ev_pos_y));
EXPECT_EQ(res, static_cast<ssize_t>(sizeof(ev_pos_y)));
// Report the event
struct input_event sync_e = {.time = t, .type = EV_SYN, .code = SYN_REPORT, .value = 0};
res = write(uinput_fd_.get(), &sync_e, sizeof(sync_e));
EXPECT_EQ(res, static_cast<ssize_t>(sizeof(sync_e)));
// Touch contact released
struct input_event ev_tracking_id_lifted = {
.time = t, .type = EV_ABS, .code = ABS_MT_TRACKING_ID, .value = -1};
res = write(uinput_fd_.get(), &ev_slot, sizeof(ev_slot));
EXPECT_EQ(res, static_cast<ssize_t>(sizeof(ev_slot)));
res = write(uinput_fd_.get(), &ev_tracking_id_lifted, sizeof(ev_tracking_id_lifted));
EXPECT_EQ(res, static_cast<ssize_t>(sizeof(ev_tracking_id_lifted)));
// Report the event
res = write(uinput_fd_.get(), &sync_e, sizeof(sync_e));
EXPECT_EQ(res, static_cast<ssize_t>(sizeof(sync_e)));
}
} // namespace