/*--------------------------------------------------------------------*/ /*--- A simple program to listen for valgrind logfile data. ---*/ /*--- valgrind-listener.c ---*/ /*--------------------------------------------------------------------*/ /* This file is part of Valgrind, a dynamic binary instrumentation framework. Copyright (C) 2000-2017 Julian Seward jseward@acm.org This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. The GNU General Public License is contained in the file COPYING. */ /*---------------------------------------------------------------*/ /* Include valgrind headers before system headers to avoid problems with the system headers #defining things which are used as names of structure members in vki headers. */ #include "pub_core_basics.h" #include "pub_core_libcassert.h" // For VG_BUGS_TO #include "pub_core_vki.h" // Avoids warnings from // pub_core_libcfile.h #include "pub_core_libcfile.h" // For VG_CLO_DEFAULT_LOGPORT #include <stdio.h> #include <unistd.h> #include <string.h> #include <time.h> #include <fcntl.h> #include <stdlib.h> #include <signal.h> #include <sys/poll.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> /*---------------------------------------------------------------*/ /* The default allowable number of concurrent connections. */ #define M_CONNECTIONS_DEFAULT 50 /* The maximum allowable number of concurrent connections. */ #define M_CONNECTIONS_MAX 5000 /* The maximum allowable number of concurrent connections. */ unsigned M_CONNECTIONS = 0; /*---------------------------------------------------------------*/ __attribute__ ((noreturn)) static void panic ( const char* str ) { fprintf(stderr, "\nvalgrind-listener: the " "'impossible' happened:\n %s\n", str); fprintf(stderr, "Please report this bug at: %s\n\n", VG_BUGS_TO); exit(1); } __attribute__ ((noreturn)) static void my_assert_fail ( const char* expr, const char* file, int line, const char* fn ) { fprintf(stderr, "\nvalgrind-listener: %s:%d (%s): Assertion '%s' failed.\n", file, line, fn, expr ); fprintf(stderr, "Please report this bug at: %s\n\n", VG_BUGS_TO); exit(1); } #undef assert #define assert(expr) \ ((void) ((expr) ? 0 : \ (my_assert_fail (VG_STRINGIFY(expr), \ __FILE__, __LINE__, \ __PRETTY_FUNCTION__), 0))) /*---------------------------------------------------------------*/ /* holds the fds for connections; zero if slot not in use. */ int conn_count = 0; int *conn_fd; struct pollfd *conn_pollfd; static void set_nonblocking ( int sd ) { int res; res = fcntl(sd, F_GETFL); res = fcntl(sd, F_SETFL, res | O_NONBLOCK); if (res != 0) { perror("fcntl failed"); panic("set_nonblocking"); } } static void set_blocking ( int sd ) { int res; res = fcntl(sd, F_GETFL); res = fcntl(sd, F_SETFL, res & ~O_NONBLOCK); if (res != 0) { perror("fcntl failed"); panic("set_blocking"); } } static void copyout ( char* buf, int nbuf ) { int i; for (i = 0; i < nbuf; i++) { if (buf[i] == '\n') { fprintf(stdout, "\n(%d) ", conn_count); } else { __attribute__((unused)) size_t ignored = fwrite(&buf[i], 1, 1, stdout); } } fflush(stdout); } static int read_from_sd ( int sd ) { char buf[100]; int n; set_blocking(sd); n = read(sd, buf, 99); if (n <= 0) return 0; /* closed */ copyout(buf, n); set_nonblocking(sd); while (1) { n = read(sd, buf, 100); if (n <= 0) return 1; /* not closed */ copyout(buf, n); } } static void snooze ( void ) { struct timespec req; req.tv_sec = 0; req.tv_nsec = 200 * 1000 * 1000; nanosleep(&req,NULL); } /* returns 0 if negative, or > BOUND or invalid characters were found */ static int atoi_with_bound ( const char* str, int bound ) { int n = 0; while (1) { if (*str == 0) break; if (*str < '0' || *str > '9') return 0; n = 10*n + (int)(*str - '0'); str++; if (n >= bound) return 0; } return n; } /* returns 0 if invalid, else port # */ static int atoi_portno ( const char* str ) { int n = atoi_with_bound(str, 65536); if (n < 1024) return 0; return n; } static void usage ( void ) { fprintf(stderr, "\n" "usage is:\n" "\n" " valgrind-listener [--exit-at-zero|-e] [--max-connect=INT] [port-number]\n" "\n" " where --exit-at-zero or -e causes the listener to exit\n" " when the number of connections falls back to zero\n" " (the default is to keep listening forever)\n" "\n" " --max-connect=INT can be used to increase the maximum\n" " number of connected processes (default = %d).\n" " INT must be positive and less than %d.\n" "\n" " port-number is the default port on which to listen for\n" " connections. It must be between 1024 and 65535.\n" " Current default is %d.\n" "\n" , M_CONNECTIONS_DEFAULT, M_CONNECTIONS_MAX, VG_CLO_DEFAULT_LOGPORT ); exit(1); } static void banner ( const char* str ) { time_t t; t = time(NULL); printf("valgrind-listener %s at %s", str, ctime(&t)); fflush(stdout); } static void exit_routine ( void ) { banner("exited"); exit(0); } static void sigint_handler ( int signo ) { exit_routine(); } int main (int argc, char** argv) { int i, j, k, res, one; int main_sd, new_sd; socklen_t client_len; struct sockaddr_in client_addr, server_addr; char /*bool*/ exit_when_zero = 0; int port = VG_CLO_DEFAULT_LOGPORT; for (i = 1; i < argc; i++) { if (0==strcmp(argv[i], "--exit-at-zero") || 0==strcmp(argv[i], "-e")) { exit_when_zero = 1; } else if (0 == strncmp(argv[i], "--max-connect=", 14)) { M_CONNECTIONS = atoi_with_bound(strchr(argv[i], '=') + 1, 5000); if (M_CONNECTIONS <= 0 || M_CONNECTIONS > M_CONNECTIONS_MAX) usage(); } else if (atoi_portno(argv[i]) > 0) { port = atoi_portno(argv[i]); } else usage(); } if (M_CONNECTIONS == 0) // nothing specified on command line M_CONNECTIONS = M_CONNECTIONS_DEFAULT; conn_fd = malloc(M_CONNECTIONS * sizeof conn_fd[0]); conn_pollfd = malloc(M_CONNECTIONS * sizeof conn_pollfd[0]); if (conn_fd == NULL || conn_pollfd == NULL) { fprintf(stderr, "Memory allocation failed; cannot continue.\n"); exit(1); } banner("started"); signal(SIGINT, sigint_handler); conn_count = 0; for (i = 0; i < M_CONNECTIONS; i++) conn_fd[i] = 0; /* create socket */ main_sd = socket(AF_INET, SOCK_STREAM, 0); if (main_sd < 0) { perror("cannot open socket "); panic("main -- create socket"); } /* allow address reuse to avoid "address already in use" errors */ one = 1; if (setsockopt(main_sd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(int)) < 0) { perror("cannot enable address reuse "); panic("main -- enable address reuse"); } /* bind server port */ server_addr.sin_family = AF_INET; server_addr.sin_addr.s_addr = htonl(INADDR_ANY); server_addr.sin_port = htons(port); if (bind(main_sd, (struct sockaddr *) &server_addr, sizeof(server_addr) ) < 0) { perror("cannot bind port "); panic("main -- bind port"); } res = listen(main_sd,M_CONNECTIONS); if (res != 0) { perror("listen failed "); panic("main -- listen"); } while (1) { snooze(); /* enquire, using poll, whether there is any activity available on the main socket descriptor. If so, someone is trying to connect; get the fd and add it to our table thereof. */ { struct pollfd ufd; while (1) { ufd.fd = main_sd; ufd.events = POLLIN; ufd.revents = 0; res = poll(&ufd, 1, 0); if (res == 0) break; /* ok, we have someone waiting to connect. Get the sd. */ client_len = sizeof(client_addr); new_sd = accept(main_sd, (struct sockaddr *)&client_addr, &client_len); if (new_sd < 0) { perror("cannot accept connection "); panic("main -- accept connection"); } /* find a place to put it. */ assert(new_sd > 0); for (i = 0; i < M_CONNECTIONS; i++) if (conn_fd[i] == 0) break; if (i >= M_CONNECTIONS) { fprintf(stderr, "\n\nMore than %d concurrent connections.\n" "Restart the listener giving --max-connect=INT on the\n" "commandline to increase the limit.\n\n", M_CONNECTIONS); exit(1); } conn_fd[i] = new_sd; conn_count++; printf("\n(%d) -------------------- CONNECT " "--------------------\n(%d)\n(%d) ", conn_count, conn_count, conn_count); fflush(stdout); } /* while (1) */ } /* We've processed all new connect requests. Listen for changes to the current set of fds. */ j = 0; for (i = 0; i < M_CONNECTIONS; i++) { if (conn_fd[i] == 0) continue; conn_pollfd[j].fd = conn_fd[i]; conn_pollfd[j].events = POLLIN /* | POLLHUP | POLLNVAL */; conn_pollfd[j].revents = 0; j++; } res = poll(conn_pollfd, j, 0 /* return immediately. */ ); if (res < 0) { perror("poll(main) failed"); panic("poll(main) failed"); } /* nothing happened. go round again. */ if (res == 0) { continue; } /* inspect the fds. */ for (i = 0; i < j; i++) { if (conn_pollfd[i].revents & POLLIN) { /* data is available on this fd */ res = read_from_sd(conn_pollfd[i].fd); if (res == 0) { /* the connection has been closed. */ close(conn_pollfd[i].fd); /* this fd has been closed or otherwise gone bad; forget about it. */ for (k = 0; k < M_CONNECTIONS; k++) if (conn_fd[k] == conn_pollfd[i].fd) break; assert(k < M_CONNECTIONS); conn_fd[k] = 0; conn_count--; printf("\n(%d) ------------------- DISCONNECT " "-------------------\n(%d)\n(%d) ", conn_count, conn_count, conn_count); fflush(stdout); if (conn_count == 0 && exit_when_zero) { printf("\n"); fflush(stdout); exit_routine(); } } } } /* for (i = 0; i < j; i++) */ } /* while (1) */ /* NOTREACHED */ } /*--------------------------------------------------------------------*/ /*--- end valgrind-listener.c ---*/ /*--------------------------------------------------------------------*/