]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
test-qmp-client-qemu: exercise add-fd on the first invoke
authorDaan De Meyer <daan@amutable.com>
Fri, 24 Apr 2026 07:51:53 +0000 (07:51 +0000)
committerDaan De Meyer <daan.j.demeyer@gmail.com>
Fri, 24 Apr 2026 12:29:31 +0000 (14:29 +0200)
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.

src/test/test-qmp-client-qemu.c

index df8d3c6e21599f9e13637262eedc456f0a2a60a8..813b9c5687a5087271de656e65169b82ea6fd870 100644 (file)
@@ -9,6 +9,7 @@
  * Skipped automatically if QEMU is not installed. */
 
 #include <signal.h>
+#include <sys/eventfd.h>
 #include <sys/socket.h>
 
 #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. */