[PATCH 2/2] tools: add wayland-dump

Marek Chalupa mchqwerty at gmail.com
Sun Jul 20 07:42:47 PDT 2014


This tool creates a socket, which the client will connect on and
can do 'anything' with the data flowing between client and display.

It's easily extensible by filters (function that is called for each
data in the socket)

Signed-off-by: Marek Chalupa <mchqwerty at gmail.com>
---
 Makefile.am  |   7 +
 tools/dump.c | 475 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 482 insertions(+)
 create mode 100644 tools/dump.c

diff --git a/Makefile.am b/Makefile.am
index c15d8b8..05caba2 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -61,6 +61,8 @@ nodist_libwayland_client_la_SOURCES =		\
 
 pkgconfig_DATA += src/wayland-client.pc src/wayland-server.pc
 
+bin_PROGRAMS =
+
 if ENABLE_SCANNER
 wayland_scanner = $(top_builddir)/wayland-scanner
 bin_PROGRAMS = wayland-scanner
@@ -72,6 +74,11 @@ else
 wayland_scanner = wayland-scanner
 endif
 
+bin_PROGRAMS += wayland-dump
+wayland_dump_SOURCES = tools/dump.c
+wayland_dump_LDADD = $(FFI_LIBS) libwayland-util.la
+wayland_dump_CFLAGS = $(FFI_CFLAGS) $(GCC_CFLAGS)
+
 protocol/%-protocol.c : $(top_srcdir)/protocol/%.xml
 	$(AM_V_GEN)$(MKDIR_P) $(dir $@) && $(wayland_scanner) code < $< > $@
 
diff --git a/tools/dump.c b/tools/dump.c
new file mode 100644
index 0000000..a0d1170
--- /dev/null
+++ b/tools/dump.c
@@ -0,0 +1,475 @@
+/*
+ * Copyright (c) 2014 Marek Chalupa
+ *
+ * Permission to use, copy, modify, distribute, and sell this software and its
+ * documentation for any purpose is hereby granted without fee, provided that
+ * the above copyright notice appear in all copies and that both that copyright
+ * notice and this permission notice appear in supporting documentation, and
+ * that the name of the copyright holders not be used in advertising or
+ * publicity pertaining to distribution of the software without specific,
+ * written prior permission.  The copyright holders make no representations
+ * about the suitability of this software for any purpose.  It is provided "as
+ * is" without express or implied warranty.
+ *
+ * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
+ * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO
+ * EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY SPECIAL, INDIRECT OR
+ * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
+ * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
+ * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
+ * OF THIS SOFTWARE.
+ */
+
+#define _GNU_SOURCE
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <errno.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <assert.h>
+#include <fcntl.h>
+#include <sys/epoll.h>
+
+#include "wayland-util.h"
+#include "wayland-os.h"
+#include "wayland-client.h"
+#include "wayland-private.h"
+
+struct message {
+	void *raw_data;
+	size_t size;
+	enum {
+		SERVER,
+		CLIENT
+	} from;
+};
+
+enum {
+	FILTER_NEXT, /* process next filter */
+	FILTER_STOP  /* stop processing filters */
+};
+
+struct filter {
+	int (*process)(void *user_data, struct message *message);
+	void *user_data;
+	struct wl_list link;
+};
+
+struct data {
+	struct {
+		int fd;
+		struct wl_connection *connection;
+
+		uint32_t serial;
+	} server;
+
+	struct {
+		int fd;
+		struct wl_connection *connection;
+		pid_t pid;
+
+		uint32_t serial;
+	} client;
+
+	int epoll_fd;
+
+	struct wl_list filters;
+};
+
+/* copied out from wayland-client.c */
+/* renamed from connect_to_socket -> connect_to_wayland_socket */
+static int
+connect_to_wayland_socket(const char *name)
+{
+	struct sockaddr_un addr;
+	socklen_t size;
+	const char *runtime_dir;
+	int name_size, fd;
+
+	runtime_dir = getenv("XDG_RUNTIME_DIR");
+	if (!runtime_dir) {
+		wl_log("error: XDG_RUNTIME_DIR not set in the environment.\n");
+		/* to prevent programs reporting
+		 * "failed to create display: Success" */
+		errno = ENOENT;
+		return -1;
+	}
+
+	if (name == NULL)
+		name = getenv("WAYLAND_DISPLAY");
+	if (name == NULL)
+		name = "wayland-0";
+
+	fd = wl_os_socket_cloexec(PF_LOCAL, SOCK_STREAM, 0);
+	if (fd < 0)
+		return -1;
+
+	memset(&addr, 0, sizeof addr);
+	addr.sun_family = AF_LOCAL;
+	name_size =
+		snprintf(addr.sun_path, sizeof addr.sun_path,
+			 "%s/%s", runtime_dir, name) + 1;
+
+	assert(name_size > 0);
+	if (name_size > (int)sizeof addr.sun_path) {
+		wl_log("error: socket path \"%s/%s\" plus null terminator"
+		       " exceeds 108 bytes\n", runtime_dir, name);
+		close(fd);
+		/* to prevent programs reporting
+		 * "failed to add socket: Success" */
+		errno = ENAMETOOLONG;
+		return -1;
+	};
+
+	size = offsetof(struct sockaddr_un, sun_path) + name_size;
+
+	if (connect(fd, (struct sockaddr *) &addr, size) < 0) {
+		close(fd);
+		return -1;
+	}
+
+	return fd;
+}
+
+static pid_t
+spawn_client(const char *path, struct data *data)
+{
+	struct epoll_event ev;
+	int sock[2];
+	char sockstr[8];
+
+	if (socketpair(AF_UNIX, SOCK_STREAM, 0, sock) != 0) {
+		perror("socketpair");
+		return -1;
+	}
+
+	data->client.fd = sock[0];
+
+	data->client.connection = wl_connection_create(data->client.fd);
+	if (!data->client.connection) {
+		perror("Failed creating wl_connection (client)");
+		goto err;
+	}
+
+	ev.events = EPOLLIN;
+	ev.data.ptr = data->client.connection;
+	if (epoll_ctl(data->epoll_fd, EPOLL_CTL_ADD,
+		      data->client.fd, &ev) == -1) {
+		perror("Failed adding client's socket to epoll");
+		goto err;
+	}
+
+	if (snprintf(sockstr, sizeof sockstr, "%d",
+		     sock[1]) == sizeof sockstr) {
+		perror("Socket number too high, hack the code");
+		goto err;
+	}
+
+	if (setenv("WAYLAND_SOCKET", sockstr, 1) != 0) {
+		perror("Setting WAYLAND_SOCKET failed");
+		goto err;
+	}
+
+	data->client.pid = fork();
+
+	if (data->client.pid == -1) {
+		perror("fork");
+		goto err;
+	}
+
+	/* exec client in child */
+	if (data->client.pid == 0) {
+		close(sock[0]);
+
+		/* XXX add arguments */
+		execlp(path, path, NULL);
+
+		perror("Exec failed");
+		abort();
+	}
+
+	close(sock[1]);
+
+	return data->client.pid;
+
+err:
+		close(sock[0]);
+		close(sock[1]);
+
+		return -1;
+}
+
+static int
+init_wayland_socket(struct data *data)
+{
+	struct epoll_event ev;
+
+	data->server.fd = connect_to_wayland_socket(NULL);
+	if (data->server.fd == -1) {
+		perror("Failed opening wayland socket");
+		return -1;
+	}
+
+	data->server.connection = wl_connection_create(data->server.fd);
+	if (!data->server.connection) {
+		perror("Failed creating wl_connection");
+		goto err;
+	}
+
+	ev.events = EPOLLIN;
+	ev.data.ptr = data->server.connection;
+	if (epoll_ctl(data->epoll_fd, EPOLL_CTL_ADD,
+		      data->server.fd, &ev) == -1) {
+		perror("Failed adding wayland socket to epoll");
+		goto err;
+	}
+
+	return 0;
+
+err:
+	close(data->server.fd);
+	return -1;
+}
+
+static int
+process_data(struct data *data, struct wl_connection *connection, int len)
+{
+	struct filter *filter;
+	char buffer[4096];
+	struct message message;
+	struct wl_connection *write_conn;
+
+	wl_connection_copy(connection, buffer, len);
+	wl_connection_consume(connection, len);
+
+	if (connection == data->server.connection) {
+		message.from = SERVER;
+		write_conn = data->client.connection;
+	} else {
+		message.from = CLIENT;
+		write_conn = data->server.connection;
+	}
+
+	message.raw_data = buffer;
+	message.size = len;
+
+	/* process filters */
+	wl_list_for_each(filter, &data->filters, link) {
+		if (filter->process(filter->user_data, &message) == FILTER_STOP)
+			break;
+	}
+
+	/* resend the data */
+	wl_connection_copy_fds(connection, write_conn);
+	wl_connection_write(write_conn, message.raw_data, message.size);
+	wl_connection_flush(write_conn);
+
+	return 0;
+}
+
+static int
+run(struct data *data)
+{
+	struct epoll_event ev;
+	struct wl_connection *conn;
+	int n, len;
+
+	while (1) {
+		n = epoll_wait(data->epoll_fd, &ev, 1, -1);
+
+		if (n < 0) {
+			perror("epoll_wait");
+			return -1;
+		}
+
+		if (ev.events & EPOLLERR) {
+			fprintf(stderr, "epoll event error");
+			return -1;
+		} else if (ev.events & EPOLLHUP) {
+			fprintf(stderr, "epoll hup\n");
+			return 0;
+		}
+
+		conn = ev.data.ptr;
+
+		len = wl_connection_read(conn);
+		if (len < 0 && errno != EAGAIN) {
+			perror("wl_connection_read");
+			return -1;
+		} else if (len < 0 && errno == EAGAIN)
+			continue;
+
+		if(process_data(data, conn, len) < 0)
+			return -1;
+	}
+
+	return 0;
+}
+
+static void
+free_data(struct data *data)
+{
+	close(data->epoll_fd);
+
+	if (data->server.connection)
+		wl_connection_destroy(data->server.connection);
+	if (data->client.connection)
+		wl_connection_destroy(data->client.connection);
+}
+
+/*
+ * ---- FILTERS ----
+ */
+
+static int
+process_default(void *user_data, struct message *message)
+{
+	struct data *data = user_data;
+
+	if (message->from == SERVER) {
+		++data->server.serial;
+	} else {
+		++data->client.serial;
+	}
+
+		return FILTER_NEXT;
+}
+
+static int
+print_one_event(void *data, unsigned int len)
+{
+	uint32_t id, size, opcode, i;
+	uint32_t *p = data;
+	static unsigned int serial = 0;
+
+	id = *p++;
+	size = opcode = *p;
+
+	opcode &= 0xffff;
+	size = size >> 16;
+
+	if (size > len)
+		/* return how many data we need */
+		return len - size;
+
+	/* header */
+	printf("(%d) Obj id %u, opcode %u, size %u\n", ++serial, id, opcode, size);
+
+	for (i = 0; i < size; ++i)
+		printf("%02x ", ((unsigned char *) data)[i]);
+
+	printf("\n");
+
+	return size;
+}
+
+static int
+process_events(void *user_data, struct message *message)
+{
+	static unsigned int from;
+	static unsigned int rest = 0;
+	static struct wl_array tmp;
+	static int initialized = 0;
+
+	int len;
+	void *p;
+
+	if (!initialized) {
+		initialized = 1;
+		wl_array_init(&tmp);
+	}
+
+	if (rest && from == message->from) {
+		if (rest > message->size) {
+			p = wl_array_add(&tmp, message->size);
+			memcpy(message->raw_data, p, message->size);
+			rest -= message->size;
+			return FILTER_STOP;
+		} else {
+			p = wl_array_add(&tmp, message->size - rest);
+			memcpy(message->raw_data, p, message->size - rest);
+
+			printf("%s ", message->from == SERVER ? "--> " : "<--");
+			print_one_event(tmp.data, tmp.size);
+
+			rest = 0;
+			tmp.size = 0;
+		}
+	} else {
+		uint32_t off = 0;
+		do {
+			printf("%s ", message->from == SERVER ? "--> " : "<--");
+			len = print_one_event(message->raw_data + off,
+					      message->size - off);
+			if (len > 0)
+				off += len;
+			else
+				break;
+		} while (message->size - off > 0);
+
+		if (len < 0) {
+			rest = -len;
+			/* so that we won't mess client and server messages */
+			from = message->from;
+			p = wl_array_add(&tmp, message->size - off);
+			/* store the part of message that we have */
+			memcpy(message->raw_data + off, p, message->size - off);
+		}
+	}
+
+	return FILTER_NEXT;
+}
+
+static void
+add_filters(struct data *data)
+{
+	static struct filter default_filter;
+	static struct filter events_filter;
+
+	wl_list_init(&data->filters);
+
+	/* add default filter (statistics atm) */
+	default_filter.process = process_default;
+	default_filter.user_data = data;
+	wl_list_init(&default_filter.link);
+	wl_list_insert(data->filters.next, &default_filter.link);
+
+	/* add events filter */
+	events_filter.process = process_events;
+	events_filter.user_data = data;
+	wl_list_init(&events_filter.link);
+	wl_list_insert(data->filters.next, &events_filter.link);
+}
+
+int main(int argc, char *argv[])
+{
+	struct data data;
+	memset(&data, 0, sizeof data);
+
+	add_filters(&data);
+
+	data.epoll_fd = epoll_create1(0);
+	if (data.epoll_fd == -1) {
+		perror("epoll_create failed");
+		return EXIT_FAILURE;
+	}
+
+	if (init_wayland_socket(&data) < 0)
+		goto err;
+
+	if (spawn_client(argv[1], &data) < 0)
+		goto err;
+
+	run(&data);
+
+	free_data(&data);
+
+	return EXIT_SUCCESS;
+err:
+	free_data(&data);
+
+	return EXIT_FAILURE;
+}
-- 
2.0.1



More information about the wayland-devel mailing list