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.
This commit is contained in:
committed by
Daan De Meyer
parent
466662c8bd
commit
4ac6fc336d
@@ -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. */
|
||||
|
||||
Reference in New Issue
Block a user