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