blob: f0723a6601a4b1d28ba5c9d3f4b5a57dc31bc2ea [file] [log] [blame]
// 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);
}