Skip to content

Commit 6850048

Browse files
committed
fix(uinput): resolve udev device node permission race condition
1 parent a2b9f94 commit 6850048

2 files changed

Lines changed: 37 additions & 33 deletions

File tree

src/evdev/uinput.c

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -177,11 +177,18 @@ static int evdev_wait_for_uinput_event_path(const char *sysname, char *path,
177177

178178
for (i = 0; i < 50; i++) {
179179
if (evdev_find_uinput_event_path(sysname, path, path_len) == 0) {
180-
return 0;
180+
int test_fd = open(path, O_RDONLY | O_NONBLOCK | O_CLOEXEC);
181+
if (test_fd >= 0) {
182+
close(test_fd);
183+
return 0;
184+
}
185+
saved_errno = errno;
186+
} else {
187+
saved_errno = errno;
181188
}
182189

183-
saved_errno = errno;
184-
if (saved_errno != ENOENT && saved_errno != ENOTDIR) {
190+
if (saved_errno != ENOENT && saved_errno != ENOTDIR &&
191+
saved_errno != EACCES) {
185192
break;
186193
}
187194
if (i + 1 < 50) {

tests/uinput.test.lua

Lines changed: 27 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,25 @@
11
---@diagnostic disable: param-type-mismatch, assign-type-mismatch
22

33
local evdev = require "evdev"
4-
local system = require "system"
54

65
local UInput = evdev.uinput.create
76
local normalize = evdev.uinput._normalize ---@diagnostic disable-line: undefined-field
87
local ecodes = evdev.ecodes
9-
local sleep = system.sleep
108
local fmt = string.format
119

1210
describe("evdev.uinput()", function()
11+
local ui
12+
13+
before_each(function()
14+
ui = nil
15+
end)
16+
17+
after_each(function()
18+
if ui then
19+
assert(ui:close())
20+
end
21+
end)
22+
1323
describe("validations", function()
1424
it("rejects a non-table spec", function()
1525
assert.Error(function()
@@ -183,31 +193,22 @@ describe("evdev.uinput()", function()
183193
end)
184194

185195
describe("UInput object", function()
186-
local ui
187-
188-
before_each(function()
189-
if not (ui and ui:is_open()) then
190-
ui = assert(UInput({ name = "Lua Virtual Device" }))
191-
sleep(0.1)
192-
end
193-
end)
194-
195-
teardown(function()
196-
ui:close()
197-
end)
198-
199196
it("loads metadata fields", function()
197+
local name = "Lua Virtual Device"
198+
ui = assert(UInput({ name = name }))
200199
assert.Match("^/dev/input/event%d+$", ui.path)
201-
assert.Equal("Lua Virtual Device", ui.name)
200+
assert.Equal(name, ui.name)
202201
end)
203202

204203
it("reports open state and closes cleanly", function()
204+
ui = assert(UInput())
205205
assert.True(ui:is_open())
206206
assert.True(ui:close())
207207
assert.False(ui:is_open())
208208
end)
209209

210210
it("returns a closed-device error for emit after close", function()
211+
ui = assert(UInput())
211212
assert.True(ui:close())
212213

213214
local ok, err = ui:emit(ecodes.EV_KEY, ecodes.KEY_A, 1)
@@ -216,6 +217,7 @@ describe("evdev.uinput()", function()
216217
end)
217218

218219
it("returns a closed-device error for sync after close", function()
220+
ui = assert(UInput())
219221
assert.True(ui:close())
220222

221223
local ok, err = ui:sync()
@@ -224,13 +226,15 @@ describe("evdev.uinput()", function()
224226
end)
225227

226228
it("returns repeat settings for repeat-capable devices", function()
229+
ui = assert(UInput())
227230
local delay, period, err = ui:get_repeat()
228231
assert.Number(delay)
229232
assert.Number(period)
230233
assert.Nil(err)
231234
end)
232235

233236
it("updates repeat settings for repeat-capable devices", function()
237+
ui = assert(UInput())
234238
local delay, period, err = ui:get_repeat()
235239
assert.Number(delay)
236240
assert.Number(period)
@@ -239,36 +243,33 @@ describe("evdev.uinput()", function()
239243
end)
240244

241245
it("returns an unsupported get_repeat error for non-repeat devices", function()
242-
local ui = assert(UInput({
246+
ui = assert(UInput({
243247
name = "uinput unsupported get repeat test",
244248
keys = { ecodes.BTN_LEFT },
245249
rels = { ecodes.REL_X },
246250
}))
247251

248-
sleep(0.1)
249-
250252
local delay, period, err = ui:get_repeat()
251253
assert.Nil(delay)
252254
assert.Nil(period)
253255
assert.Equal(fmt("get repeat %s: device does not support repeat settings", ui.path), err)
254-
assert.True(ui:close())
255256
end)
256257

257258
it("returns an unsupported set_repeat error for non-repeat devices", function()
258-
local ui = assert(UInput({
259+
ui = assert(UInput({
259260
name = "uinput unsupported set repeat test",
260261
keys = { ecodes.BTN_RIGHT },
261262
rels = { ecodes.REL_Y },
262263
}))
263-
sleep(0.1)
264+
264265
local ok, err = ui:set_repeat(300, 40)
265266
assert.Nil(ok)
266267
assert.Equal(fmt("set repeat %s: device does not support repeat settings", ui.path), err)
267268
assert.True(ui:close())
268269
end)
269270

270271
it("returns a closed-device error for get_repeat after close", function()
271-
local ui = assert(UInput({ name = "closed get repeat test" }))
272+
ui = assert(UInput({ name = "closed get repeat test" }))
272273
assert.True(ui:close())
273274

274275
local delay, period, err = ui:get_repeat()
@@ -278,7 +279,7 @@ describe("evdev.uinput()", function()
278279
end)
279280

280281
it("returns a closed-device error for set_repeat after close", function()
281-
local ui = assert(UInput({ name = "closed set repeat test" }))
282+
ui = assert(UInput({ name = "closed set repeat test" }))
282283
assert.True(ui:close())
283284

284285
local ok, err = ui:set_repeat(300, 40)
@@ -287,7 +288,7 @@ describe("evdev.uinput()", function()
287288
end)
288289

289290
it("validates emit argument types before using the handle", function()
290-
local ui = assert(UInput({ name = "emit validation test" }))
291+
ui = assert(UInput({ name = "emit validation test" }))
291292

292293
assert.Error(function()
293294
ui:emit("key", ecodes.KEY_A, 1)
@@ -300,12 +301,10 @@ describe("evdev.uinput()", function()
300301
assert.Error(function()
301302
ui:emit(ecodes.EV_KEY, ecodes.KEY_A, "down")
302303
end, "value: (number expected, got string)")
303-
304-
ui:close()
305304
end)
306305

307306
it("validates set_repeat argument types before using the handle", function()
308-
local ui = assert(UInput({ name = "set repeat validation test" }))
307+
ui = assert(UInput({ name = "set repeat validation test" }))
309308

310309
assert.Error(function()
311310
ui:set_repeat("fast", 40)
@@ -314,8 +313,6 @@ describe("evdev.uinput()", function()
314313
assert.Error(function()
315314
ui:set_repeat(300, "slow")
316315
end, "period: (number expected, got string)")
317-
318-
ui:close()
319316
end)
320317
end)
321318
end)

0 commit comments

Comments
 (0)