blob: 10a1046427a54fa16363f3b7c00416bb3f2131e0 [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 GUEST_NAME "::GUEST::"
#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 = tcmd_vsock_make_any();
if (fd < 0)
die("Failed to open vsocket");
ret = tcmd_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 int wait_for_connection(int fd)
{
int sd;
if (fd < 0)
return -1;
while (true) {
tracecmd_debug("Listening on fd:%d\n", fd);
sd = accept(fd, NULL, NULL);
tracecmd_debug("Accepted fd:%d\n", sd);
if (sd < 0) {
if (errno == EINTR)
continue;
return -1;
}
break;
}
close(fd);
return sd;
}
static void agent_handle(int sd, int nr_cpus, int page_size,
int cid, int rcid, const char *network)
{
struct tracecmd_tsync_protos *tsync_protos = NULL;
struct tracecmd_time_sync *tsync = NULL;
struct tracecmd_msg_handle *msg_handle;
const char *tsync_proto = NULL;
struct trace_guest *guest;
unsigned long long peer_trace_id;
unsigned long long trace_id;
unsigned long flags = rcid >= 0 ? TRACECMD_MSG_FL_PROXY : 0;
enum tracecmd_time_sync_role tsync_role = TRACECMD_TIME_SYNC_ROLE_GUEST;
unsigned int remote_id;
unsigned int local_id;
unsigned int tsync_port = 0;
unsigned int *ports;
unsigned int client_cpus = 0;
unsigned int guests = 0;
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, flags);
if (!msg_handle)
die("Failed to allocate message handle");
if (rcid >= 0) {
tsync_role = TRACECMD_TIME_SYNC_ROLE_HOST;
ret = tracecmd_msg_recv_trace_proxy(msg_handle, &argc, &argv,
&use_fifos, &peer_trace_id,
&tsync_protos,
&client_cpus,
&guests);
/* Update the guests peer_trace_id */
guest = trace_get_guest(rcid, NULL);
if (guest)
guest->trace_id = peer_trace_id;
} else {
ret = tracecmd_msg_recv_trace_req(msg_handle, &argc, &argv,
&use_fifos, &peer_trace_id,
&tsync_protos);
}
if (ret < 0)
die("Failed to receive trace request");
tsync_proto = tracecmd_tsync_get_proto(tsync_protos, get_clock(argc, argv),
tsync_role);
if (use_fifos && open_agent_fifos(nr_cpus, fds))
use_fifos = false;
if (!use_fifos)
make_sockets(nr_cpus, fds, ports, network);
if (tsync_proto) {
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 = tcmd_vsock_make_any();
if (fd >= 0 &&
tcmd_vsock_get_port(fd, &tsync_port) < 0) {
close(fd);
fd = -1;
}
}
}
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");
if (tsync_proto) {
fd = wait_for_connection(fd);
if (rcid >= 0) {
tsync = trace_tsync_as_host(fd, trace_id, 0, rcid,
client_cpus, tsync_proto,
get_clock(argc, argv));
} else {
tsync = trace_tsync_as_guest(fd, tsync_proto,
get_clock(argc, argv),
remote_id, local_id);
}
if (!tsync)
close(fd);
}
trace_record_agent(msg_handle, nr_cpus, fds, argc, argv,
use_fifos, tsync, trace_id, rcid, network);
if (tsync) {
if (rcid < 0)
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, int proxy_id,
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 = -1, rcid = -1;
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 = tcmd_vsock_make(port);
if (sd < 0)
die("Failed to open socket");
tracecmd_tsync_init();
if (!network) {
cid = tcmd_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 (proxy_id >= 0) {
/* Only works with vsockets */
if (get_vsocket_params(cd, NULL, &rcid) < 0) {
dprint("Failed to find connected cid");
close(cd);
continue;
}
if (rcid != proxy_id) {
dprint("Cid %d does not match expected cid %d\n",
rcid, proxy_id);
close(cd);
continue;
}
}
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, cid, rcid, network);
}
if (pid > 0)
handler_pid = pid;
busy:
close(cd);
}
}
enum {
OPT_verbose = 254,
OPT_debug = 255,
OPT_notimeout = 256,
};
void trace_agent(int argc, char **argv)
{
struct trace_guest *guest;
bool do_daemon = false;
unsigned int port = TRACE_AGENT_DEFAULT_PORT;
const char *network = NULL;
int proxy_id = -1;
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, OPT_debug},
{"notimeout", no_argument, NULL, OPT_notimeout},
{"verbose", optional_argument, NULL, OPT_verbose},
{NULL, 0, NULL, 0}
};
c = getopt_long(argc-1, argv+1, "+hp:DN:P:",
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);
if (proxy_id >= 0)
die("-N cannot be used with -P");
break;
case 'D':
do_daemon = true;
break;
case 'P':
proxy_id = atoi(optarg);
guest = trace_get_guest(proxy_id, GUEST_NAME);
if (!guest)
die("Failed to allocate guest instance");
break;
case OPT_debug:
tracecmd_set_debug(true);
break;
case OPT_notimeout:
tracecmd_set_notimeout(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, proxy_id, network);
}