xref: /openbmc/qemu/tests/qtest/dbus-display-test.c (revision e88510fcdc13380bd4895a17d6f8a0b3a3325b85)
1 #include "qemu/osdep.h"
2 #include "qemu/sockets.h"
3 #include "qemu/dbus.h"
4 #include "qemu/sockets.h"
5 #include "glib.h"
6 #include "glibconfig.h"
7 #include <gio/gio.h>
8 #include <gio/gunixfdlist.h>
9 #include "libqtest.h"
10 #include "ui/dbus-display1.h"
11 
12 static GDBusConnection*
13 test_dbus_p2p_from_fd(int fd)
14 {
15     g_autoptr(GError) err = NULL;
16     g_autoptr(GSocket) socket = NULL;
17     g_autoptr(GSocketConnection) socketc = NULL;
18     GDBusConnection *conn;
19 
20 #ifdef WIN32
21     socket = g_socket_new_from_fd(_get_osfhandle(fd), &err);
22 #else
23     socket = g_socket_new_from_fd(fd, &err);
24 #endif
25     g_assert_no_error(err);
26 
27     socketc = g_socket_connection_factory_create_connection(socket);
28     g_assert(socketc != NULL);
29 
30     conn = g_dbus_connection_new_sync(
31         G_IO_STREAM(socketc), NULL,
32         G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT |
33         G_DBUS_CONNECTION_FLAGS_DELAY_MESSAGE_PROCESSING,
34         NULL, NULL, &err);
35     g_assert_no_error(err);
36 
37     return conn;
38 }
39 
40 static void
41 test_setup(QTestState **qts, GDBusConnection **conn)
42 {
43     int pair[2];
44 
45     *qts = qtest_init("-display dbus,p2p=yes -name dbus-test");
46 
47     g_assert_cmpint(qemu_socketpair(AF_UNIX, SOCK_STREAM, 0, pair), ==, 0);
48 
49     qtest_qmp_add_client(*qts, "@dbus-display", pair[1]);
50 
51     *conn = test_dbus_p2p_from_fd(pair[0]);
52     g_dbus_connection_start_message_processing(*conn);
53 }
54 
55 static void
56 test_dbus_display_vm(void)
57 {
58     g_autoptr(GError) err = NULL;
59     g_autoptr(GDBusConnection) conn = NULL;
60     g_autoptr(QemuDBusDisplay1VMProxy) vm = NULL;
61     QTestState *qts = NULL;
62 
63     test_setup(&qts, &conn);
64 
65     vm = QEMU_DBUS_DISPLAY1_VM_PROXY(
66         qemu_dbus_display1_vm_proxy_new_sync(
67             conn,
68             G_DBUS_PROXY_FLAGS_NONE,
69             NULL,
70             DBUS_DISPLAY1_ROOT "/VM",
71             NULL,
72             &err));
73     g_assert_no_error(err);
74 
75     g_assert_cmpstr(
76         qemu_dbus_display1_vm_get_name(QEMU_DBUS_DISPLAY1_VM(vm)),
77         ==,
78         "dbus-test");
79     qtest_quit(qts);
80 }
81 
82 typedef struct TestDBusConsoleRegister {
83     GMainLoop *loop;
84     GThread *thread;
85     GDBusConnection *listener_conn;
86     GDBusObjectManagerServer *server;
87     bool with_map;
88 } TestDBusConsoleRegister;
89 
90 static gboolean listener_handle_scanout(
91     QemuDBusDisplay1Listener *object,
92     GDBusMethodInvocation *invocation,
93     guint arg_width,
94     guint arg_height,
95     guint arg_stride,
96     guint arg_pixman_format,
97     GVariant *arg_data,
98     TestDBusConsoleRegister *test)
99 {
100     if (!test->with_map) {
101         g_main_loop_quit(test->loop);
102     }
103 
104     return DBUS_METHOD_INVOCATION_HANDLED;
105 }
106 
107 #ifndef WIN32
108 static gboolean listener_handle_scanout_map(
109     QemuDBusDisplay1ListenerUnixMap *object,
110     GDBusMethodInvocation *invocation,
111     GUnixFDList *fd_list,
112     GVariant *arg_handle,
113     guint arg_offset,
114     guint arg_width,
115     guint arg_height,
116     guint arg_stride,
117     guint arg_pixman_format,
118     TestDBusConsoleRegister *test)
119 {
120     int fd = -1;
121     gint32 handle = g_variant_get_handle(arg_handle);
122     g_autoptr(GError) error = NULL;
123     void *addr = NULL;
124     size_t len = arg_height * arg_stride;
125 
126     g_assert_cmpuint(g_unix_fd_list_get_length(fd_list), ==, 1);
127     fd = g_unix_fd_list_get(fd_list, handle, &error);
128     g_assert_no_error(error);
129 
130     addr = mmap(NULL, len, PROT_READ, MAP_PRIVATE, fd, arg_offset);
131     g_assert_no_errno(addr == MAP_FAILED ? -1 : 0);
132     g_assert_no_errno(munmap(addr, len));
133 
134     g_main_loop_quit(test->loop);
135 
136     close(fd);
137     return DBUS_METHOD_INVOCATION_HANDLED;
138 }
139 #endif
140 
141 static void
142 test_dbus_console_setup_listener(TestDBusConsoleRegister *test, bool with_map)
143 {
144     g_autoptr(GDBusObjectSkeleton) listener = NULL;
145     g_autoptr(QemuDBusDisplay1ListenerSkeleton) iface = NULL;
146 
147     test->server = g_dbus_object_manager_server_new(DBUS_DISPLAY1_ROOT);
148     listener = g_dbus_object_skeleton_new(DBUS_DISPLAY1_ROOT "/Listener");
149     iface = QEMU_DBUS_DISPLAY1_LISTENER_SKELETON(
150         qemu_dbus_display1_listener_skeleton_new());
151     g_object_connect(iface,
152                      "signal::handle-scanout", listener_handle_scanout, test,
153                      NULL);
154     g_dbus_object_skeleton_add_interface(listener,
155                                          G_DBUS_INTERFACE_SKELETON(iface));
156     if (with_map) {
157 #ifdef WIN32
158         g_test_skip("map test lacking on win32");
159         return;
160 #else
161         g_autoptr(QemuDBusDisplay1ListenerUnixMapSkeleton) iface_map =
162             QEMU_DBUS_DISPLAY1_LISTENER_UNIX_MAP_SKELETON(
163                 qemu_dbus_display1_listener_unix_map_skeleton_new());
164 
165         g_object_connect(iface_map,
166                          "signal::handle-scanout-map", listener_handle_scanout_map, test,
167                          NULL);
168         g_dbus_object_skeleton_add_interface(listener,
169                                              G_DBUS_INTERFACE_SKELETON(iface_map));
170         g_object_set(iface, "interfaces",
171             (const gchar *[]) { "org.qemu.Display1.Listener.Unix.Map", NULL },
172             NULL);
173 #endif
174     }
175     g_dbus_object_manager_server_export(test->server, listener);
176     g_dbus_object_manager_server_set_connection(test->server,
177                                                 test->listener_conn);
178 
179     g_dbus_connection_start_message_processing(test->listener_conn);
180 }
181 
182 static void
183 test_dbus_console_registered(GObject *source_object,
184                              GAsyncResult *res,
185                              gpointer user_data)
186 {
187     TestDBusConsoleRegister *test = user_data;
188     g_autoptr(GError) err = NULL;
189 
190     qemu_dbus_display1_console_call_register_listener_finish(
191         QEMU_DBUS_DISPLAY1_CONSOLE(source_object),
192 #ifndef WIN32
193         NULL,
194 #endif
195         res, &err);
196 
197     if (g_error_matches(err, G_DBUS_ERROR, G_DBUS_ERROR_UNKNOWN_METHOD)) {
198         g_test_skip("The VM doesn't have a console!");
199         g_main_loop_quit(test->loop);
200         return;
201     }
202 
203     g_assert_no_error(err);
204 
205     test->listener_conn = g_thread_join(test->thread);
206     test_dbus_console_setup_listener(test, test->with_map);
207 }
208 
209 static gpointer
210 test_dbus_p2p_server_setup_thread(gpointer data)
211 {
212     return test_dbus_p2p_from_fd(GPOINTER_TO_INT(data));
213 }
214 
215 static void
216 test_dbus_display_console(const void* data)
217 {
218     g_autoptr(GError) err = NULL;
219     g_autoptr(GDBusConnection) conn = NULL;
220     g_autoptr(QemuDBusDisplay1ConsoleProxy) console = NULL;
221     g_autoptr(GMainLoop) loop = NULL;
222     QTestState *qts = NULL;
223     int pair[2];
224     TestDBusConsoleRegister test = { 0, .with_map = GPOINTER_TO_INT(data) };
225 #ifdef WIN32
226     WSAPROTOCOL_INFOW info;
227     g_autoptr(GVariant) listener = NULL;
228 #else
229     g_autoptr(GUnixFDList) fd_list = NULL;
230     int idx;
231 #endif
232 
233     test_setup(&qts, &conn);
234 
235     g_assert_cmpint(qemu_socketpair(AF_UNIX, SOCK_STREAM, 0, pair), ==, 0);
236 #ifndef WIN32
237     fd_list = g_unix_fd_list_new();
238     idx = g_unix_fd_list_append(fd_list, pair[1], NULL);
239 #endif
240 
241     console = QEMU_DBUS_DISPLAY1_CONSOLE_PROXY(
242         qemu_dbus_display1_console_proxy_new_sync(
243             conn,
244             G_DBUS_PROXY_FLAGS_NONE,
245             NULL,
246             "/org/qemu/Display1/Console_0",
247             NULL,
248             &err));
249     g_assert_no_error(err);
250 
251     test.loop = loop = g_main_loop_new(NULL, FALSE);
252     test.thread = g_thread_new(NULL, test_dbus_p2p_server_setup_thread,
253                                GINT_TO_POINTER(pair[0]));
254 
255 #ifdef WIN32
256     if (WSADuplicateSocketW(_get_osfhandle(pair[1]),
257                             GetProcessId((HANDLE) qtest_pid(qts)),
258                             &info) == SOCKET_ERROR)
259     {
260         g_autofree char *emsg = g_win32_error_message(WSAGetLastError());
261         g_error("WSADuplicateSocket failed: %s", emsg);
262     }
263     close(pair[1]);
264     listener = g_variant_new_fixed_array(G_VARIANT_TYPE_BYTE,
265                                          &info,
266                                          sizeof(info),
267                                          1);
268 #endif
269 
270     qemu_dbus_display1_console_call_register_listener(
271         QEMU_DBUS_DISPLAY1_CONSOLE(console),
272 #ifdef WIN32
273         listener,
274 #else
275         g_variant_new_handle(idx),
276 #endif
277         G_DBUS_CALL_FLAGS_NONE,
278         -1,
279 #ifndef WIN32
280         fd_list,
281 #endif
282         NULL,
283         test_dbus_console_registered,
284         &test);
285 
286     g_main_loop_run(loop);
287 
288     g_clear_object(&test.server);
289     g_clear_object(&test.listener_conn);
290     qtest_quit(qts);
291 }
292 
293 static void
294 test_dbus_display_keyboard(void)
295 {
296     g_autoptr(GError) err = NULL;
297     g_autoptr(GDBusConnection) conn = NULL;
298     g_autoptr(QemuDBusDisplay1KeyboardProxy) keyboard = NULL;
299     QTestState *qts = NULL;
300 
301     test_setup(&qts, &conn);
302 
303     keyboard = QEMU_DBUS_DISPLAY1_KEYBOARD_PROXY(
304         qemu_dbus_display1_keyboard_proxy_new_sync(
305             conn,
306             G_DBUS_PROXY_FLAGS_NONE,
307             NULL,
308             "/org/qemu/Display1/Console_0",
309             NULL,
310             &err));
311     g_assert_no_error(err);
312 
313     g_assert_cmpint(qtest_inb(qts, 0x64) & 0x1, ==, 0);
314     g_assert_cmpint(qtest_inb(qts, 0x60), ==, 0);
315 
316     qemu_dbus_display1_keyboard_call_press_sync(
317         QEMU_DBUS_DISPLAY1_KEYBOARD(keyboard),
318         0x1C, /* qnum enter */
319         G_DBUS_CALL_FLAGS_NONE,
320         -1,
321         NULL,
322         &err);
323     if (g_error_matches(err, G_DBUS_ERROR, G_DBUS_ERROR_UNKNOWN_METHOD)) {
324         g_test_skip("The VM doesn't have a console!");
325         qtest_quit(qts);
326         return;
327     }
328 
329     g_assert_no_error(err);
330 
331     /* may be should wait for interrupt? */
332     g_assert_cmpint(qtest_inb(qts, 0x64) & 0x1, ==, 1);
333     g_assert_cmpint(qtest_inb(qts, 0x60), ==, 0x5A); /* scan code 2 enter */
334 
335     qemu_dbus_display1_keyboard_call_release_sync(
336         QEMU_DBUS_DISPLAY1_KEYBOARD(keyboard),
337         0x1C, /* qnum enter */
338         G_DBUS_CALL_FLAGS_NONE,
339         -1,
340         NULL,
341         &err);
342     g_assert_no_error(err);
343 
344     g_assert_cmpint(qtest_inb(qts, 0x64) & 0x1, ==, 1);
345     g_assert_cmpint(qtest_inb(qts, 0x60), ==, 0xF0); /* scan code 2 release */
346     g_assert_cmpint(qtest_inb(qts, 0x60), ==, 0x5A); /* scan code 2 enter */
347 
348     g_assert_cmpint(qemu_dbus_display1_keyboard_get_modifiers(
349                         QEMU_DBUS_DISPLAY1_KEYBOARD(keyboard)), ==, 0);
350 
351     qtest_quit(qts);
352 }
353 
354 int
355 main(int argc, char **argv)
356 {
357     g_test_init(&argc, &argv, NULL);
358 
359     qtest_add_func("/dbus-display/vm", test_dbus_display_vm);
360     qtest_add_data_func("/dbus-display/console", GINT_TO_POINTER(false), test_dbus_display_console);
361     qtest_add_data_func("/dbus-display/console/map", GINT_TO_POINTER(true), test_dbus_display_console);
362     qtest_add_func("/dbus-display/keyboard", test_dbus_display_keyboard);
363 
364     return g_test_run();
365 }
366