Signed-off-by: Boyan Ding <stu_...@126.com> --- .gitignore | 1 + Makefile.am | 10 + configure.ac | 7 + src/tracer.c | 689 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 707 insertions(+) create mode 100644 src/tracer.c
diff --git a/.gitignore b/.gitignore index c146bac..510b7ae 100644 --- a/.gitignore +++ b/.gitignore @@ -54,4 +54,5 @@ sanity-test signal-test socket-test wayland-scanner +wayland-tracer protocol/*.[ch] diff --git a/Makefile.am b/Makefile.am index fee19ab..61827dd 100644 --- a/Makefile.am +++ b/Makefile.am @@ -70,6 +70,16 @@ $(BUILT_SOURCES) : wayland-scanner pkgconfig_DATA += src/wayland-scanner.pc else wayland_scanner = wayland-scanner +bin_PROGRAMS = +endif + +if ENABLE_TRACER +wayland_tracer = $(top_builddir)/wayland-tracer +bin_PROGRAMS += wayland-tracer +wayland_tracer_SOURCES = src/tracer.c +wayland_tracer_LDADD = libwayland-util.la $(FFI_LIBS) -lrt +else +wayland_tracer = wayland-tracer endif protocol/%-protocol.c : $(top_srcdir)/protocol/%.xml diff --git a/configure.ac b/configure.ac index e16c5b5..b3e81a7 100644 --- a/configure.ac +++ b/configure.ac @@ -64,7 +64,14 @@ AC_ARG_ENABLE([documentation], [], [enable_documentation=yes]) +AC_ARG_ENABLE([tracer], + [AC_HELP_STRING([--disable-tracer], + [Disable compilation of wayland-tracer])], + [], + [enable_tracer=yes]) + AM_CONDITIONAL(ENABLE_SCANNER, test "x$enable_scanner" = xyes) +AM_CONDITIONAL(ENABLE_TRACER, test "x$enable_tracer" = xyes) AC_ARG_WITH(icondir, [ --with-icondir=<dir> Look for cursor icons here], [ ICONDIR=$withval], diff --git a/src/tracer.c b/src/tracer.c new file mode 100644 index 0000000..9adb21f --- /dev/null +++ b/src/tracer.c @@ -0,0 +1,689 @@ +/* + * Copyright © 2014 Boyan Ding + * + * 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. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <stddef.h> +#include <string.h> +#include <unistd.h> +#include <fcntl.h> +#include <sys/epoll.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/stat.h> +#include <sys/file.h> +#include <sys/un.h> +#include <stdint.h> +#include <errno.h> +#include <assert.h> + +#include "wayland-os.h" +#include "wayland-private.h" +#include "wayland-util.h" + +#define TRACER_SERVER_SIDE 0 +#define TRACER_CLIENT_SIDE 1 + +#define TRACER_MODE_SINGLE 0 +#define TRACER_MODE_SERVER 1 + +#ifndef UNIX_PATH_MAX +#define UNIX_PATH_MAX 108 +#endif + +#define LOCK_SUFFIX ".lock" +#define LOCK_SUFFIXLEN 5 +struct tracer; +struct tracer_instance; + +struct tracer_connection { + struct wl_connection *wl_conn; + struct tracer_connection *peer; + struct tracer_instance *instance; + int side; +}; + +struct tracer_instance { + int id; + struct tracer_connection *client_conn; + struct tracer_connection *server_conn; + struct tracer *tracer; + struct wl_list link; +}; + +/* A simple copy of wl_socket in wayland-server.c */ +struct tracer_socket { + int fd; + int fd_lock; + struct sockaddr_un addr; + char lock_addr[UNIX_PATH_MAX + LOCK_SUFFIXLEN]; +}; + +struct tracer { + struct tracer_socket *socket; + int32_t epollfd; + int next_id; + struct wl_list instance_list; +}; + +struct tracer_options { + int mode; + char **spawn_args; + char *socket; +}; + +static int +tracer_dump_bin(struct tracer_connection *connection) +{ + int i, len, fdlen, fd; + char buf[4096]; + struct wl_connection *wl_conn= connection->wl_conn; + struct tracer_connection *peer = connection->peer; + struct tracer_instance *instance = connection->instance; + + len = wl_buffer_size(&wl_conn->in); + if (len == 0) + return 0; + + wl_connection_copy(wl_conn, buf, len); + + printf("%d: %s Data dumped: %d bytes:\n", instance->id, + connection->side == TRACER_SERVER_SIDE ? "=>" : "<=", len); + for (i = 0; i < len; i++) { + printf("%02x ", (unsigned char)buf[i]); + } + printf("\n"); + wl_connection_consume(wl_conn, len); + wl_connection_write(peer->wl_conn, buf, len); + + fdlen = wl_buffer_size(&wl_conn->fds_in); + + wl_buffer_copy(&wl_conn->fds_in, buf, fdlen); + fdlen /= sizeof(int32_t); + + if (fdlen != 0) + printf("%d Fds in control data:", fdlen); + + for (i = 0; i < fdlen; i++) { + fd = ((int *) buf)[i]; + printf("%d ", fd); + wl_connection_put_fd(peer->wl_conn, fd); + } + printf("\n"); + + wl_conn->fds_in.tail += fdlen * sizeof(int32_t); + wl_connection_flush(peer->wl_conn); + + return len; +} + +/* The following two functions are taken from wayland-client.c*/ +static int +tracer_connect_to_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) { + fprintf(stderr, "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) { + fprintf(stderr, "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 int +tracer_connect_server(const char *name) +{ + char *connection, *end; + int flags, fd; + + connection = getenv("WAYLAND_SOCKET"); + if (connection) { + fd = strtol(connection, &end, 0); + if (*end != '\0') + return -1; + + flags = fcntl(fd, F_GETFD); + if (flags != -1) + fcntl(fd, F_SETFD, flags | FD_CLOEXEC); + unsetenv("WAYLAND_SOCKET"); + } else + fd = tracer_connect_to_socket(name); + + return fd; +} + +static struct tracer_connection* +tracer_connection_create(int fd, int side) +{ + struct tracer_connection *connection; + + connection = malloc(sizeof *connection); + if (connection == NULL) { + errno = ENOMEM; + return NULL; + } + + connection->wl_conn = wl_connection_create(fd); + if (connection->wl_conn == NULL) + return NULL; + + connection->side = side; + + return connection; +} + +static void +tracer_connection_destroy(struct tracer_connection *connection) +{ + struct wl_connection *wl_conn = connection->wl_conn; + struct tracer *tracer = connection->instance->tracer; + + epoll_ctl(tracer->epollfd, EPOLL_CTL_DEL, wl_conn->fd, NULL); + wl_connection_destroy(connection->wl_conn); + free(connection); +} + +static int +tracer_epoll_add_fd(struct tracer *tracer, int fd, void *userdata) +{ + struct epoll_event ev; + + ev.events = EPOLLIN; + ev.data.ptr = userdata; + + return epoll_ctl(tracer->epollfd, EPOLL_CTL_ADD, fd, &ev); +} + +static int +tracer_instance_create(struct tracer *tracer, int clientfd) +{ + int serverfd; + struct tracer_instance *instance; + + instance = malloc(sizeof *instance); + if (instance == NULL) { + errno = ENOMEM; + return -1; + } + + if (tracer->socket == NULL) + serverfd = tracer_connect_server(NULL); + else + serverfd = tracer_connect_to_socket(NULL); + + instance->server_conn = tracer_connection_create(serverfd, + TRACER_SERVER_SIDE); + if (instance->server_conn == NULL) + goto err_conn; + + instance->client_conn = tracer_connection_create(clientfd, + TRACER_CLIENT_SIDE); + if (instance->client_conn == NULL) + goto err_conn; + + instance->server_conn->peer = instance->client_conn; + instance->client_conn->peer = instance->server_conn; + + instance->server_conn->instance = instance; + instance->client_conn->instance = instance; + + tracer_epoll_add_fd(tracer, serverfd, instance->server_conn); + tracer_epoll_add_fd(tracer, clientfd, instance->client_conn); + + instance->tracer = tracer; + instance->id = tracer->next_id; + tracer->next_id++; + + wl_list_insert(&tracer->instance_list, &instance->link); + return 0; + +err_conn: + free(instance); + return -1; +} + +static void +tracer_instance_destroy(struct tracer_instance *instance) +{ + tracer_connection_destroy(instance->server_conn); + tracer_connection_destroy(instance->client_conn); + + wl_list_remove(&instance->link); + + free(instance); +} + +static void +tracer_handle_hup(struct tracer_connection *connection) +{ + tracer_instance_destroy(connection->instance); +} + +static void +tracer_handle_data(struct tracer_connection *connection) +{ + wl_connection_read(connection->wl_conn); + + while(tracer_dump_bin(connection) > 0); +} + +static void +tracer_handle_client(struct tracer *tracer) +{ + struct tracer_socket *s = tracer->socket; + struct sockaddr_un name; + socklen_t length; + int clientfd; + + length = sizeof name; + clientfd = wl_os_accept_cloexec(s->fd, (struct sockaddr *) &name, + &length); + + if (clientfd < 0) + fprintf(stderr, "failed to accept(): %m\n"); + else if (tracer_instance_create(tracer, clientfd) < 0) { + fprintf(stderr, "failed to create instance\n"); + close(clientfd); + } +} + +static int +tracer_run(struct tracer *tracer) +{ + struct epoll_event ev; + struct tracer_connection *connection; + int nfds; + + for (;;) { + nfds = epoll_wait(tracer->epollfd, &ev, 1, -1); + + if (nfds < 0) { + fprintf(stderr, "Failed to poll: %m\n"); + return -1; + } + + connection = (struct tracer_connection *) ev.data.ptr; + + if (ev.events & EPOLLIN) { + if (connection == NULL) + tracer_handle_client(tracer); + else + tracer_handle_data(connection); + } + + if (ev.events & EPOLLHUP) { + tracer_handle_hup(connection); + + if (tracer->socket == NULL) { + fprintf(stderr, "Child hups, exiting\n"); + break; + } + } + } + + return 0; +} + +/* Following two functions adapted from wayland-server.c */ +static int +get_socket_lock(struct tracer_socket *socket) +{ + struct stat socket_stat; + int fd_lock; + + snprintf(socket->lock_addr, sizeof socket->lock_addr, + "%s%s", socket->addr.sun_path, LOCK_SUFFIX); + + fd_lock = open(socket->lock_addr, O_CREAT | O_CLOEXEC, + (S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP)); + + if (fd_lock < 0) { + fprintf(stderr, "unable to open lockfile %s check permissions\n", + socket->lock_addr); + return -1; + } + + if (flock(fd_lock, LOCK_EX | LOCK_NB) < 0) { + fprintf(stderr, "unable to lock lockfile %s, maybe another compositor is running\n", + socket->lock_addr); + close(fd_lock); + return -1; + } + + if (stat(socket->addr.sun_path, &socket_stat) < 0 ) { + if (errno != ENOENT) { + fprintf(stderr, "did not manage to stat file %s\n", + socket->addr.sun_path); + close(fd_lock); + return -1; + } + } else if (socket_stat.st_mode & S_IWUSR || + socket_stat.st_mode & S_IWGRP) { + unlink(socket->addr.sun_path); + } + + return fd_lock; +} + +static int +tracer_create_socket(struct tracer *tracer, const char *name) +{ + struct tracer_socket *s; + socklen_t size; + int name_size; + const char *runtime_dir; + + s = malloc(sizeof *s); + if (s == NULL) + return -1; + + 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 add socket: Success" */ + errno = ENOENT; + return -1; + } + + s->fd = wl_os_socket_cloexec(PF_LOCAL, SOCK_STREAM, 0); + if (s->fd < 0) + return -1; + + if (name == NULL) + name = getenv("WAYLAND_DISPLAY"); + if (name == NULL) + name = "wayland-0"; + + memset(&s->addr, 0, sizeof s->addr); + s->addr.sun_family = AF_LOCAL; + name_size = snprintf(s->addr.sun_path, sizeof s->addr.sun_path, + "%s/%s", runtime_dir, name) + 1; + + assert(name_size > 0); + if (name_size > (int)sizeof s->addr.sun_path) { + fprintf(stderr, "error: socket path \"%s/%s\" plus null " + "terminator exceeds 108 bytes\n", runtime_dir, name); + close(s->fd); + free(s); + /* to prevent programs reporting + * "failed to add socket: Success" */ + errno = ENAMETOOLONG; + return -1; + }; + + s->fd_lock = get_socket_lock(s); + if (s->fd_lock < 0) { + close(s->fd); + free(s); + return -1; + } + + size = offsetof (struct sockaddr_un, sun_path) + name_size; + if (bind(s->fd, (struct sockaddr *) &s->addr, size) < 0) { + fprintf(stderr, "bind() failed with error: %m\n"); + close(s->fd); + unlink(s->lock_addr); + close(s->fd_lock); + free(s); + return -1; + } + + if (listen(s->fd, 1) < 0) { + fprintf(stderr, "listen() failed with error: %m\n"); + unlink(s->addr.sun_path); + close(s->fd); + unlink(s->lock_addr); + close(s->fd_lock); + free(s); + return -1; + } + + tracer_epoll_add_fd(tracer, s->fd, NULL); + tracer->socket = s; + + return 0; +} + +static void +usage(void) +{ + fprintf(stderr, "wayland-tracer: a wayland protocol dumper\n" + "Usage:\twayland-tracer -- file ...\n" + "\twayland-tracer -S NAME\n\n" + "Options:\n\n" + " -S NAME\t\tMake wayland-tracer run under server mode\n" + "\t\t\tand make the name of server socket NAME (such as\n" + "\t\t\twayland-0)\n" + " -h\t\t\tThis help message\n\n"); +} + +static struct tracer_options* +tracer_parse_args(int argc, char *argv[]) +{ + int i; + struct tracer_options *options; + + options = malloc(sizeof *options); + if (options == NULL) { + errno = ENOMEM; + return NULL; + } + + options->spawn_args = NULL; + options->mode = TRACER_MODE_SINGLE; + + if (argc == 1) { + usage(); + exit(EXIT_SUCCESS); + } + + for (i = 1; i < argc; i++) { + if (!strcmp(argv[i], "-h")) { + usage(); + exit(EXIT_SUCCESS); + } else if (!strcmp(argv[i], "-S")) { + i++; + if (i == argc) { + fprintf(stderr, "Socket not specified\n"); + exit(EXIT_FAILURE); + } + options->mode = TRACER_MODE_SERVER; + options->socket = argv[i]; + } else if (!strcmp(argv[i], "--")) { + i++; + if (i == argc) { + fprintf(stderr, "Program not specified\n"); + exit(EXIT_FAILURE); + } + options->spawn_args = &argv[i]; + break; + } else { + fprintf(stderr, "Unknown argument '%s'\n", argv[i]); + usage(); + exit(EXIT_FAILURE); + } + } + + if (options->mode == TRACER_MODE_SINGLE && + options->spawn_args == NULL) { + fprintf(stderr, "No client specified in single mode\n"); + exit(EXIT_FAILURE); + } + return options; +} + +static struct tracer* +tracer_create(struct tracer_options *options) +{ + int sock_vec[2], ret; + pid_t pid; + struct tracer *tracer; + char sockfdstr[12]; + + tracer = malloc(sizeof *tracer); + if (tracer == NULL) { + errno = ENOMEM; + return NULL; + } + + wl_list_init(&tracer->instance_list); + tracer->next_id = 0; + // Spawn child if we're in single mode + if (options->mode == TRACER_MODE_SINGLE) { + tracer->socket = NULL; + ret = socketpair(PF_LOCAL, SOCK_STREAM, 0, sock_vec); + if (ret != 0) { + fprintf(stderr, "Failed to create socketpair: %m\n"); + goto err_socketpair; + } + + pid = fork(); + if (pid < 0) { + fprintf(stderr, "Failed to fork: %m\n"); + goto err_fork; + } + + if (pid == 0) { + close(sock_vec[0]); + sprintf(sockfdstr, "%d", sock_vec[1]); + setenv("WAYLAND_SOCKET", sockfdstr, 1); + + execvp(options->spawn_args[0], + options->spawn_args); + + // Only when exec fails can we reach here + close(sock_vec[1]); + exit(EXIT_FAILURE); + } + } + + tracer->epollfd = epoll_create1(0); + if (tracer->epollfd < 0) { + fprintf(stderr, "Failed to create epollfd: %m\n"); + goto err_epoll_create; + } + + if (options->mode == TRACER_MODE_SINGLE) { + close(sock_vec[1]); + ret = tracer_instance_create(tracer, sock_vec[0]); + if (ret < 0) { + fprintf(stderr, "Failed to init instance\n"); + goto err_instance; + } + } else { + ret = tracer_create_socket(tracer, "wayland-1"); + if (ret < 0) + exit(EXIT_FAILURE); + } + + return tracer; + +err_socketpair: + free(tracer); + return NULL; + +err_fork: + close(sock_vec[0]); + close(sock_vec[1]); + free(tracer); + return NULL; + +err_epoll_create: + if (options->mode == TRACER_MODE_SINGLE) { + close(sock_vec[0]); + close(sock_vec[1]); + } + free(tracer); + return NULL; + +err_instance: + close(tracer->epollfd); + close(sock_vec[0]); + free(tracer); + return NULL; +} + +int +main(int argc, char *argv[]) +{ + int ret; + struct tracer *tracer; + struct tracer_options *options; + + options = tracer_parse_args(argc, argv); + if (options == NULL) { + fprintf(stderr, "Failed to parse command line: %m\n"); + exit(EXIT_FAILURE); + } + + tracer = tracer_create(options); + + ret = tracer_run(tracer); + + if (ret == 0) + exit(EXIT_SUCCESS); + else + exit(EXIT_FAILURE); +} -- 2.0.1 _______________________________________________ wayland-devel mailing list wayland-devel@lists.freedesktop.org http://lists.freedesktop.org/mailman/listinfo/wayland-devel