From: Daan De Meyer Date: Fri, 24 Apr 2026 07:51:53 +0000 (+0000) Subject: test-qmp-client-qemu: exercise add-fd on the first invoke X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=4ac6fc336d63f0b27de0961dc56c2fa75ba5c311;p=thirdparty%2Fsystemd.git test-qmp-client-qemu: exercise add-fd on the first invoke Covers the SCM_RIGHTS fd-passing path end-to-end against a real QEMU: open an eventfd, hand it off via QMP_CLIENT_ARGS_FD() on the very first qmp_client_invoke() against a fresh client, and verify QEMU's add-fd reply carries the expected fdset-id. Complements the mock-based unit test with an authoritative check that QEMU actually consumes the fd from its FIFO receive queue when processing the command — the AF_UNIX kernel behaviour around non-scm skb absorption into following scm-bearing recvs is real-traffic-shaped rather than mock-shaped. --- diff --git a/src/test/test-qmp-client-qemu.c b/src/test/test-qmp-client-qemu.c index df8d3c6e215..813b9c5687a 100644 --- a/src/test/test-qmp-client-qemu.c +++ b/src/test/test-qmp-client-qemu.c @@ -9,6 +9,7 @@ * Skipped automatically if QEMU is not installed. */ #include +#include #include #include "sd-event.h" @@ -254,6 +255,78 @@ TEST(qmp_client_qemu_query_status) { pidref_done(&pidref); } +TEST(qmp_client_qemu_add_fd) { + _cleanup_free_ char *qemu = NULL; + _cleanup_(qmp_client_unrefp) QmpClient *client = NULL; + _cleanup_(sd_event_unrefp) sd_event *event = NULL; + _cleanup_(pidref_done_sigkill_wait) PidRef pidref = PIDREF_NULL; + _cleanup_(sd_json_variant_unrefp) sd_json_variant *args = NULL; + _cleanup_close_ int fd_to_pass = -EBADF; + QmpTestResult t = {}; + _cleanup_close_pair_ int qmp_fds[2] = EBADF_PAIR; + int r; + + if (find_qemu_binary(&qemu) < 0) { + log_tests_skipped("QEMU not found"); + return; + } + + ASSERT_OK(sd_event_new(&event)); + ASSERT_OK_ERRNO(socketpair(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC, 0, qmp_fds)); + + ASSERT_OK(start_qemu(qemu, qmp_fds[1], &pidref)); + qmp_fds[1] = safe_close(qmp_fds[1]); + + r = qmp_client_connect_fd(&client, qmp_fds[0]); + if (r < 0) { + log_tests_skipped_errno(r, "QMP connect failed"); + return; + } + TAKE_FD(qmp_fds[0]); + + ASSERT_OK(qmp_client_attach_event(client, event, SD_EVENT_PRIORITY_NORMAL)); + + fd_to_pass = eventfd(0, EFD_CLOEXEC); + ASSERT_OK_ERRNO(fd_to_pass); + + ASSERT_OK(sd_json_buildo(&args, SD_JSON_BUILD_PAIR_UNSIGNED("fdset-id", 0))); + + /* Pass an fd via SCM_RIGHTS on the very first invoke against a fresh client: + * add-fd lands right after the eagerly-enqueued qmp_capabilities. QEMU processes cap + * first (no fd needed), then add-fd, popping the fd from its FIFO receive queue. */ + r = qmp_client_invoke(client, /* ret_slot= */ NULL, "add-fd", + QMP_CLIENT_ARGS_FD(args, TAKE_FD(fd_to_pass)), + on_test_result, &t); + if (r < 0) { + log_tests_skipped_errno(r, "QMP add-fd invoke failed"); + return; + } + qmp_test_wait(event, &t); + ASSERT_EQ(t.error, 0); + ASSERT_NOT_NULL(t.result); + + sd_json_variant *fdset_id = ASSERT_NOT_NULL(sd_json_variant_by_key(t.result, "fdset-id")); + sd_json_variant *fd_v = ASSERT_NOT_NULL(sd_json_variant_by_key(t.result, "fd")); + ASSERT_EQ(sd_json_variant_unsigned(fdset_id), (uint64_t) 0); + log_info("add-fd returned fdset-id=%" PRIu64 ", fd=%" PRIu64, + sd_json_variant_unsigned(fdset_id), + sd_json_variant_unsigned(fd_v)); + + qmp_test_result_done(&t); + + /* Clean shutdown */ + ASSERT_OK(qmp_client_invoke(client, /* ret_slot= */ NULL, "quit", NULL, on_test_result, &t)); + qmp_test_wait(event, &t); + ASSERT_EQ(t.error, 0); + qmp_test_result_done(&t); + + siginfo_t si = {}; + ASSERT_OK(pidref_wait_for_terminate(&pidref, &si)); + ASSERT_EQ(si.si_code, CLD_EXITED); + ASSERT_EQ(si.si_status, EXIT_SUCCESS); + pidref_done(&pidref); +} + static int intro(void) { /* QEMU dies between our last write and read on the QMP socket — without this we'd * get killed by the SIGPIPE the kernel raises on write-after-EOF. */