diff options
Diffstat (limited to 'tracecmd/trace-agent.c')
-rw-r--r-- | tracecmd/trace-agent.c | 384 |
1 files changed, 384 insertions, 0 deletions
diff --git a/tracecmd/trace-agent.c b/tracecmd/trace-agent.c new file mode 100644 index 00000000..f0723a66 --- /dev/null +++ b/tracecmd/trace-agent.c @@ -0,0 +1,384 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2018 VMware Inc, Slavomir Kaslev <kaslevs@vmware.com> + * + * based on prior implementation by Yoshihiro Yunomae + * Copyright (C) 2013 Hitachi, Ltd. + * Yoshihiro YUNOMAE <yoshihiro.yunomae.ez@hitachi.com> + */ + +#include <errno.h> +#include <fcntl.h> +#include <getopt.h> +#include <signal.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <sys/socket.h> +#include <sys/wait.h> +#include <unistd.h> +#include <pthread.h> + +#include "trace-local.h" +#include "trace-msg.h" + +#define dprint(fmt, ...) tracecmd_debug(fmt, ##__VA_ARGS__) + +static void make_vsocks(int nr, int *fds, unsigned int *ports) +{ + unsigned int port; + int i, fd, ret; + + for (i = 0; i < nr; i++) { + fd = trace_vsock_make_any(); + if (fd < 0) + die("Failed to open vsocket"); + + ret = trace_vsock_get_port(fd, &port); + if (ret < 0) + die("Failed to get vsocket address"); + + fds[i] = fd; + ports[i] = port; + } +} + +static void make_net(int nr, int *fds, unsigned int *ports) +{ + int port; + int i, fd; + int start_port = START_PORT_SEARCH; + + for (i = 0; i < nr; i++) { + port = trace_net_search(start_port, &fd, USE_TCP); + if (port < 0) + die("Failed to open socket"); + if (listen(fd, 5) < 0) + die("Failed to listen on port %d\n", port); + fds[i] = fd; + ports[i] = port; + dprint("CPU[%d]: fd:%d port:%d\n", i, fd, port); + start_port = port + 1; + } +} + +static void make_sockets(int nr, int *fds, unsigned int *ports, + const char * network) +{ + if (network) + return make_net(nr, fds, ports); + else + return make_vsocks(nr, fds, ports); +} + +static int open_agent_fifos(int nr_cpus, int *fds) +{ + char path[PATH_MAX]; + int i, fd, ret; + + for (i = 0; i < nr_cpus; i++) { + snprintf(path, sizeof(path), VIRTIO_FIFO_FMT, i); + fd = open(path, O_WRONLY); + if (fd < 0) { + ret = -errno; + goto cleanup; + } + + fds[i] = fd; + } + + return 0; + +cleanup: + while (--i >= 0) + close(fds[i]); + + return ret; +} + +static char *get_clock(int argc, char **argv) +{ + int i; + + if (!argc || !argv) + return NULL; + + for (i = 0; i < argc - 1; i++) { + if (!strcmp("-C", argv[i])) + return argv[i+1]; + } + return NULL; +} + +static void trace_print_connection(int fd, const char *network) +{ + int ret; + + if (network) + ret = trace_net_print_connection(fd); + else + ret = trace_vsock_print_connection(fd); + if (ret < 0) + tracecmd_debug("Could not print connection fd:%d\n", fd); +} + +static void agent_handle(int sd, int nr_cpus, int page_size, const char *network) +{ + struct tracecmd_tsync_protos *tsync_protos = NULL; + struct tracecmd_time_sync *tsync = NULL; + struct tracecmd_msg_handle *msg_handle; + char *tsync_proto = NULL; + unsigned long long trace_id; + unsigned int remote_id; + unsigned int local_id; + unsigned int tsync_port = 0; + unsigned int *ports; + char **argv = NULL; + int argc = 0; + bool use_fifos; + int *fds; + int ret; + int fd; + + fds = calloc(nr_cpus, sizeof(*fds)); + ports = calloc(nr_cpus, sizeof(*ports)); + if (!fds || !ports) + die("Failed to allocate memory"); + + msg_handle = tracecmd_msg_handle_alloc(sd, 0); + if (!msg_handle) + die("Failed to allocate message handle"); + + ret = tracecmd_msg_recv_trace_req(msg_handle, &argc, &argv, + &use_fifos, &trace_id, + &tsync_protos); + if (ret < 0) + die("Failed to receive trace request"); + + if (use_fifos && open_agent_fifos(nr_cpus, fds)) + use_fifos = false; + + if (!use_fifos) + make_sockets(nr_cpus, fds, ports, network); + if (tsync_protos && tsync_protos->names) { + if (network) { + /* For now just use something */ + remote_id = 2; + local_id = 1; + tsync_port = trace_net_search(START_PORT_SEARCH, &fd, USE_TCP); + if (listen(fd, 5) < 0) + die("Failed to listen on %d\n", tsync_port); + } else { + if (get_vsocket_params(msg_handle->fd, &local_id, + &remote_id)) { + warning("Failed to get local and remote ids"); + /* Just make something up */ + remote_id = -1; + local_id = -2; + } + fd = trace_vsock_make_any(); + if (fd >= 0 && + trace_vsock_get_port(fd, &tsync_port) < 0) { + close(fd); + fd = -1; + } + } + if (fd >= 0) { + tsync = tracecmd_tsync_with_host(fd, tsync_protos, + get_clock(argc, argv), + remote_id, local_id); + } + if (tsync) { + tracecmd_tsync_get_selected_proto(tsync, &tsync_proto); + } else { + warning("Failed to negotiate timestamps synchronization with the host"); + if (fd >= 0) + close(fd); + } + } + trace_id = tracecmd_generate_traceid(); + ret = tracecmd_msg_send_trace_resp(msg_handle, nr_cpus, page_size, + ports, use_fifos, trace_id, + tsync_proto, tsync_port); + if (ret < 0) + die("Failed to send trace response"); + + trace_record_agent(msg_handle, nr_cpus, fds, argc, argv, + use_fifos, trace_id, network); + + if (tsync) { + tracecmd_tsync_with_host_stop(tsync); + tracecmd_tsync_free(tsync); + } + + if (tsync_protos) { + free(tsync_protos->names); + free(tsync_protos); + } + free(argv[0]); + free(argv); + free(ports); + free(fds); + tracecmd_msg_handle_close(msg_handle); + exit(0); +} + +static volatile pid_t handler_pid; + +static void handle_sigchld(int sig) +{ + int wstatus; + pid_t pid; + + for (;;) { + pid = waitpid(-1, &wstatus, WNOHANG); + if (pid <= 0) + break; + + if (pid == handler_pid) + handler_pid = 0; + } +} + +static pid_t do_fork() +{ + /* in debug mode, we do not fork off children */ + if (tracecmd_get_debug()) + return 0; + + return fork(); +} + +static void agent_serve(unsigned int port, bool do_daemon, const char *network) +{ + struct sockaddr_storage net_addr; + struct sockaddr *addr = NULL; + socklen_t *addr_len_p = NULL; + socklen_t addr_len = sizeof(net_addr); + int sd, cd, nr_cpus; + unsigned int cid; + pid_t pid; + + signal(SIGCHLD, handle_sigchld); + + if (network) { + addr = (struct sockaddr *)&net_addr; + addr_len_p = &addr_len; + } + + nr_cpus = tracecmd_count_cpus(); + page_size = getpagesize(); + + if (network) { + sd = trace_net_make(port, USE_TCP); + if (listen(sd, 5) < 0) + die("Failed to listen on %d\n", port); + } else + sd = trace_vsock_make(port); + if (sd < 0) + die("Failed to open socket"); + tracecmd_tsync_init(); + + if (!network) { + cid = trace_vsock_local_cid(); + if (cid >= 0) + printf("listening on @%u:%u\n", cid, port); + } + + if (do_daemon && daemon(1, 0)) + die("daemon"); + + for (;;) { + cd = accept(sd, addr, addr_len_p); + if (cd < 0) { + if (errno == EINTR) + continue; + die("accept"); + } + if (tracecmd_get_debug()) + trace_print_connection(cd, network); + + if (network && !trace_net_cmp_connection(&net_addr, network)) { + dprint("Client does not match '%s'\n", network); + close(cd); + continue; + } + + if (handler_pid) + goto busy; + + pid = do_fork(); + if (pid == 0) { + close(sd); + signal(SIGCHLD, SIG_DFL); + agent_handle(cd, nr_cpus, page_size, network); + } + if (pid > 0) + handler_pid = pid; + +busy: + close(cd); + } +} + +enum { + OPT_verbose = 254, + DO_DEBUG = 255 +}; + +void trace_agent(int argc, char **argv) +{ + bool do_daemon = false; + unsigned int port = TRACE_AGENT_DEFAULT_PORT; + const char *network = NULL; + + if (argc < 2) + usage(argv); + + if (strcmp(argv[1], "agent") != 0) + usage(argv); + + for (;;) { + int c, option_index = 0; + static struct option long_options[] = { + {"port", required_argument, NULL, 'p'}, + {"help", no_argument, NULL, '?'}, + {"debug", no_argument, NULL, DO_DEBUG}, + {"verbose", optional_argument, NULL, OPT_verbose}, + {NULL, 0, NULL, 0} + }; + + c = getopt_long(argc-1, argv+1, "+hp:DN:", + long_options, &option_index); + if (c == -1) + break; + switch (c) { + case 'h': + usage(argv); + break; + case 'N': + network = optarg; + break; + case 'p': + port = atoi(optarg); + break; + case 'D': + do_daemon = true; + break; + case DO_DEBUG: + tracecmd_set_debug(true); + break; + case OPT_verbose: + if (trace_set_verbose(optarg) < 0) + die("invalid verbose level %s", optarg); + break; + default: + usage(argv); + } + } + + if (optind < argc-1) + usage(argv); + + agent_serve(port, do_daemon, network); +} |