tests: server needs zombies and zombie domino effect

2 new tests added to display-test.c:
server_needs_zombies demonstrates that the same reason for zombies in the
client exist in the server: proper demarshalling of fds
zombie_domino_effect demonstrates that messages sent to zombies need to
create new zombies for new_id args

Signed-off-by: Jonathan Leivent <jleivent@comcast.net>
This commit is contained in:
Jonathan Leivent 2024-01-26 15:03:27 -05:00
parent 47de87263c
commit 0db836ebc8
2 changed files with 506 additions and 0 deletions

View file

@ -49,4 +49,54 @@
<arg name="passer" type="object" interface="fd_passer"/>
</request>
</interface>
<interface name="zombie_init" version="1">
<description summary="Set up zombie server and domino-effect tests">
A way to get these tests started.
</description>
<event name="new_obj_event">
<description summary="create a new object on the server"/>
<arg name="the_object" type="new_id" interface="zombie_target"/>
</event>
<request name="new_obj_request">
<description summary="create a new object on the client"/>
<arg name="the_object" type="new_id" interface="zombie_target"/>
</request>
<event name="got_it_event">
<arg name="target" type="object" interface="zombie_target"/>
</event>
<request name="got_it_request">
<arg name="target" type="object" interface="zombie_target"/>
</request>
<request name="try_this_fd_request">
<arg name="fd" type="fd" summary="file descriptor"/>
</request>
<event name="try_this_fd_event">
<arg name="fd" type="fd" summary="file descriptor"/>
</event>
</interface>
<interface name="zombie_target" version="1">
<description summary="object that will or should become a zombie"/>
<request name="have_an_fd_request">
<arg name="fd" type="fd" summary="file descriptor"/>
</request>
<event name="have_an_fd_event">
<arg name="fd" type="fd" summary="file descriptor"/>
</event>
<event name="have_a_new_id_event">
<arg name="the_id" type="new_id" interface="zombie_target"/>
</event>
<request name="destruct_request" type="destuctor"/>
<event name="destruct_event" type="destructor"/>
</interface>
</protocol>

View file

@ -41,6 +41,7 @@
#include <pthread.h>
#include <poll.h>
#include <fcntl.h>
#include "wayland-private.h"
#include "wayland-server.h"
@ -1710,3 +1711,458 @@ TEST(no_source_terminate)
display_run(d);
display_destroy(d);
}
struct zombie_init *zinit_global = NULL;
/* snz: server needs zombies test - demonstrate that the server has a
need for zombies for the same reason as the client: proper
demarshalling of fds in the wire protocol so that they continue to
appear as the args to requests they're supposed to be args to. */
static void
snz_have_an_fd_event(void *data, struct zombie_target *target, int32_t fd)
{
assert(false); /* not used in snz test */
}
static void
snz_have_a_new_id_event(void *data, struct zombie_target *target,
struct zombie_target *the_id)
{
assert(false); /* not used in snz test */
}
bool snz_test_done = false;
static void
snz_destruct_event(void *data, struct zombie_target *target)
{
zombie_target_destroy(target);
snz_test_done = true;
/* send a final fd that is writable to the server to try
writing. If it writes the previous one in the wire (sent
by snz_new_obj_event) by mistake, it will fail because that
one is RO. */
zombie_init_try_this_fd_request(zinit_global, fileno(stderr));
}
static const struct zombie_target_listener snz_target_listener = {
snz_have_an_fd_event,
snz_have_a_new_id_event,
snz_destruct_event,
};
static void
snz_new_obj_event(void *data, struct zombie_init *zinit, struct zombie_target *target)
{
zombie_init_got_it_request(zinit, target);
assert(zombie_target_add_listener(target, &snz_target_listener, NULL)==0);
/* Use the current dir as a read-only fd to send to the
zombie. This happens before we send a writable fd (stderr,
sent by snz_destruct_event), so if this fd is not
demarshalled properly, an attempt to write to the supposed
second fd would fail. */
int rofd = open(".", O_RDONLY | O_CLOEXEC | O_DIRECTORY);
zombie_target_have_an_fd_request(target, rofd);
close(rofd);
}
static void
snz_got_it_event(void *data, struct zombie_init *zinit,
struct zombie_target *target)
{
/*noop for snz test*/
}
static void
snz_try_this_fd_event(void *data, struct zombie_init *zinit, int32_t fd)
{
assert(false); /* not used for snz test */
}
static const struct zombie_init_listener snz_init_listener = {
snz_new_obj_event,
snz_got_it_event,
snz_try_this_fd_event,
};
static void
snz_handle_globals(void *data, struct wl_registry *registry,
uint32_t id, const char *intf, uint32_t ver)
{
if (!strcmp(intf, "zombie_init")) {
zinit_global = wl_registry_bind(registry, id, &zombie_init_interface, 1);
zombie_init_add_listener(zinit_global, &snz_init_listener, NULL);
}
}
static const struct wl_registry_listener snz_registry_listener = {
snz_handle_globals,
NULL,
};
static void
snz_client_loop(void *data)
{
struct client *c = client_connect();
struct wl_registry *registry;
registry = wl_display_get_registry(c->wl_display);
wl_registry_add_listener(registry, &snz_registry_listener, NULL);
while (!snz_test_done)
if (wl_display_roundtrip(c->wl_display) < 0)
assert(false && "snz client loop killed off");
assert(zinit_global);
zombie_init_destroy(zinit_global);
wl_registry_destroy(registry);
client_disconnect_nocheck(c);
}
static void
snz_init_new_obj_request(struct wl_client *client, struct wl_resource *resource,
uint32_t the_object)
{
assert(false); /* not used in snz test */
}
static void
snz_init_got_it_request(struct wl_client *client, struct wl_resource *resource,
struct wl_resource *target)
{
/* this should make target a zombie, if the server had zombies */
zombie_target_send_destruct_event(target);
wl_resource_destroy(target);
}
bool snz_got_try_this_fd_request = false;
static const char finalmsg[] = "This write proves we demarshalled fds properly\n";
static void
snz_try_this_fd_request(struct wl_client *client, struct wl_resource *resource, int32_t fd)
{
/* If writing this fd fails, that's because it's the wrong fd
left on the wire from a previous request that wasn't
demarshalled properly */
assert(write(fd, finalmsg, sizeof finalmsg) == sizeof finalmsg);
fsync(fd);
close(fd);
snz_got_try_this_fd_request = true;
}
static const struct zombie_init_interface snz_init_interface = {
snz_init_new_obj_request,
snz_init_got_it_request,
snz_try_this_fd_request,
};
static void
snz_zombie_init_destroy(struct wl_resource *res)
{
}
static void
snz_target_have_an_fd_request(struct wl_client *client, struct wl_resource *resource,
int32_t fd)
{
/* noop */
}
static void
snz_target_destruct_request(struct wl_client *client, struct wl_resource *resource)
{
assert(false); /* not used in snz test */
}
static const struct zombie_target_interface snz_target_interface = {
snz_target_have_an_fd_request,
snz_target_destruct_request,
};
static void
snz_zombie_target_destroy(struct wl_resource *res)
{
}
static void
bind_snz(struct wl_client *client, void *data,
uint32_t vers, uint32_t id)
{
struct wl_resource *init, *target;
init = wl_resource_create(client, &zombie_init_interface, vers, id);
wl_resource_set_implementation(init, &snz_init_interface, NULL,
snz_zombie_init_destroy);
assert(init);
target = wl_resource_create(client, &zombie_target_interface, 1, 0);
wl_resource_set_implementation(target, &snz_target_interface, NULL,
snz_zombie_target_destroy);
zombie_init_send_new_obj_event(init, target);
}
TEST(server_needs_zombies) /*snz*/
{
struct display *d;
struct wl_global *g;
snz_got_try_this_fd_request = false;
d = display_create();
g = wl_global_create(d->wl_display, &zombie_init_interface,
1, d, bind_snz);
client_create_noarg(d, snz_client_loop);
display_run(d);
assert(snz_got_try_this_fd_request);
wl_global_destroy(g);
display_destroy(d);
}
/*zde: zombie domino effect test - show that properly demarshalling fds
is not the only job that zombies should have - they also need to
make new_id args into zombies, else an fd sent to one of them will
get left in the wire and foul things up. */
static void
zde_have_an_fd_event(void *data, struct zombie_target *target, int32_t fd)
{
/*if we get here, then the zde passes!*/
}
static void
zde_have_a_new_id_event(void *data, struct zombie_target *target,
struct zombie_target *the_id)
{
/*noop - but demarshalling this must zombify the_id to avoid eventual failure */
}
static void
zde_destruct_event(void *data, struct zombie_target *target)
{
assert(false); /*not used by zde test */
}
static const struct zombie_target_listener zde_target_listener = {
zde_have_an_fd_event,
zde_have_a_new_id_event,
zde_destruct_event,
};
static void
zde_new_obj_event(void *data, struct zombie_init *zinit, struct zombie_target *target)
{
assert(false); /* not used in zde test */
}
static void
zde_got_it_event(void *data, struct zombie_init *zinit, struct zombie_target *target)
{
/*noop*/
}
bool zde_test_done = false;
static void
zde_try_this_fd_event(void *data, struct zombie_init *zinit, int32_t fd)
{
zde_test_done = true;
/* if writing this fd fails, that's because it's the wrong fd
left in the wire by improperly demarshalling an event sent
to a new_id that should have become a zombie but didn't */
assert(write(fd, finalmsg, sizeof finalmsg) == sizeof finalmsg);
zombie_init_try_this_fd_request(zinit_global, fd);
fsync(fd);
close(fd);
}
static const struct zombie_init_listener zde_init_listener = {
zde_new_obj_event,
zde_got_it_event,
zde_try_this_fd_event,
};
static void
zde_handle_globals(void *data, struct wl_registry *registry,
uint32_t id, const char *intf, uint32_t ver)
{
if (!strcmp(intf, "zombie_init")) {
zinit_global = wl_registry_bind(registry, id, &zombie_init_interface, 1);
zombie_init_add_listener(zinit_global, &zde_init_listener, NULL);
}
}
static const struct wl_registry_listener zde_registry_listener = {
zde_handle_globals,
NULL,
};
static void
zde_client_loop(void *data)
{
struct client *c = client_connect();
struct wl_registry *registry;
zinit_global = NULL;
registry = wl_display_get_registry(c->wl_display);
wl_registry_add_listener(registry, &zde_registry_listener, NULL);
while (!zinit_global &&
wl_display_roundtrip(c->wl_display) > 0);
struct zombie_target *target = zombie_init_new_obj_request(zinit_global);
zombie_target_add_listener(target, &zde_target_listener, NULL);
/*make target into a zombie */
zombie_target_destruct_request(target);
zombie_target_destroy(target);
while (!zde_test_done)
if (wl_display_roundtrip(c->wl_display) < 0)
assert(false && "zde client loop killed off");
/*once more, with feeling:*/
assert(wl_display_roundtrip(c->wl_display) > 0);
zombie_init_destroy(zinit_global);
wl_registry_destroy(registry);
client_disconnect_nocheck(c);
}
static void
zde_target_have_an_fd_request(struct wl_client *client, struct wl_resource *resource,
int32_t fd)
{
assert(false); /* not used in zde test */
}
static void
zde_zombie_target_destroy(struct wl_resource *res)
{
/*noop*/
}
static void
zde_target_destruct_request(struct wl_client *client, struct wl_resource *resource)
{
wl_resource_destroy(resource);
}
static const struct zombie_target_interface zde_target_interface = {
zde_target_have_an_fd_request,
zde_target_destruct_request,
};
const int num_domino_new_ids = 3;
static void
zde_init_new_obj_request(struct wl_client *client, struct wl_resource *resource,
uint32_t the_object)
{
int i;
struct wl_resource *target =
wl_resource_create(client, &zombie_target_interface, 1, the_object);
struct wl_resource *next_target = NULL;
assert(target);
wl_resource_set_implementation(target, &zde_target_interface, NULL,
zde_zombie_target_destroy);
zombie_init_send_got_it_event(resource, target);
/* the dominos: send a chain of new_id events...*/
for (i = 1; i <= num_domino_new_ids; i++) {
next_target = wl_resource_create(client, &zombie_target_interface, 1, 0);
wl_resource_set_implementation(next_target, &zde_target_interface, NULL,
zde_zombie_target_destroy);
zombie_target_send_have_a_new_id_event(target, next_target);
target = next_target;
}
/* open the current dir read-only and send it as the fd to the
zombie. This happens before we send a writable fd
(stderr), so if this fd is not demarshalled properly, an
attempt to write to the supposed second fd would fail. */
int rofd = open(".", O_RDONLY | O_CLOEXEC | O_DIRECTORY);
zombie_target_send_have_an_fd_event(target, rofd);
close(rofd);
/* one last round-trip to make sure the client survived: */
zombie_init_send_try_this_fd_event(resource, fileno(stderr));
}
static void
zde_init_got_it_request(struct wl_client *client, struct wl_resource *resource,
struct wl_resource *target)
{
assert(false); /* not used in zde test */
}
bool zde_got_try_this_fd_request = false;
static void
zde_try_this_fd_request(struct wl_client *client, struct wl_resource *resource,
int32_t fd)
{
/* this is redundant with zde_try_this_fd_event, but why not try it as well */
assert(write(fd, finalmsg, sizeof finalmsg) == sizeof finalmsg);
fsync(fd);
close(fd);
zde_got_try_this_fd_request = true;
}
static const struct zombie_init_interface zde_init_interface = {
zde_init_new_obj_request,
zde_init_got_it_request,
zde_try_this_fd_request,
};
static void
zde_zombie_init_destroy(struct wl_resource *res)
{
}
static void
bind_zde(struct wl_client *client, void *data,
uint32_t vers, uint32_t id)
{
struct wl_resource *init;
init = wl_resource_create(client, &zombie_init_interface, vers, id);
wl_resource_set_implementation(init, &zde_init_interface, NULL,
zde_zombie_init_destroy);
assert(init);
}
TEST(zombie_domino_effect) /*zde*/
{
struct display *d;
struct wl_global *g;
zde_got_try_this_fd_request = false;
d = display_create();
g = wl_global_create(d->wl_display, &zombie_init_interface,
1, d, bind_zde);
client_create_noarg(d, zde_client_loop);
display_run(d);
assert(zde_got_try_this_fd_request);
wl_global_destroy(g);
display_destroy(d);
}