/************************************************************************** * * Copyright (C) 2015 Red Hat Inc. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. * **************************************************************************/ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #include #include #include #include #include #include #include #include #include #include #include "util.h" #include "util/u_double_list.h" #include "util/u_math.h" #include "util/u_memory.h" #include "vtest.h" #include "vtest_protocol.h" #include "virglrenderer.h" #ifdef HAVE_SYS_SELECT_H #include #endif enum vtest_client_error { VTEST_CLIENT_ERROR_INPUT_READ = 2, /* for backward compatibility */ VTEST_CLIENT_ERROR_CONTEXT_MISSING, VTEST_CLIENT_ERROR_CONTEXT_FAILED, VTEST_CLIENT_ERROR_COMMAND_ID, VTEST_CLIENT_ERROR_COMMAND_UNEXPECTED, VTEST_CLIENT_ERROR_COMMAND_DISPATCH, }; struct vtest_client { int in_fd; int out_fd; struct vtest_input input; struct list_head head; bool in_fd_ready; struct vtest_context *context; int context_poll_fd; bool context_need_poll; }; struct vtest_server { const char *socket_name; int socket; const char *read_file; const char *render_device; bool main_server; bool do_fork; bool loop; bool multi_clients; bool use_glx; bool use_egl_surfaceless; bool use_gles; bool venus; bool render_server; int ctx_flags; struct list_head new_clients; struct list_head active_clients; struct list_head inactive_clients; }; struct vtest_server server = { .socket_name = VTEST_DEFAULT_SOCKET_NAME, .socket = -1, .read_file = NULL, .render_device = 0, .main_server = true, .do_fork = true, .loop = true, .multi_clients = false, .ctx_flags = 0, }; static void vtest_server_getenv(void); static void vtest_server_parse_args(int argc, char **argv); static void vtest_server_set_signal_child(void); static void vtest_server_set_signal_segv(void); static void vtest_server_open_read_file(void); static void vtest_server_open_socket(void); static void vtest_server_run(void); static void vtest_server_close_socket(void); static int vtest_client_dispatch_commands(struct vtest_client *client); int main(int argc, char **argv) { #ifdef __AFL_LOOP while (__AFL_LOOP(1000)) { #endif vtest_server_getenv(); vtest_server_parse_args(argc, argv); list_inithead(&server.new_clients); list_inithead(&server.active_clients); list_inithead(&server.inactive_clients); if (server.do_fork) { vtest_server_set_signal_child(); } else { vtest_server_set_signal_segv(); } vtest_server_run(); #ifdef __AFL_LOOP if (!server.main_server) { exit(0); } } #endif } #define OPT_NO_FORK 'f' #define OPT_NO_LOOP_OR_FORK 'l' #define OPT_MULTI_CLIENTS 'm' #define OPT_USE_GLX 'x' #define OPT_USE_EGL_SURFACELESS 's' #define OPT_USE_GLES 'e' #define OPT_RENDERNODE 'r' #define OPT_VENUS 'v' #define OPT_RENDER_SERVER 'n' static void vtest_server_parse_args(int argc, char **argv) { int ret; static struct option long_options[] = { {"no-fork", no_argument, NULL, OPT_NO_FORK}, {"no-loop-or-fork", no_argument, NULL, OPT_NO_LOOP_OR_FORK}, {"multi-clients", no_argument, NULL, OPT_MULTI_CLIENTS}, {"use-glx", no_argument, NULL, OPT_USE_GLX}, {"use-egl-surfaceless", no_argument, NULL, OPT_USE_EGL_SURFACELESS}, {"use-gles", no_argument, NULL, OPT_USE_GLES}, {"rendernode", required_argument, NULL, OPT_RENDERNODE}, {"venus", no_argument, NULL, OPT_VENUS}, {"render-server", no_argument, NULL, OPT_RENDER_SERVER}, {0, 0, 0, 0} }; /* getopt_long stores the option index here. */ int option_index = 0; do { ret = getopt_long(argc, argv, "", long_options, &option_index); switch (ret) { case -1: break; case OPT_NO_FORK: server.do_fork = false; break; case OPT_NO_LOOP_OR_FORK: server.do_fork = false; server.loop = false; break; case OPT_MULTI_CLIENTS: printf("multi-clients enabled: clients must trust each other\n"); server.multi_clients = true; break; case OPT_USE_GLX: server.use_glx = true; break; case OPT_USE_EGL_SURFACELESS: server.use_egl_surfaceless = true; break; case OPT_USE_GLES: server.use_gles = true; break; case OPT_RENDERNODE: server.render_device = optarg; break; #ifdef ENABLE_VENUS case OPT_VENUS: server.venus = true; break; #endif #ifdef ENABLE_RENDER_SERVER case OPT_RENDER_SERVER: server.render_server = true; break; #endif default: printf("Usage: %s [--no-fork] [--no-loop-or-fork] [--multi-clients] " "[--use-glx] [--use-egl-surfaceless] [--use-gles] " "[--rendernode ]" #ifdef ENABLE_VENUS " [--venus]" #endif #ifdef ENABLE_RENDER_SERVER " [--render-server]" #endif " [file]\n", argv[0]); exit(EXIT_FAILURE); break; } } while (ret >= 0); if (optind < argc) { server.read_file = argv[optind]; server.loop = false; server.do_fork = false; server.multi_clients = false; } server.ctx_flags = VIRGL_RENDERER_USE_EGL; if (server.use_glx) { if (server.use_egl_surfaceless || server.use_gles) { fprintf(stderr, "Cannot use surfaceless or GLES with GLX.\n"); exit(EXIT_FAILURE); } server.ctx_flags = VIRGL_RENDERER_USE_GLX; } else { if (server.use_egl_surfaceless) server.ctx_flags |= VIRGL_RENDERER_USE_SURFACELESS; if (server.use_gles) server.ctx_flags |= VIRGL_RENDERER_USE_GLES; } if (server.venus) { server.ctx_flags |= VIRGL_RENDERER_VENUS; } if (server.render_server) { server.ctx_flags |= VIRGL_RENDERER_RENDER_SERVER; } } static void vtest_server_getenv(void) { server.use_glx = getenv("VTEST_USE_GLX") != NULL; server.use_egl_surfaceless = getenv("VTEST_USE_EGL_SURFACELESS") != NULL; server.use_gles = getenv("VTEST_USE_GLES") != NULL; server.render_device = getenv("VTEST_RENDERNODE"); } static void handler(int sig, siginfo_t *si, void *unused) { (void)sig; (void)si, (void)unused; printf("SIGSEGV!\n"); exit(EXIT_FAILURE); } static void vtest_server_set_signal_child(void) { struct sigaction sa; int ret; memset(&sa, 0, sizeof(sa)); sigemptyset(&sa.sa_mask); sa.sa_handler = SIG_IGN; sa.sa_flags = 0; ret = sigaction(SIGCHLD, &sa, NULL); if (ret == -1) { perror("Failed to set SIGCHLD"); exit(1); } } static void vtest_server_set_signal_segv(void) { struct sigaction sa; int ret; memset(&sa, 0, sizeof(sa)); sigemptyset(&sa.sa_mask); sa.sa_flags = SA_SIGINFO; sa.sa_sigaction = handler; ret = sigaction(SIGSEGV, &sa, NULL); if (ret == -1) { perror("Failed to set SIGSEGV"); exit(1); } } static int vtest_server_add_client(int in_fd, int out_fd) { struct vtest_client *client; client = calloc(1, sizeof(*client)); if (!client) return -1; client->in_fd = in_fd; client->out_fd = out_fd; client->input.data.fd = in_fd; client->input.read = vtest_block_read; client->context_poll_fd = -1; list_addtail(&client->head, &server.new_clients); return 0; } static void vtest_server_open_read_file(void) { int in_fd; int out_fd; in_fd = open(server.read_file, O_RDONLY); if (in_fd == -1) { perror(NULL); exit(1); } out_fd = open("/dev/null", O_WRONLY); if (out_fd == -1) { perror(NULL); exit(1); } if (vtest_server_add_client(in_fd, out_fd)) { perror(NULL); exit(1); } } static void vtest_server_open_socket(void) { struct sockaddr_un un; server.socket = socket(PF_UNIX, SOCK_STREAM, 0); if (server.socket < 0) { goto err; } memset(&un, 0, sizeof(un)); un.sun_family = AF_UNIX; snprintf(un.sun_path, sizeof(un.sun_path), "%s", server.socket_name); unlink(un.sun_path); if (bind(server.socket, (struct sockaddr *)&un, sizeof(un)) < 0) { goto err; } if (listen(server.socket, 1) < 0){ goto err; } return; err: perror("Failed to setup socket."); exit(1); } static void vtest_server_wait_clients(void) { struct vtest_client *client; fd_set read_fds; int max_fd = -1; int ret; FD_ZERO(&read_fds); LIST_FOR_EACH_ENTRY(client, &server.active_clients, head) { FD_SET(client->in_fd, &read_fds); max_fd = MAX2(client->in_fd, max_fd); if (client->context_poll_fd >= 0) { FD_SET(client->context_poll_fd, &read_fds); max_fd = MAX2(client->context_poll_fd, max_fd); } } /* accept new clients when there is none or when multi_clients is set */ if (server.socket >= 0 && (max_fd < 0 || server.multi_clients)) { FD_SET(server.socket, &read_fds); max_fd = MAX2(server.socket, max_fd); } if (max_fd < 0) { if (!LIST_IS_EMPTY(&server.new_clients)) { return; } fprintf(stderr, "server has no fd to wait\n"); exit(1); } ret = select(max_fd + 1, &read_fds, NULL, NULL, NULL); if (ret < 0) { perror("Failed to select on socket!"); exit(1); } LIST_FOR_EACH_ENTRY(client, &server.active_clients, head) { if (FD_ISSET(client->in_fd, &read_fds)) { client->in_fd_ready = true; } if (client->context_poll_fd >= 0) { if (FD_ISSET(client->context_poll_fd, &read_fds)) { client->context_need_poll = true; } } else if (client->context) { client->context_need_poll = true; } } if (server.socket >= 0 && FD_ISSET(server.socket, &read_fds)) { int new_fd = accept(server.socket, NULL, NULL); if (new_fd < 0) { perror("Failed to accept socket."); exit(1); } if (vtest_server_add_client(new_fd, new_fd)) { perror("Failed to add client."); exit(1); } } } static const char *vtest_client_error_string(enum vtest_client_error err) { switch (err) { #define CASE(e) case e: return #e; CASE(VTEST_CLIENT_ERROR_INPUT_READ) CASE(VTEST_CLIENT_ERROR_CONTEXT_MISSING) CASE(VTEST_CLIENT_ERROR_CONTEXT_FAILED) CASE(VTEST_CLIENT_ERROR_COMMAND_ID) CASE(VTEST_CLIENT_ERROR_COMMAND_UNEXPECTED) CASE(VTEST_CLIENT_ERROR_COMMAND_DISPATCH) #undef CASE default: return "VTEST_CLIENT_ERROR_UNKNOWN"; } } static void vtest_server_dispatch_clients(void) { struct vtest_client *client, *tmp; LIST_FOR_EACH_ENTRY_SAFE(client, tmp, &server.active_clients, head) { int err; if (client->context_need_poll) { vtest_poll_context(client->context); client->context_need_poll = false; } if (!client->in_fd_ready) continue; client->in_fd_ready = false; err = vtest_client_dispatch_commands(client); if (err) { fprintf(stderr, "client failed: %s\n", vtest_client_error_string(err)); list_del(&client->head); list_addtail(&client->head, &server.inactive_clients); } } } static pid_t vtest_server_fork(void) { pid_t pid = fork(); if (pid == 0) { /* child */ vtest_server_set_signal_segv(); vtest_server_close_socket(); server.main_server = false; server.do_fork = false; server.loop = false; server.multi_clients = false; } return pid; } static void vtest_server_fork_clients(void) { struct vtest_client *client, *tmp; LIST_FOR_EACH_ENTRY_SAFE(client, tmp, &server.new_clients, head) { if (vtest_server_fork()) { /* parent: move new clients to the inactive list */ list_del(&client->head); list_addtail(&client->head, &server.inactive_clients); } else { /* child: move the first new client to the active list */ list_del(&client->head); list_addtail(&client->head, &server.active_clients); /* move the rest new clients to the inactive list */ LIST_FOR_EACH_ENTRY_SAFE(client, tmp, &server.new_clients, head) { list_del(&client->head); list_addtail(&client->head, &server.inactive_clients); } } } } static void vtest_server_activate_clients(void) { struct vtest_client *client, *tmp; /* move new clients to the active list */ LIST_FOR_EACH_ENTRY_SAFE(client, tmp, &server.new_clients, head) { list_addtail(&client->head, &server.active_clients); } list_inithead(&server.new_clients); } static void vtest_server_inactivate_clients(void) { struct vtest_client *client, *tmp; /* move active clients to the inactive list */ LIST_FOR_EACH_ENTRY_SAFE(client, tmp, &server.active_clients, head) { list_addtail(&client->head, &server.inactive_clients); } list_inithead(&server.active_clients); } static void vtest_server_tidy_clients(void) { struct vtest_client *client, *tmp; LIST_FOR_EACH_ENTRY_SAFE(client, tmp, &server.inactive_clients, head) { if (client->context) { vtest_destroy_context(client->context); } if (client->in_fd >= 0) { close(client->in_fd); } if (client->out_fd >= 0 && client->out_fd != client->in_fd) { close(client->out_fd); } free(client); } list_inithead(&server.inactive_clients); } static void vtest_server_run(void) { bool run = true; if (server.read_file) { vtest_server_open_read_file(); } else { vtest_server_open_socket(); } while (run) { const bool was_empty = LIST_IS_EMPTY(&server.active_clients); bool is_empty; vtest_server_wait_clients(); vtest_server_dispatch_clients(); if (server.do_fork) { vtest_server_fork_clients(); } else { vtest_server_activate_clients(); } /* init renderer after the first active client is added */ is_empty = LIST_IS_EMPTY(&server.active_clients); if (was_empty && !is_empty) { int ret = vtest_init_renderer(server.multi_clients, server.ctx_flags, server.render_device); if (ret) { vtest_server_inactivate_clients(); run = false; } } vtest_server_tidy_clients(); /* clean up renderer after the last active client is removed */ if (!was_empty && is_empty) { vtest_cleanup_renderer(); if (!server.loop) { run = false; } } } vtest_server_close_socket(); } static const struct vtest_command { int (*dispatch)(uint32_t); bool init_context; } vtest_commands[] = { /* CMD ids starts at 1 */ [0] = { NULL, false }, [VCMD_GET_CAPS] = { vtest_send_caps, false }, [VCMD_RESOURCE_CREATE] = { vtest_create_resource, true }, [VCMD_RESOURCE_UNREF] = { vtest_resource_unref, true }, [VCMD_TRANSFER_GET] = { vtest_transfer_get, true }, [VCMD_TRANSFER_PUT] = { vtest_transfer_put, true }, [VCMD_SUBMIT_CMD] = { vtest_submit_cmd, true }, [VCMD_RESOURCE_BUSY_WAIT] = { vtest_resource_busy_wait, false }, /* VCMD_CREATE_RENDERER is a special case */ [VCMD_CREATE_RENDERER] = { NULL, false }, [VCMD_GET_CAPS2] = { vtest_send_caps2, false }, [VCMD_PING_PROTOCOL_VERSION] = { vtest_ping_protocol_version, false }, [VCMD_PROTOCOL_VERSION] = { vtest_protocol_version, false }, /* since protocol version 2 */ [VCMD_RESOURCE_CREATE2] = { vtest_create_resource2, true }, [VCMD_TRANSFER_GET2] = { vtest_transfer_get2, true }, [VCMD_TRANSFER_PUT2] = { vtest_transfer_put2, true }, /* since protocol version 3 */ [VCMD_GET_PARAM] = { vtest_get_param, false }, [VCMD_GET_CAPSET] = { vtest_get_capset, false }, [VCMD_CONTEXT_INIT] = { vtest_context_init, false }, [VCMD_RESOURCE_CREATE_BLOB] = { vtest_resource_create_blob, true }, [VCMD_SYNC_CREATE] = { vtest_sync_create, true }, [VCMD_SYNC_UNREF] = { vtest_sync_unref, true }, [VCMD_SYNC_READ] = { vtest_sync_read, true }, [VCMD_SYNC_WRITE] = { vtest_sync_write, true }, [VCMD_SYNC_WAIT] = { vtest_sync_wait, true }, [VCMD_SUBMIT_CMD2] = { vtest_submit_cmd2, true }, }; static int vtest_client_dispatch_commands(struct vtest_client *client) { const struct vtest_command *cmd; int ret; uint32_t header[VTEST_HDR_SIZE]; ret = client->input.read(&client->input, &header, sizeof(header)); if (ret < 0 || (size_t)ret < sizeof(header)) { return VTEST_CLIENT_ERROR_INPUT_READ; } if (!client->context) { /* The first command MUST be VCMD_CREATE_RENDERER */ if (header[1] != VCMD_CREATE_RENDERER) { return VTEST_CLIENT_ERROR_CONTEXT_MISSING; } ret = vtest_create_context(&client->input, client->out_fd, header[0], &client->context); if (ret < 0) { return VTEST_CLIENT_ERROR_CONTEXT_FAILED; } printf("%s: client context created.\n", __func__); vtest_poll_resource_busy_wait(); return 0; } vtest_poll_resource_busy_wait(); if (header[1] <= 0 || header[1] >= ARRAY_SIZE(vtest_commands)) { return VTEST_CLIENT_ERROR_COMMAND_ID; } cmd = &vtest_commands[header[1]]; if (cmd->dispatch == NULL) { return VTEST_CLIENT_ERROR_COMMAND_UNEXPECTED; } /* we should consider per-context dispatch table to get rid of if's */ if (cmd->init_context) { ret = vtest_lazy_init_context(client->context); if (ret) { return VTEST_CLIENT_ERROR_CONTEXT_FAILED; } client->context_poll_fd = vtest_get_context_poll_fd(client->context); } vtest_set_current_context(client->context); ret = cmd->dispatch(header[0]); if (ret < 0) { return VTEST_CLIENT_ERROR_COMMAND_DISPATCH; } return 0; } static void vtest_server_close_socket(void) { if (server.socket != -1) { close(server.socket); server.socket = -1; } }