| // SPDX-License-Identifier: GPL-2.0 |
| /* |
| * Copyright (C) 2018 VMware Inc, Slavomir Kaslev <[email protected]> |
| * |
| * based on prior implementation by Yoshihiro Yunomae |
| * Copyright (C) 2013 Hitachi, Ltd. |
| * Yoshihiro YUNOMAE <[email protected]> |
| */ |
| |
| #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); |
| } |