diff --git a/protocol/tests.xml b/protocol/tests.xml
index 22d80a10..8ca098f8 100644
--- a/protocol/tests.xml
+++ b/protocol/tests.xml
@@ -49,4 +49,54 @@
+
+
+
+ A way to get these tests started.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tests/display-test.c b/tests/display-test.c
index bcb3267f..8b9f1906 100644
--- a/tests/display-test.c
+++ b/tests/display-test.c
@@ -41,6 +41,7 @@
#include
#include
+#include
#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);
+}