aboutsummaryrefslogtreecommitdiff
path: root/tracecmd/trace-listen.c
diff options
context:
space:
mode:
Diffstat (limited to 'tracecmd/trace-listen.c')
-rw-r--r--tracecmd/trace-listen.c1201
1 files changed, 1201 insertions, 0 deletions
diff --git a/tracecmd/trace-listen.c b/tracecmd/trace-listen.c
new file mode 100644
index 00000000..86d2b9e9
--- /dev/null
+++ b/tracecmd/trace-listen.c
@@ -0,0 +1,1201 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2009, 2010 Red Hat Inc, Steven Rostedt <srostedt@redhat.com>
+ *
+ */
+#define _LARGEFILE64_SOURCE
+#include <dirent.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <getopt.h>
+#include <limits.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/wait.h>
+#include <netdb.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <errno.h>
+
+#ifdef VSOCK
+#include <linux/vm_sockets.h>
+#endif
+
+#include "trace-local.h"
+#include "trace-msg.h"
+
+#define dprint(fmt, ...) tracecmd_debug(fmt, ##__VA_ARGS__)
+
+#define MAX_OPTION_SIZE 4096
+
+#define _VAR_DIR_Q(dir) #dir
+#define VAR_DIR_Q(dir) _VAR_DIR_Q(dir)
+
+#define VAR_RUN_DIR VAR_DIR_Q(VAR_DIR) "/run"
+
+static char *default_output_dir = ".";
+static char *output_dir;
+static char *default_output_file = "trace";
+static char *output_file;
+
+static bool use_vsock;
+
+static int backlog = 5;
+
+static int do_daemon;
+
+/* Used for signaling INT to finish */
+static struct tracecmd_msg_handle *stop_msg_handle;
+static bool done;
+
+#define pdie(fmt, ...) \
+ do { \
+ tracecmd_plog_error(fmt, ##__VA_ARGS__);\
+ remove_pid_file(); \
+ exit(-1); \
+ } while (0)
+
+#define TEMP_FILE_STR "%s.%s:%s.cpu%d", output_file, host, port, cpu
+static char *get_temp_file(const char *host, const char *port, int cpu)
+{
+ char *file = NULL;
+ int size;
+
+ size = snprintf(file, 0, TEMP_FILE_STR);
+ file = malloc(size + 1);
+ if (!file)
+ return NULL;
+ sprintf(file, TEMP_FILE_STR);
+
+ return file;
+}
+
+static void put_temp_file(char *file)
+{
+ free(file);
+}
+
+static void signal_setup(int sig, sighandler_t handle)
+{
+ struct sigaction action;
+
+ sigaction(sig, NULL, &action);
+ /* Make accept return EINTR */
+ action.sa_flags &= ~SA_RESTART;
+ action.sa_handler = handle;
+ sigaction(sig, &action, NULL);
+}
+
+static void delete_temp_file(const char *host, const char *port, int cpu)
+{
+ char file[PATH_MAX];
+
+ snprintf(file, PATH_MAX, TEMP_FILE_STR);
+ unlink(file);
+}
+
+static int read_string(int fd, char *buf, size_t size)
+{
+ size_t i;
+ int n;
+
+ for (i = 0; i < size; i++) {
+ n = read(fd, buf+i, 1);
+ if (!buf[i] || n <= 0)
+ break;
+ }
+
+ return i;
+}
+
+static int process_option(struct tracecmd_msg_handle *msg_handle, char *option)
+{
+ /* currently the only option we have is to us TCP */
+ if (strcmp(option, "TCP") == 0) {
+ msg_handle->flags |= TRACECMD_MSG_FL_USE_TCP;
+ return 1;
+ }
+ return 0;
+}
+
+static void finish(int sig)
+{
+ if (stop_msg_handle)
+ tracecmd_msg_set_done(stop_msg_handle);
+ done = true;
+}
+
+static void make_pid_name(int mode, char *buf)
+{
+ snprintf(buf, PATH_MAX, VAR_RUN_DIR "/trace-cmd-net.pid");
+}
+
+static void remove_pid_file(void)
+{
+ char buf[PATH_MAX];
+ int mode = do_daemon;
+
+ if (!do_daemon)
+ return;
+
+ make_pid_name(mode, buf);
+
+ unlink(buf);
+}
+
+static int process_child(int sfd, const char *host, const char *port,
+ int cpu, int page_size, enum port_type type)
+{
+ struct sockaddr_storage peer_addr;
+#ifdef VSOCK
+ struct sockaddr_vm vm_addr;
+#endif
+ struct sockaddr *addr;
+ socklen_t addr_len;
+ char buf[page_size];
+ char *tempfile;
+ int left;
+ int cfd;
+ int fd;
+ int r, w;
+ int once = 0;
+
+ signal_setup(SIGUSR1, finish);
+
+ tempfile = get_temp_file(host, port, cpu);
+ if (!tempfile)
+ return -ENOMEM;
+
+ fd = open(tempfile, O_WRONLY | O_TRUNC | O_CREAT, 0644);
+ if (fd < 0)
+ pdie("creating %s", tempfile);
+
+ if (type == USE_TCP) {
+ addr = (struct sockaddr *)&peer_addr;
+ addr_len = sizeof(peer_addr);
+#ifdef VSOCK
+ } else if (type == USE_VSOCK) {
+ addr = (struct sockaddr *)&vm_addr;
+ addr_len = sizeof(vm_addr);
+#endif
+ }
+
+ if (type == USE_TCP || type == USE_VSOCK) {
+ if (listen(sfd, backlog) < 0)
+ pdie("listen");
+
+ cfd = accept(sfd, addr, &addr_len);
+ if (cfd < 0 && errno == EINTR)
+ goto done;
+ if (cfd < 0)
+ pdie("accept");
+ close(sfd);
+ sfd = cfd;
+ }
+
+ for (;;) {
+ /* TODO, make this copyless! */
+ r = read(sfd, buf, page_size);
+ if (r < 0) {
+ if (errno == EINTR)
+ break;
+ pdie("reading pages from client");
+ }
+ if (!r)
+ break;
+ /* UDP requires that we get the full size in one go */
+ if (type == USE_UDP && r < page_size && !once) {
+ once = 1;
+ warning("read %d bytes, expected %d", r, page_size);
+ }
+
+ left = r;
+ do {
+ w = write(fd, buf + (r - left), left);
+ if (w > 0)
+ left -= w;
+ } while (w >= 0 && left);
+ }
+
+ done:
+ put_temp_file(tempfile);
+ exit(0);
+}
+
+static int setup_vsock_port(int start_port, int *sfd)
+{
+ int sd;
+
+ sd = trace_vsock_make(start_port);
+ if (sd < 0)
+ return -errno;
+ *sfd = sd;
+
+ return start_port;
+}
+
+int trace_net_make(int port, enum port_type type)
+{
+ struct addrinfo hints;
+ struct addrinfo *result, *rp;
+ char buf[BUFSIZ];
+ int sd;
+ int s;
+
+ snprintf(buf, BUFSIZ, "%d", port);
+
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_family = AF_UNSPEC;
+ hints.ai_flags = AI_PASSIVE;
+
+ switch (type) {
+ case USE_TCP:
+ hints.ai_socktype = SOCK_STREAM;
+ break;
+ case USE_UDP:
+ hints.ai_socktype = SOCK_DGRAM;
+ break;
+ default:
+ return -1;
+ }
+
+ s = getaddrinfo(NULL, buf, &hints, &result);
+ if (s != 0)
+ pdie("getaddrinfo: error opening socket");
+
+ for (rp = result; rp != NULL; rp = rp->ai_next) {
+ sd = socket(rp->ai_family, rp->ai_socktype,
+ rp->ai_protocol);
+ if (sd < 0)
+ continue;
+
+ if (bind(sd, rp->ai_addr, rp->ai_addrlen) == 0)
+ break;
+
+ close(sd);
+ }
+ freeaddrinfo(result);
+
+ if (rp == NULL)
+ return -1;
+
+ dprint("Create listen port: %d fd:%d\n", port, sd);
+
+ return sd;
+}
+
+int trace_net_search(int start_port, int *sfd, enum port_type type)
+{
+ int num_port = start_port;
+
+ if (type == USE_VSOCK)
+ return setup_vsock_port(start_port, sfd);
+ again:
+ *sfd = trace_net_make(num_port, type);
+ if (*sfd < 0) {
+ if (++num_port > MAX_PORT_SEARCH)
+ pdie("No available ports to bind");
+ goto again;
+ }
+
+ return num_port;
+}
+
+static void fork_reader(int sfd, const char *node, const char *port,
+ int *pid, int cpu, int pagesize, enum port_type type)
+{
+ int ret;
+
+ *pid = fork();
+
+ if (*pid < 0)
+ pdie("creating reader");
+
+ if (!*pid) {
+ ret = process_child(sfd, node, port, cpu, pagesize, type);
+ if (ret < 0)
+ pdie("Problem with reader %d", ret);
+ }
+
+ close(sfd);
+}
+
+static int open_port(const char *node, const char *port, int *pid,
+ int cpu, int pagesize, int start_port, enum port_type type)
+{
+ int sfd;
+ int num_port;
+
+ /*
+ * trace_net_search() currently does not return an error, but if that
+ * changes in the future, we have a check for it now.
+ */
+ num_port = trace_net_search(start_port, &sfd, type);
+ if (num_port < 0)
+ return num_port;
+
+ fork_reader(sfd, node, port, pid, cpu, pagesize, type);
+
+ return num_port;
+}
+
+static int communicate_with_client(struct tracecmd_msg_handle *msg_handle)
+{
+ char *last_proto = NULL;
+ char buf[BUFSIZ];
+ char *option;
+ int pagesize = 0;
+ int options;
+ int size;
+ int cpus;
+ int n, s, t, i;
+ int ret = -EINVAL;
+ int fd = msg_handle->fd;
+
+ /* Let the client know what we are */
+ write(fd, "tracecmd", 8);
+
+ try_again:
+ /* read back the CPU count */
+ n = read_string(fd, buf, BUFSIZ);
+ if (n == BUFSIZ)
+ /** ERROR **/
+ return -EINVAL;
+
+ cpus = atoi(buf);
+
+ /* Is the client using the new protocol? */
+ if (cpus == -1) {
+ if (memcmp(buf, V3_CPU, n) != 0) {
+ /* If it did not send a version, then bail */
+ if (memcmp(buf, "-1V", 3)) {
+ tracecmd_plog("Unknown string %s\n", buf);
+ goto out;
+ }
+ /* Skip "-1" */
+ tracecmd_plog("Cannot handle the protocol %s\n", buf+2);
+
+ /* If it returned the same command as last time, bail! */
+ if (last_proto && strncmp(last_proto, buf, n) == 0) {
+ tracecmd_plog("Repeat of version %s sent\n", last_proto);
+ goto out;
+ }
+ free(last_proto);
+ last_proto = malloc(n + 1);
+ if (last_proto) {
+ memcpy(last_proto, buf, n);
+ last_proto[n] = 0;
+ }
+ /* Return the highest protocol we can use */
+ write(fd, "V3", 3);
+ goto try_again;
+ }
+
+ /* Let the client know we use v3 protocol */
+ write(fd, "V3", 3);
+
+ /* read the rest of dummy data */
+ n = read(fd, buf, sizeof(V3_MAGIC));
+ if (memcmp(buf, V3_MAGIC, n) != 0)
+ goto out;
+
+ /* We're off! */
+ write(fd, "OK", 2);
+
+ msg_handle->version = V3_PROTOCOL;
+
+ /* read the CPU count, the page size, and options */
+ if ((pagesize = tracecmd_msg_initial_setting(msg_handle)) < 0)
+ goto out;
+ } else {
+ /* The client is using the v1 protocol */
+
+ tracecmd_plog("cpus=%d\n", cpus);
+ if (cpus < 0)
+ goto out;
+
+ msg_handle->cpu_count = cpus;
+
+ /* next read the page size */
+ n = read_string(fd, buf, BUFSIZ);
+ if (n == BUFSIZ)
+ /** ERROR **/
+ goto out;
+
+ pagesize = atoi(buf);
+
+ tracecmd_plog("pagesize=%d\n", pagesize);
+ if (pagesize <= 0)
+ goto out;
+
+ /* Now the number of options */
+ n = read_string(fd, buf, BUFSIZ);
+ if (n == BUFSIZ)
+ /** ERROR **/
+ return -EINVAL;
+
+ options = atoi(buf);
+
+ for (i = 0; i < options; i++) {
+ /* next is the size of the options */
+ n = read_string(fd, buf, BUFSIZ);
+ if (n == BUFSIZ)
+ /** ERROR **/
+ goto out;
+ size = atoi(buf);
+ /* prevent a client from killing us */
+ if (size > MAX_OPTION_SIZE)
+ goto out;
+
+ ret = -ENOMEM;
+ option = malloc(size);
+ if (!option)
+ goto out;
+
+ ret = -EIO;
+ do {
+ t = size;
+ s = 0;
+ s = read(fd, option+s, t);
+ if (s <= 0)
+ goto out;
+ t -= s;
+ s = size - t;
+ } while (t);
+
+ s = process_option(msg_handle, option);
+ free(option);
+ /* do we understand this option? */
+ ret = -EINVAL;
+ if (!s)
+ goto out;
+ }
+ }
+
+ if (msg_handle->flags & TRACECMD_MSG_FL_USE_TCP)
+ tracecmd_plog("Using TCP for live connection\n");
+
+ ret = pagesize;
+ out:
+ free(last_proto);
+
+ return ret;
+}
+
+static int create_client_file(const char *node, const char *port)
+{
+ char buf[BUFSIZ];
+ int ofd;
+
+ snprintf(buf, BUFSIZ, "%s.%s:%s.dat", output_file, node, port);
+
+ ofd = open(buf, O_RDWR | O_CREAT | O_TRUNC, 0644);
+ if (ofd < 0)
+ pdie("Can not create file %s", buf);
+ return ofd;
+}
+
+static void destroy_all_readers(int cpus, int *pid_array, const char *node,
+ const char *port)
+{
+ int cpu;
+
+ for (cpu = 0; cpu < cpus; cpu++) {
+ if (pid_array[cpu] > 0) {
+ kill(pid_array[cpu], SIGKILL);
+ waitpid(pid_array[cpu], NULL, 0);
+ delete_temp_file(node, port, cpu);
+ pid_array[cpu] = 0;
+ }
+ }
+
+ free(pid_array);
+}
+
+static int *create_all_readers(const char *node, const char *port,
+ int pagesize, struct tracecmd_msg_handle *msg_handle)
+{
+ enum port_type port_type = USE_UDP;
+ char buf[BUFSIZ];
+ unsigned int *port_array;
+ int *pid_array;
+ unsigned int start_port;
+ unsigned int connect_port;
+ int cpus = msg_handle->cpu_count;
+ int cpu;
+ int pid;
+
+ if (!pagesize)
+ return NULL;
+
+ if (msg_handle->flags & TRACECMD_MSG_FL_USE_TCP)
+ port_type = USE_TCP;
+ else if (msg_handle->flags & TRACECMD_MSG_FL_USE_VSOCK)
+ port_type = USE_VSOCK;
+
+ port_array = malloc(sizeof(*port_array) * cpus);
+ if (!port_array)
+ return NULL;
+
+ pid_array = malloc(sizeof(*pid_array) * cpus);
+ if (!pid_array) {
+ free(port_array);
+ return NULL;
+ }
+
+ memset(pid_array, 0, sizeof(int) * cpus);
+
+ start_port = START_PORT_SEARCH;
+
+ /* Now create a port for each CPU */
+ for (cpu = 0; cpu < cpus; cpu++) {
+ connect_port = open_port(node, port, &pid, cpu,
+ pagesize, start_port, port_type);
+ if (connect_port < 0)
+ goto out_free;
+ port_array[cpu] = connect_port;
+ pid_array[cpu] = pid;
+ /*
+ * Due to some bugging finding ports,
+ * force search after last port
+ */
+ start_port = connect_port + 1;
+ }
+
+ if (msg_handle->version == V3_PROTOCOL) {
+ /* send set of port numbers to the client */
+ if (tracecmd_msg_send_port_array(msg_handle, port_array) < 0) {
+ tracecmd_plog("Failed sending port array\n");
+ goto out_free;
+ }
+ } else {
+ /* send the client a comma deliminated set of port numbers */
+ for (cpu = 0; cpu < cpus; cpu++) {
+ snprintf(buf, BUFSIZ, "%s%d",
+ cpu ? "," : "", port_array[cpu]);
+ write(msg_handle->fd, buf, strlen(buf));
+ }
+ /* end with null terminator */
+ write(msg_handle->fd, "\0", 1);
+ }
+
+ free(port_array);
+ return pid_array;
+
+ out_free:
+ free(port_array);
+ destroy_all_readers(cpus, pid_array, node, port);
+ return NULL;
+}
+
+static int
+collect_metadata_from_client(struct tracecmd_msg_handle *msg_handle,
+ int ofd)
+{
+ char buf[BUFSIZ];
+ int n, s, t;
+ int ifd = msg_handle->fd;
+ int ret = 0;
+
+ do {
+ n = read(ifd, buf, BUFSIZ);
+ if (n < 0) {
+ if (errno == EINTR)
+ continue;
+ ret = -errno;
+ break;
+ }
+ t = n;
+ s = 0;
+ do {
+ s = write(ofd, buf+s, t);
+ if (s < 0) {
+ if (errno == EINTR)
+ break;
+ ret = -errno;
+ goto out;
+ }
+ t -= s;
+ s = n - t;
+ } while (t);
+ } while (n > 0 && !tracecmd_msg_done(msg_handle));
+
+out:
+ return ret;
+}
+
+static void stop_all_readers(int cpus, int *pid_array)
+{
+ int cpu;
+
+ for (cpu = 0; cpu < cpus; cpu++) {
+ if (pid_array[cpu] > 0)
+ kill(pid_array[cpu], SIGUSR1);
+ }
+}
+
+static int put_together_file(int cpus, int ofd, const char *node,
+ const char *port, bool write_options)
+{
+ struct tracecmd_output *handle = NULL;
+ char **temp_files;
+ int cpu;
+ int ret = -ENOMEM;
+
+ /* Now put together the file */
+ temp_files = malloc(sizeof(*temp_files) * cpus);
+ if (!temp_files)
+ return -ENOMEM;
+
+ for (cpu = 0; cpu < cpus; cpu++) {
+ temp_files[cpu] = get_temp_file(node, port, cpu);
+ if (!temp_files[cpu])
+ goto out;
+ }
+
+ handle = tracecmd_get_output_handle_fd(ofd);
+ if (!handle) {
+ ret = -1;
+ goto out;
+ }
+
+ if (write_options) {
+ ret = tracecmd_write_cpus(handle, cpus);
+ if (ret)
+ goto out;
+ ret = tracecmd_write_buffer_info(handle);
+ if (ret)
+ goto out;
+ ret = tracecmd_write_options(handle);
+ if (ret)
+ goto out;
+ }
+ ret = tracecmd_write_cpu_data(handle, cpus, temp_files, NULL);
+
+out:
+ tracecmd_output_close(handle);
+ for (cpu--; cpu >= 0; cpu--) {
+ put_temp_file(temp_files[cpu]);
+ }
+ free(temp_files);
+ return ret;
+}
+
+static int process_client(struct tracecmd_msg_handle *msg_handle,
+ const char *node, const char *port)
+{
+ int *pid_array;
+ int pagesize;
+ int cpus;
+ int ofd;
+ int ret;
+
+ pagesize = communicate_with_client(msg_handle);
+ if (pagesize < 0)
+ return pagesize;
+
+ ofd = create_client_file(node, port);
+
+ pid_array = create_all_readers(node, port, pagesize, msg_handle);
+ if (!pid_array)
+ return -ENOMEM;
+
+ /* on signal stop this msg */
+ stop_msg_handle = msg_handle;
+
+ /* Now we are ready to start reading data from the client */
+ if (msg_handle->version == V3_PROTOCOL)
+ ret = tracecmd_msg_collect_data(msg_handle, ofd);
+ else
+ ret = collect_metadata_from_client(msg_handle, ofd);
+ stop_msg_handle = NULL;
+
+ /* wait a little to let our readers finish reading */
+ sleep(1);
+
+ cpus = msg_handle->cpu_count;
+
+ /* stop our readers */
+ stop_all_readers(cpus, pid_array);
+
+ /* wait a little to have the readers clean up */
+ sleep(1);
+
+ if (!ret)
+ ret = put_together_file(cpus, ofd, node, port,
+ msg_handle->version < V3_PROTOCOL);
+
+ destroy_all_readers(cpus, pid_array, node, port);
+
+ return ret;
+}
+
+static int do_fork(int cfd)
+{
+ pid_t pid;
+
+ /* in debug mode, we do not fork off children */
+ if (tracecmd_get_debug())
+ return 0;
+
+ pid = fork();
+ if (pid < 0) {
+ warning("failed to create child");
+ return -1;
+ }
+
+ if (pid > 0) {
+ close(cfd);
+ return pid;
+ }
+
+ signal_setup(SIGINT, finish);
+
+ return 0;
+}
+
+bool trace_net_cmp_connection(struct sockaddr_storage *addr, const char *name)
+{
+ char host[NI_MAXHOST], nhost[NI_MAXHOST];
+ char service[NI_MAXSERV];
+ socklen_t addr_len = sizeof(*addr);
+ struct addrinfo *result, *rp;
+ struct addrinfo hints;
+ bool found = false;
+ int s;
+
+ if (getnameinfo((struct sockaddr *)addr, addr_len,
+ host, NI_MAXHOST,
+ service, NI_MAXSERV, NI_NUMERICSERV))
+ return -1;
+
+ if (strcmp(host, name) == 0)
+ return true;
+
+ /* Check other IPs that name could be for */
+
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_family = AF_UNSPEC;
+ hints.ai_socktype = SOCK_STREAM;
+
+ /* Check other IPs that name could be for */
+ s = getaddrinfo(name, NULL, &hints, &result);
+ if (s != 0)
+ return false;
+
+ for (rp = result; rp != NULL; rp = rp->ai_next) {
+ if (getnameinfo(rp->ai_addr, rp->ai_addrlen,
+ nhost, NI_MAXHOST,
+ service, NI_MAXSERV, NI_NUMERICSERV))
+ continue;
+ if (strcmp(host, nhost) == 0) {
+ found = 1;
+ break;
+ }
+ }
+
+ freeaddrinfo(result);
+ return found;
+}
+
+bool trace_net_cmp_connection_fd(int fd, const char *name)
+{
+ struct sockaddr_storage addr;
+ socklen_t addr_len = sizeof(addr);
+
+ if (getpeername(fd, (struct sockaddr *)&addr, &addr_len))
+ return false;
+
+ return trace_net_cmp_connection(&addr, name);
+};
+
+int trace_net_print_connection(int fd)
+{
+ char host[NI_MAXHOST], service[NI_MAXSERV];
+ struct sockaddr_storage net_addr;
+ socklen_t addr_len;
+
+ addr_len = sizeof(net_addr);
+ if (getpeername(fd, (struct sockaddr *)&net_addr, &addr_len))
+ return -1;
+
+ if (getnameinfo((struct sockaddr *)&net_addr, addr_len,
+ host, NI_MAXHOST,
+ service, NI_MAXSERV, NI_NUMERICSERV))
+ return -1;
+
+ if (tracecmd_get_debug())
+ tracecmd_debug("Connected to %s:%s fd:%d\n", host, service, fd);
+ else
+ tracecmd_plog("Connected to %s:%s\n", host, service);
+ return 0;
+}
+
+static int do_connection(int cfd, struct sockaddr *addr,
+ socklen_t addr_len)
+{
+ struct tracecmd_msg_handle *msg_handle;
+ char host[NI_MAXHOST], service[NI_MAXSERV];
+ int s;
+ int ret;
+
+ ret = do_fork(cfd);
+ if (ret)
+ return ret;
+
+ msg_handle = tracecmd_msg_handle_alloc(cfd, 0);
+
+ if (use_vsock) {
+#ifdef VSOCK
+ struct sockaddr_vm *vm_addr = (struct sockaddr_vm *)addr;
+ snprintf(host, NI_MAXHOST, "V%d", vm_addr->svm_cid);
+ snprintf(service, NI_MAXSERV, "%d", vm_addr->svm_port);
+#endif
+ } else {
+ s = getnameinfo((struct sockaddr *)addr, addr_len,
+ host, NI_MAXHOST,
+ service, NI_MAXSERV, NI_NUMERICSERV);
+
+ if (s == 0)
+ tracecmd_plog("Connected with %s:%s\n", host, service);
+ else {
+ tracecmd_plog("Error with getnameinfo: %s\n", gai_strerror(s));
+ close(cfd);
+ tracecmd_msg_handle_close(msg_handle);
+ return -1;
+ }
+ }
+
+ process_client(msg_handle, host, service);
+
+ tracecmd_msg_handle_close(msg_handle);
+
+ if (!tracecmd_get_debug())
+ exit(0);
+
+ return 0;
+}
+
+static int *client_pids;
+static int free_pids;
+static int saved_pids;
+
+static void add_process(int pid)
+{
+ int *client = NULL;
+ int i;
+
+ if (free_pids) {
+ for (i = 0; i < saved_pids; i++) {
+ if (!client_pids[i]) {
+ client = &client_pids[i];
+ break;
+ }
+ }
+ free_pids--;
+ if (!client)
+ warning("Could not find free pid");
+ }
+ if (!client) {
+ client_pids = realloc(client_pids,
+ sizeof(*client_pids) * (saved_pids + 1));
+ if (!client_pids)
+ pdie("allocating pids");
+ client = &client_pids[saved_pids++];
+ }
+ *client = pid;
+}
+
+static void remove_process(int pid)
+{
+ int i;
+
+ for (i = 0; i < saved_pids; i++) {
+ if (client_pids[i] == pid)
+ break;
+ }
+
+ if (i == saved_pids)
+ return;
+
+ client_pids[i] = 0;
+ free_pids++;
+}
+
+static void kill_clients(void)
+{
+ int status;
+ int i;
+
+ for (i = 0; i < saved_pids; i++) {
+ if (!client_pids[i])
+ continue;
+ /* Only kill the clients if we received SIGINT or SIGTERM */
+ if (done)
+ kill(client_pids[i], SIGINT);
+ waitpid(client_pids[i], &status, 0);
+ }
+
+ saved_pids = 0;
+}
+
+static void clean_up(void)
+{
+ int status;
+ int ret;
+
+ /* Clean up any children that has started before */
+ do {
+ ret = waitpid(0, &status, WNOHANG);
+ if (ret > 0)
+ remove_process(ret);
+ } while (ret > 0);
+}
+
+static void do_accept_loop(int sfd)
+{
+ struct sockaddr_storage peer_addr;
+#ifdef VSOCK
+ struct sockaddr_vm vm_addr;
+#endif
+ struct sockaddr *addr;
+ socklen_t addr_len;
+ int cfd, pid;
+
+ if (use_vsock) {
+#ifdef VSOCK
+ addr = (struct sockaddr *)&vm_addr;
+ addr_len = sizeof(vm_addr);
+#endif
+ } else {
+ addr = (struct sockaddr *)&peer_addr;
+ addr_len = sizeof(peer_addr);
+ }
+
+ do {
+ cfd = accept(sfd, addr, &addr_len);
+ if (cfd < 0 && errno == EINTR) {
+ clean_up();
+ continue;
+ }
+ if (cfd < 0)
+ pdie("connecting");
+
+ pid = do_connection(cfd, addr, addr_len);
+ if (pid > 0)
+ add_process(pid);
+
+ } while (!done);
+ /* Get any final stragglers */
+ clean_up();
+}
+
+static void make_pid_file(void)
+{
+ char buf[PATH_MAX];
+ int mode = do_daemon;
+ int fd;
+
+ if (!do_daemon)
+ return;
+
+ make_pid_name(mode, buf);
+
+ fd = open(buf, O_WRONLY | O_CREAT | O_TRUNC, 0644);
+ if (fd < 0) {
+ perror(buf);
+ return;
+ }
+
+ sprintf(buf, "%d\n", getpid());
+ write(fd, buf, strlen(buf));
+ close(fd);
+}
+
+static void sigstub(int sig)
+{
+}
+
+static int get_vsock(const char *port)
+{
+ unsigned int cid;
+ int sd;
+
+ sd = trace_vsock_make(atoi(port));
+ if (sd < 0)
+ return sd;
+
+ cid = trace_vsock_local_cid();
+ if (cid >= 0)
+ printf("listening on @%u:%s\n", cid, port);
+
+ return sd;
+}
+
+static int get_network(char *port)
+{
+ struct addrinfo hints;
+ struct addrinfo *result, *rp;
+ int sfd, s;
+
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_family = AF_UNSPEC;
+ hints.ai_socktype = SOCK_STREAM;
+ hints.ai_flags = AI_PASSIVE;
+
+ s = getaddrinfo(NULL, port, &hints, &result);
+ if (s != 0)
+ pdie("getaddrinfo: error opening %s", port);
+
+ for (rp = result; rp != NULL; rp = rp->ai_next) {
+ sfd = socket(rp->ai_family, rp->ai_socktype,
+ rp->ai_protocol);
+ if (sfd < 0)
+ continue;
+
+ if (bind(sfd, rp->ai_addr, rp->ai_addrlen) == 0)
+ break;
+
+ close(sfd);
+ }
+
+ if (rp == NULL)
+ pdie("Could not bind");
+
+ freeaddrinfo(result);
+
+ return sfd;
+}
+
+static void do_listen(char *port)
+{
+ int sfd;
+
+ if (!tracecmd_get_debug())
+ signal_setup(SIGCHLD, sigstub);
+
+ make_pid_file();
+
+ if (use_vsock)
+ sfd = get_vsock(port);
+ else
+ sfd = get_network(port);
+
+
+ if (listen(sfd, backlog) < 0)
+ pdie("listen");
+
+ do_accept_loop(sfd);
+
+ kill_clients();
+
+ remove_pid_file();
+}
+
+static void start_daemon(void)
+{
+ do_daemon = 1;
+
+ if (daemon(1, 0) < 0)
+ die("starting daemon");
+}
+
+enum {
+ OPT_verbose = 254,
+ OPT_debug = 255,
+};
+
+void trace_listen(int argc, char **argv)
+{
+ char *logfile = NULL;
+ char *port = NULL;
+ int daemon = 0;
+ int c;
+
+ if (argc < 2)
+ usage(argv);
+
+ if (strcmp(argv[1], "listen") != 0)
+ usage(argv);
+
+ for (;;) {
+ int option_index = 0;
+ static struct option long_options[] = {
+ {"port", required_argument, NULL, 'p'},
+ {"help", no_argument, NULL, '?'},
+ {"debug", no_argument, NULL, OPT_debug},
+ {"verbose", optional_argument, NULL, OPT_verbose},
+ {NULL, 0, NULL, 0}
+ };
+
+ c = getopt_long (argc-1, argv+1, "+hp:Vo:d:l:D",
+ long_options, &option_index);
+ if (c == -1)
+ break;
+ switch (c) {
+ case 'h':
+ usage(argv);
+ break;
+ case 'p':
+ port = optarg;
+ break;
+ case 'd':
+ output_dir = optarg;
+ break;
+ case 'V':
+ use_vsock = true;
+ break;
+ case 'o':
+ output_file = optarg;
+ break;
+ case 'l':
+ logfile = optarg;
+ break;
+ case 'D':
+ daemon = 1;
+ break;
+ case OPT_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 (!port)
+ usage(argv);
+
+ if ((argc - optind) >= 2)
+ usage(argv);
+
+ if (!output_file)
+ output_file = default_output_file;
+
+ if (!output_dir)
+ output_dir = default_output_dir;
+
+ if (logfile) {
+ /* set the writes to a logfile instead */
+ if (tracecmd_set_logfile(logfile) < 0)
+ die("creating log file %s", logfile);
+ }
+
+ if (chdir(output_dir) < 0)
+ die("Can't access directory %s", output_dir);
+
+ if (daemon)
+ start_daemon();
+
+ signal_setup(SIGINT, finish);
+ signal_setup(SIGTERM, finish);
+
+ do_listen(port);
+
+ return;
+}