2 Commits

Author SHA1 Message Date
lirent 85041c12ab fix(input): resolve the real evdev node for input-linux, not the sysfs name
UI_GET_SYSNAME returns the input-class directory name (inputNNN), not a usable
device node -- /dev/input/inputNNN does not exist, so QEMU's input-linux failed
with "Could not open". Resolve the actual evdev node (/dev/input/eventN) as the
event* child of /sys/class/input/<sysname>/. Confirmed against the live host
sysfs. The unit path can't exercise this (no /dev/uinput in CI) -- armed only.

Bump 0.3.10.
2026-06-24 16:50:05 +03:00
lirent 228dc5af79 fix(input): use the hyphenated QMP object-add/object-del command names
The 0.3.8 bridge sent object_add/object_del (underscores, modeled on the legacy
device_add) but QMP's object commands are hyphenated -- object-add/object-del --
so QEMU rejected them with CommandNotFound and no bridge was set up. Confirmed
against the live monitor: object-add/object-del exist, object_add does not, and
input-linux is a creatable type. The stub QMP in the unit test used the same
wrong string, so it didn't catch this -- only the armed monitor did.

Bump 0.3.9.
2026-06-24 16:41:46 +03:00
4 changed files with 33 additions and 10 deletions
+1 -1
View File
@@ -1,7 +1,7 @@
cmake_minimum_required(VERSION 3.16)
# Single source of truth for the version: CI passes -DVMSIG_VERSION=${TAG#v}, so the project
# version (-> libvgpu-perception SONAME/.so version) and the .deb version come from one tag.
set(VMSIG_VERSION "0.3.8" CACHE STRING "Release version (MAJOR.MINOR.PATCH); CI passes the tag")
set(VMSIG_VERSION "0.3.10" CACHE STRING "Release version (MAJOR.MINOR.PATCH); CI passes the tag")
project(vmsig VERSION ${VMSIG_VERSION} LANGUAGES C)
set(CMAKE_C_STANDARD 17)
+5 -3
View File
@@ -133,7 +133,9 @@ static int bridge_add(struct vmsig_adapter* a, char ab, const char* evdev, int g
uint32_t qid = ++a->next_id;
char line[320];
int len = snprintf(line, sizeof line,
"{\"execute\":\"object_add\",\"arguments\":{\"qom-type\":\"input-linux\","
/* QMP object commands use HYPHENS (object-add/object-del); only the legacy
* device_add/device_del keep underscores. Underscore here => CommandNotFound. */
"{\"execute\":\"object-add\",\"arguments\":{\"qom-type\":\"input-linux\","
"\"id\":\"%s\",\"evdev\":\"%s\"%s},\"id\":%u}\r\n",
id, evdev, grab_all ? ",\"grab_all\":true" : "", qid);
p->used = 1; p->id = qid; p->origin = 0; p->corr = 0; p->op = VH_OP_BRIDGE_ADD;
@@ -150,7 +152,7 @@ static void bridge_del_fire(struct vmsig_adapter* a, char ab) {
bridge_id(id, sizeof id, a->endpoint, ab);
char line[160];
int len = snprintf(line, sizeof line,
"{\"execute\":\"object_del\",\"arguments\":{\"id\":\"%s\"}}\r\n", id);
"{\"execute\":\"object-del\",\"arguments\":{\"id\":\"%s\"}}\r\n", id);
ssize_t r = write(a->fd, line, (size_t)len);
(void)r;
}
@@ -227,7 +229,7 @@ static void handle_line(struct vmsig_adapter* a, const char* line) {
/* Bridge infrastructure: never surfaces to control. Log a failed add so the
* stand can see it; otherwise silent. */
if (p->op == VH_OP_BRIDGE_ADD && strstr(line, "\"error\""))
fprintf(stderr, "vmsig vmhost: input-linux object_add failed: %s\n", line);
fprintf(stderr, "vmsig vmhost: input-linux object-add failed: %s\n", line);
} else if (p->op == VMSIG_VMOP_QUERY && strstr(line, "\"return\"")) {
char stbuf[32]; uint32_t s = VMSIG_VM_UNKNOWN;
if (jstr(line, "\"status\"", stbuf, sizeof stbuf)) s = status_state(stbuf);
+23 -2
View File
@@ -31,6 +31,7 @@
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <dirent.h>
#include <sys/ioctl.h>
#include <linux/uinput.h>
#include <linux/input-event-codes.h>
@@ -120,8 +121,28 @@ static int uinput_create(const uinput_role* role, const vmctl_uinput_id* id,
char sysname[64] = {0};
evdev[0] = '\0';
if (ioctl(fd, UI_GET_SYSNAME(sizeof sysname), sysname) >= 0)
snprintf(evdev, 64, "/dev/input/%s", sysname);
/* UI_GET_SYSNAME returns the input-class directory name (e.g. "input174"), NOT the usable
* device node: a bare /dev/input/<sysname> does not exist (QEMU input-linux fails to open
* it). The evdev character device is /dev/input/eventN, exposed as the "event*" child of
* /sys/class/input/<sysname>/ — resolve it there. */
if (ioctl(fd, UI_GET_SYSNAME(sizeof sysname), sysname) >= 0) {
char dpath[128];
snprintf(dpath, sizeof dpath, "/sys/class/input/%s", sysname);
DIR* dir = opendir(dpath);
if (dir) {
struct dirent* de;
while ((de = readdir(dir)) != NULL)
if (strncmp(de->d_name, "event", 5) == 0) {
size_t nl = strlen(de->d_name);
if (nl + 11 < 64) { /* "/dev/input/" is 11 chars + name + NUL */
memcpy(evdev, "/dev/input/", 11);
memcpy(evdev + 11, de->d_name, nl + 1);
}
break;
}
closedir(dir);
}
}
if (!evdev[0]) {
ioctl(fd, UI_DEV_DESTROY);
+4 -4
View File
@@ -142,7 +142,7 @@ int main(void) {
/* On READY the seam adds the input-linux bridge BEFORE the SEAM_UP-driven CMD_VM query
* (bridge ids 1,2; query id 3). Verify both object_add lines and their properties. */
CHECK(srv_expect(c, "object_add"), "seam sent object_add (input-linux bridge)");
CHECK(srv_expect(c, "object-add"), "seam sent object-add (input-linux bridge)");
CHECK(srv_expect(c, "\"input-linux\""), "bridge object uses qom-type input-linux");
CHECK(srv_expect(c, "\"vmsig-in-a-0\""), "bridge A has neutral per-endpoint id");
CHECK(srv_expect(c, "\"vmsig-in-b-0\""), "bridge B has neutral per-endpoint id");
@@ -224,12 +224,12 @@ int main(void) {
pthread_join(th2, NULL);
vmsig_core_free(core2);
for (int i = 0; i < 50 && !strstr(g_rx, "object_del"); i++) {
for (int i = 0; i < 50 && !strstr(g_rx, "object-del"); i++) {
rx_pump(c2);
struct timespec t = { 0, 10 * 1000000 }; nanosleep(&t, NULL);
}
const char* d = strstr(g_rx, "object_del");
CHECK(d != NULL, "scenario2: teardown fired object_del");
const char* d = strstr(g_rx, "object-del");
CHECK(d != NULL, "scenario2: teardown fired object-del");
CHECK(strstr(g_rx, "vmsig-in-a-0"), "scenario2: object_del for bridge A");
CHECK(strstr(g_rx, "vmsig-in-b-0"), "scenario2: object_del for bridge B");
close(c2);