| // 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/ioctl.h> |
| #include <sys/socket.h> |
| #include <sys/wait.h> |
| #include <unistd.h> |
| #include <linux/vm_sockets.h> |
| #include <pthread.h> |
| |
| #include "trace-local.h" |
| #include "trace-msg.h" |
| |
| #define GET_LOCAL_CID 0x7b9 |
| |
| static int get_local_cid(unsigned int *cid) |
| { |
| int fd, ret = 0; |
| |
| fd = open("/dev/vsock", O_RDONLY); |
| if (fd < 0) |
| return -errno; |
| |
| if (ioctl(fd, GET_LOCAL_CID, cid)) |
| ret = -errno; |
| |
| close(fd); |
| return ret; |
| } |
| |
| int trace_make_vsock(unsigned int port) |
| { |
| struct sockaddr_vm addr = { |
| .svm_family = AF_VSOCK, |
| .svm_cid = VMADDR_CID_ANY, |
| .svm_port = port, |
| }; |
| int sd; |
| |
| sd = socket(AF_VSOCK, SOCK_STREAM, 0); |
| if (sd < 0) |
| return -errno; |
| |
| setsockopt(sd, SOL_SOCKET, SO_REUSEADDR, &(int){1}, sizeof(int)); |
| |
| if (bind(sd, (struct sockaddr *)&addr, sizeof(addr))) |
| return -errno; |
| |
| if (listen(sd, SOMAXCONN)) |
| return -errno; |
| |
| return sd; |
| } |
| |
| int trace_get_vsock_port(int sd, unsigned int *port) |
| { |
| struct sockaddr_vm addr; |
| socklen_t addr_len = sizeof(addr); |
| |
| if (getsockname(sd, (struct sockaddr *)&addr, &addr_len)) |
| return -errno; |
| |
| if (addr.svm_family != AF_VSOCK) |
| return -EINVAL; |
| |
| if (port) |
| *port = addr.svm_port; |
| |
| return 0; |
| } |
| |
| 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_make_vsock(VMADDR_PORT_ANY); |
| if (fd < 0) |
| die("Failed to open vsocket"); |
| |
| ret = trace_get_vsock_port(fd, &port); |
| if (ret < 0) |
| die("Failed to get vsocket address"); |
| |
| fds[i] = fd; |
| ports[i] = port; |
| } |
| } |
| |
| 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 agent_handle(int sd, int nr_cpus, int page_size) |
| { |
| struct tracecmd_msg_handle *msg_handle; |
| unsigned int tsync_protos_size = 0; |
| unsigned int tsync_proto = 0; |
| unsigned long long trace_id; |
| unsigned int tsync_port = 0; |
| char *tsync_protos = NULL; |
| unsigned int *ports; |
| pthread_t sync_thr; |
| char **argv = NULL; |
| int argc = 0; |
| bool use_fifos; |
| int *fds; |
| int ret; |
| |
| 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, &tsync_protos_size); |
| 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_vsocks(nr_cpus, fds, ports); |
| if (tsync_protos) { |
| tsync_proto = tracecmd_guest_tsync(tsync_protos, |
| tsync_protos_size, |
| get_clock(argc, argv), |
| &tsync_port, &sync_thr); |
| if (!tsync_proto) |
| warning("Failed to negotiate timestamps synchronization with the host"); |
| } |
| 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); |
| |
| if (tsync_proto) |
| pthread_join(sync_thr, NULL); |
| |
| 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) |
| { |
| int sd, cd, nr_cpus; |
| unsigned int cid; |
| pid_t pid; |
| |
| signal(SIGCHLD, handle_sigchld); |
| |
| nr_cpus = tracecmd_count_cpus(); |
| page_size = getpagesize(); |
| |
| sd = trace_make_vsock(port); |
| if (sd < 0) |
| die("Failed to open vsocket"); |
| |
| if (!get_local_cid(&cid)) |
| printf("listening on @%u:%u\n", cid, port); |
| |
| for (;;) { |
| cd = accept(sd, NULL, NULL); |
| if (cd < 0) { |
| if (errno == EINTR) |
| continue; |
| die("accept"); |
| } |
| |
| if (handler_pid) |
| goto busy; |
| |
| pid = do_fork(); |
| if (pid == 0) { |
| close(sd); |
| signal(SIGCHLD, SIG_DFL); |
| agent_handle(cd, nr_cpus, page_size); |
| } |
| if (pid > 0) |
| handler_pid = pid; |
| |
| busy: |
| close(cd); |
| } |
| } |
| |
| enum { |
| DO_DEBUG = 255 |
| }; |
| |
| void trace_agent(int argc, char **argv) |
| { |
| bool do_daemon = false; |
| unsigned int port = TRACE_AGENT_DEFAULT_PORT; |
| |
| 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}, |
| {NULL, 0, NULL, 0} |
| }; |
| |
| c = getopt_long(argc-1, argv+1, "+hp:D", |
| long_options, &option_index); |
| if (c == -1) |
| break; |
| switch (c) { |
| case 'h': |
| usage(argv); |
| break; |
| case 'p': |
| port = atoi(optarg); |
| break; |
| case 'D': |
| do_daemon = true; |
| break; |
| case DO_DEBUG: |
| tracecmd_set_debug(true); |
| break; |
| default: |
| usage(argv); |
| } |
| } |
| |
| if (optind < argc-1) |
| usage(argv); |
| |
| if (do_daemon && daemon(1, 0)) |
| die("daemon"); |
| |
| agent_serve(port); |
| } |