#include "qemu/osdep.h" #include "qemu/sockets.h" #include "qemu/dbus.h" #include "qemu/sockets.h" #include "glib.h" #include "glibconfig.h" #include #include #include "libqtest.h" #ifndef WIN32 #include #endif #include "ui/dbus-display1.h" static GDBusConnection* test_dbus_p2p_from_fd(int fd) { g_autoptr(GError) err = NULL; g_autoptr(GSocket) socket = NULL; g_autoptr(GSocketConnection) socketc = NULL; GDBusConnection *conn; #ifdef WIN32 socket = g_socket_new_from_fd(_get_osfhandle(fd), &err); #else socket = g_socket_new_from_fd(fd, &err); #endif g_assert_no_error(err); socketc = g_socket_connection_factory_create_connection(socket); g_assert(socketc != NULL); conn = g_dbus_connection_new_sync( G_IO_STREAM(socketc), NULL, G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT | G_DBUS_CONNECTION_FLAGS_DELAY_MESSAGE_PROCESSING, NULL, NULL, &err); g_assert_no_error(err); return conn; } static void test_setup(QTestState **qts, GDBusConnection **conn) { int pair[2]; *qts = qtest_init("-display dbus,p2p=yes -name dbus-test"); g_assert_cmpint(qemu_socketpair(AF_UNIX, SOCK_STREAM, 0, pair), ==, 0); qtest_qmp_add_client(*qts, "@dbus-display", pair[1]); *conn = test_dbus_p2p_from_fd(pair[0]); g_dbus_connection_start_message_processing(*conn); } static void test_dbus_display_vm(void) { g_autoptr(GError) err = NULL; g_autoptr(GDBusConnection) conn = NULL; g_autoptr(QemuDBusDisplay1VMProxy) vm = NULL; QTestState *qts = NULL; test_setup(&qts, &conn); vm = QEMU_DBUS_DISPLAY1_VM_PROXY( qemu_dbus_display1_vm_proxy_new_sync( conn, G_DBUS_PROXY_FLAGS_NONE, NULL, DBUS_DISPLAY1_ROOT "/VM", NULL, &err)); g_assert_no_error(err); g_assert_cmpstr( qemu_dbus_display1_vm_get_name(QEMU_DBUS_DISPLAY1_VM(vm)), ==, "dbus-test"); qtest_quit(qts); } typedef struct TestDBusConsoleRegister { GMainLoop *loop; GThread *thread; GDBusConnection *listener_conn; GDBusObjectManagerServer *server; bool with_map; } TestDBusConsoleRegister; static gboolean listener_handle_scanout( QemuDBusDisplay1Listener *object, GDBusMethodInvocation *invocation, guint arg_width, guint arg_height, guint arg_stride, guint arg_pixman_format, GVariant *arg_data, TestDBusConsoleRegister *test) { if (!test->with_map) { g_main_loop_quit(test->loop); } return DBUS_METHOD_INVOCATION_HANDLED; } #ifndef WIN32 static gboolean listener_handle_scanout_map( QemuDBusDisplay1ListenerUnixMap *object, GDBusMethodInvocation *invocation, GUnixFDList *fd_list, GVariant *arg_handle, guint arg_offset, guint arg_width, guint arg_height, guint arg_stride, guint arg_pixman_format, TestDBusConsoleRegister *test) { int fd = -1; gint32 handle = g_variant_get_handle(arg_handle); g_autoptr(GError) error = NULL; void *addr = NULL; size_t len = arg_height * arg_stride; g_assert_cmpuint(g_unix_fd_list_get_length(fd_list), ==, 1); fd = g_unix_fd_list_get(fd_list, handle, &error); g_assert_no_error(error); addr = mmap(NULL, len, PROT_READ, MAP_PRIVATE, fd, arg_offset); g_assert_no_errno(addr == MAP_FAILED ? -1 : 0); g_assert_no_errno(munmap(addr, len)); g_main_loop_quit(test->loop); close(fd); return DBUS_METHOD_INVOCATION_HANDLED; } #endif static void test_dbus_console_setup_listener(TestDBusConsoleRegister *test, bool with_map) { g_autoptr(GDBusObjectSkeleton) listener = NULL; g_autoptr(QemuDBusDisplay1ListenerSkeleton) iface = NULL; test->server = g_dbus_object_manager_server_new(DBUS_DISPLAY1_ROOT); listener = g_dbus_object_skeleton_new(DBUS_DISPLAY1_ROOT "/Listener"); iface = QEMU_DBUS_DISPLAY1_LISTENER_SKELETON( qemu_dbus_display1_listener_skeleton_new()); g_object_connect(iface, "signal::handle-scanout", listener_handle_scanout, test, NULL); g_dbus_object_skeleton_add_interface(listener, G_DBUS_INTERFACE_SKELETON(iface)); if (with_map) { #ifdef WIN32 g_test_skip("map test lacking on win32"); return; #else g_autoptr(QemuDBusDisplay1ListenerUnixMapSkeleton) iface_map = QEMU_DBUS_DISPLAY1_LISTENER_UNIX_MAP_SKELETON( qemu_dbus_display1_listener_unix_map_skeleton_new()); g_object_connect(iface_map, "signal::handle-scanout-map", listener_handle_scanout_map, test, NULL); g_dbus_object_skeleton_add_interface(listener, G_DBUS_INTERFACE_SKELETON(iface_map)); g_object_set(iface, "interfaces", (const gchar *[]) { "org.qemu.Display1.Listener.Unix.Map", NULL }, NULL); #endif } g_dbus_object_manager_server_export(test->server, listener); g_dbus_object_manager_server_set_connection(test->server, test->listener_conn); g_dbus_connection_start_message_processing(test->listener_conn); } static void test_dbus_console_registered(GObject *source_object, GAsyncResult *res, gpointer user_data) { TestDBusConsoleRegister *test = user_data; g_autoptr(GError) err = NULL; qemu_dbus_display1_console_call_register_listener_finish( QEMU_DBUS_DISPLAY1_CONSOLE(source_object), #ifndef WIN32 NULL, #endif res, &err); if (g_error_matches(err, G_DBUS_ERROR, G_DBUS_ERROR_UNKNOWN_METHOD)) { g_test_skip("The VM doesn't have a console!"); g_main_loop_quit(test->loop); return; } g_assert_no_error(err); test->listener_conn = g_thread_join(test->thread); test_dbus_console_setup_listener(test, test->with_map); } static gpointer test_dbus_p2p_server_setup_thread(gpointer data) { return test_dbus_p2p_from_fd(GPOINTER_TO_INT(data)); } static void test_dbus_display_console(const void* data) { g_autoptr(GError) err = NULL; g_autoptr(GDBusConnection) conn = NULL; g_autoptr(QemuDBusDisplay1ConsoleProxy) console = NULL; g_autoptr(GMainLoop) loop = NULL; QTestState *qts = NULL; int pair[2]; TestDBusConsoleRegister test = { 0, .with_map = GPOINTER_TO_INT(data) }; #ifdef WIN32 WSAPROTOCOL_INFOW info; g_autoptr(GVariant) listener = NULL; #else g_autoptr(GUnixFDList) fd_list = NULL; int idx; #endif test_setup(&qts, &conn); g_assert_cmpint(qemu_socketpair(AF_UNIX, SOCK_STREAM, 0, pair), ==, 0); #ifndef WIN32 fd_list = g_unix_fd_list_new(); idx = g_unix_fd_list_append(fd_list, pair[1], NULL); #endif console = QEMU_DBUS_DISPLAY1_CONSOLE_PROXY( qemu_dbus_display1_console_proxy_new_sync( conn, G_DBUS_PROXY_FLAGS_NONE, NULL, "/org/qemu/Display1/Console_0", NULL, &err)); g_assert_no_error(err); test.loop = loop = g_main_loop_new(NULL, FALSE); test.thread = g_thread_new(NULL, test_dbus_p2p_server_setup_thread, GINT_TO_POINTER(pair[0])); #ifdef WIN32 if (WSADuplicateSocketW(_get_osfhandle(pair[1]), GetProcessId((HANDLE) qtest_pid(qts)), &info) == SOCKET_ERROR) { g_autofree char *emsg = g_win32_error_message(WSAGetLastError()); g_error("WSADuplicateSocket failed: %s", emsg); } close(pair[1]); listener = g_variant_new_fixed_array(G_VARIANT_TYPE_BYTE, &info, sizeof(info), 1); #endif qemu_dbus_display1_console_call_register_listener( QEMU_DBUS_DISPLAY1_CONSOLE(console), #ifdef WIN32 listener, #else g_variant_new_handle(idx), #endif G_DBUS_CALL_FLAGS_NONE, -1, #ifndef WIN32 fd_list, #endif NULL, test_dbus_console_registered, &test); g_main_loop_run(loop); g_clear_object(&test.server); g_clear_object(&test.listener_conn); qtest_quit(qts); } static void test_dbus_display_keyboard(void) { g_autoptr(GError) err = NULL; g_autoptr(GDBusConnection) conn = NULL; g_autoptr(QemuDBusDisplay1KeyboardProxy) keyboard = NULL; QTestState *qts = NULL; test_setup(&qts, &conn); keyboard = QEMU_DBUS_DISPLAY1_KEYBOARD_PROXY( qemu_dbus_display1_keyboard_proxy_new_sync( conn, G_DBUS_PROXY_FLAGS_NONE, NULL, "/org/qemu/Display1/Console_0", NULL, &err)); g_assert_no_error(err); g_assert_cmpint(qtest_inb(qts, 0x64) & 0x1, ==, 0); g_assert_cmpint(qtest_inb(qts, 0x60), ==, 0); qemu_dbus_display1_keyboard_call_press_sync( QEMU_DBUS_DISPLAY1_KEYBOARD(keyboard), 0x1C, /* qnum enter */ G_DBUS_CALL_FLAGS_NONE, -1, NULL, &err); if (g_error_matches(err, G_DBUS_ERROR, G_DBUS_ERROR_UNKNOWN_METHOD)) { g_test_skip("The VM doesn't have a console!"); qtest_quit(qts); return; } g_assert_no_error(err); /* may be should wait for interrupt? */ g_assert_cmpint(qtest_inb(qts, 0x64) & 0x1, ==, 1); g_assert_cmpint(qtest_inb(qts, 0x60), ==, 0x5A); /* scan code 2 enter */ qemu_dbus_display1_keyboard_call_release_sync( QEMU_DBUS_DISPLAY1_KEYBOARD(keyboard), 0x1C, /* qnum enter */ G_DBUS_CALL_FLAGS_NONE, -1, NULL, &err); g_assert_no_error(err); g_assert_cmpint(qtest_inb(qts, 0x64) & 0x1, ==, 1); g_assert_cmpint(qtest_inb(qts, 0x60), ==, 0xF0); /* scan code 2 release */ g_assert_cmpint(qtest_inb(qts, 0x60), ==, 0x5A); /* scan code 2 enter */ g_assert_cmpint(qemu_dbus_display1_keyboard_get_modifiers( QEMU_DBUS_DISPLAY1_KEYBOARD(keyboard)), ==, 0); qtest_quit(qts); } int main(int argc, char **argv) { g_test_init(&argc, &argv, NULL); qtest_add_func("/dbus-display/vm", test_dbus_display_vm); qtest_add_data_func("/dbus-display/console", GINT_TO_POINTER(false), test_dbus_display_console); qtest_add_data_func("/dbus-display/console/map", GINT_TO_POINTER(true), test_dbus_display_console); qtest_add_func("/dbus-display/keyboard", test_dbus_display_keyboard); return g_test_run(); }