clamdtop/clamdtop.c
aa22174b
 /*
8db0a346
  *  ClamdTOP
aa22174b
  *
e1cbc270
  *  Copyright (C) 2013-2019 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
  *  Copyright (C) 2008-2013 Sourcefire, Inc.
aa22174b
  *
  *  Authors: Török Edvin
  *
  *  This program is free software; you can redistribute it and/or modify
  *  it under the terms of the GNU General Public License version 2 as
  *  published by the Free Software Foundation.
  *
  *  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., 51 Franklin Street, Fifth Floor, Boston,
  *  MA 02110-1301, USA.
  */
e7fdfa9a
 #define _GNU_SOURCE
 #define __EXTENSIONS
 #define GCC_PRINTF
 #define GCC_SCANF
8db0a346
 
 #ifdef HAVE_CONFIG_H
 #include "clamav-config.h"
 #endif
 
e2f3282b
 #ifdef HAVE_STDINT_H
 #include <stdint.h>
 #endif
 
aa22174b
 #include <unistd.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
 #include <sys/types.h>
8db0a346
 #include CURSES_INCLUDE
aa22174b
 #include <time.h>
 #include <ctype.h>
 #include <signal.h>
ce936887
 #ifdef _WIN32
 #include <windows.h>
 #include <winsock2.h>
 /* this is not correct, perhaps winsock errors are not mapped on errno */
 #define herror perror
 #else
aa22174b
 #include <netdb.h>
 #include <arpa/inet.h>
 #include <netinet/in.h>
 #include <sys/socket.h>
 #include <sys/un.h>
ce936887
 #endif
aa22174b
 #include <sys/time.h>
8ff2ee26
 #include <assert.h>
233732d7
 #include <errno.h>
aa22174b
 
6df13d04
 #include "libclamav/clamav.h"
8db0a346
 #include "shared/optparser.h"
 #include "shared/misc.h"
 
e7fdfa9a
 /* Types, prototypes and globals*/
 typedef struct connection {
288057e9
     int sd;
     char *remote;
     int tcp;
     struct timeval tv_conn;
     char *version;
     int line;
e7fdfa9a
 } conn_t;
 
 struct global_stats {
288057e9
     struct task *tasks;
     ssize_t n;
     struct stats *all_stats;
     size_t num_clamd;
     conn_t *conn;
e7fdfa9a
 };
 
 struct stats {
288057e9
     const char *remote;
     char *engine_version;
     char *db_version;
     struct tm db_time;
     const char *version;
     int stats_unsupp;
     uint8_t conn_hr, conn_min, conn_sec;
     /* threads - primary */
     unsigned prim_live, prim_idle, prim_max;
     /* threads - sum */
     unsigned live, idle, max;
     /* queue */
     unsigned biggest_queue, current_q;
     double mem; /* in megabytes */
     unsigned long lheapu, lmmapu, ltotalu, ltotalf, lreleasable, lpoolu, lpoolt;
     unsigned pools_cnt;
e7fdfa9a
 };
 
 static void cleanup(void);
 static int send_string_noreconn(conn_t *conn, const char *cmd);
 static void send_string(conn_t *conn, const char *cmd);
e419cad6
 static int read_version(conn_t *conn);
6df13d04
 char *get_ip(const char *ip);
 char *get_port(const char *ip);
 char *make_ip(const char *host, const char *port);
e7fdfa9a
 
1615194d
 enum exit_reason {
288057e9
     FAIL_CMDLINE = 1,
     FAIL_INITIAL_CONN,
     OUT_OF_MEMORY,
     RECONNECT_FAIL,
     SIGINT_REASON
1615194d
 };
 
 static void exit_program(enum exit_reason reason, const char *func, unsigned line);
ebed1714
 #if __GNUC__ >= 3
1615194d
 #define EXIT_PROGRAM(r) exit_program(r, __PRETTY_FUNCTION__, __LINE__);
ebed1714
 #else
 #define EXIT_PROGRAM(r) exit_program(r, "<unknown>", __LINE__);
 #endif
288057e9
 #define OOM_CHECK(p)                         \
     do {                                     \
         if (!p) EXIT_PROGRAM(OUT_OF_MEMORY); \
     } while (0)
1615194d
 
e7fdfa9a
 static struct global_stats global;
288057e9
 static int curses_inited   = 1;
 static int maxystats       = 0;
29d6dd3f
 static int detail_selected = -1;
 
8db0a346
 static int detail_exists(void)
29d6dd3f
 {
288057e9
     return global.num_clamd != 1;
29d6dd3f
 }
 
 static int detail_is_selected(int idx)
 {
288057e9
     if (!detail_exists()) {
         assert(idx == 0);
         return 1;
     }
     return idx == detail_selected;
29d6dd3f
 }
 
233732d7
 /* ---------------------- NCurses routines -----------------*/
aa22174b
 enum colors {
288057e9
     header_color = 1,
     version_color,
     error_color,
     value_color,
     descr_color,
     selected_color,
     queue_header_color,
     activ_color,
     dim_color,
     red_color,
aa22174b
 };
 
 #define UPDATE_INTERVAL 2
 #define MIN_INTERVAL 1
 
 /* the default color of the terminal in ncurses */
 #define DEFAULT_COLOR -1
 
 #define VALUE_ATTR A_BOLD | COLOR_PAIR(value_color)
 #define DESCR_ATTR COLOR_PAIR(descr_color)
1615194d
 #define ERROR_ATTR A_BOLD | COLOR_PAIR(error_color)
aa22174b
 
288057e9
 static WINDOW *header_window     = NULL;
aa22174b
 static WINDOW *stats_head_window = NULL;
288057e9
 static WINDOW *stats_window      = NULL;
aa22174b
 static WINDOW *status_bar_window = NULL;
288057e9
 static WINDOW *mem_window        = NULL;
aa22174b
 
 static const char *status_bar_keys[10];
288057e9
 static unsigned maxy = 0, maxx = 0;
aa22174b
 static char *queue_header = NULL;
e7fdfa9a
 static char *clamd_header = NULL;
 
55885104
 #define CMDHEAD " COMMAND        QUEUEDSINCE   FILE"
 #define CMDHEAD2 " # COMMAND     QUEUEDSINCE   FILE"
e7fdfa9a
 
 /*
  * CLAMD - which local/remote clamd this is
  * CONNTIM - since when we are connected (TODO: zeroed at reconnect)
  * QUEUE   - no of items in queue (total)
  * MAXQUEUE - max no of items in queue observed
  * LIVETHR - sum of live threads
  * IDLETHR - sum of idle threads
  */
 #define SUMHEAD "NO CONNTIME LIV IDL QUEUE  MAXQ   MEM HOST           ENGINE DBVER DBTIME"
aa22174b
 
 static void resize(void)
 {
288057e9
     char *p;
     unsigned new_maxy, new_maxx;
     getmaxyx(stdscr, new_maxy, new_maxx);
     if (new_maxy == maxy && new_maxx == maxx)
         return;
     maxx = new_maxx;
     maxy = new_maxy;
     free(queue_header);
     free(clamd_header);
     queue_header = malloc(maxx + 1);
     OOM_CHECK(queue_header);
     clamd_header = malloc(maxx + 1);
     OOM_CHECK(clamd_header);
     assert(clamd_header && queue_header);
     strncpy(queue_header, global.num_clamd > 1 ? CMDHEAD2 : CMDHEAD, maxx);
     strncpy(clamd_header, SUMHEAD, maxx);
     queue_header[maxx] = '\0';
     clamd_header[maxx] = '\0';
     p                  = queue_header + strlen(queue_header);
     while (p < queue_header + maxx)
         *p++ = ' ';
     p = clamd_header + strlen(clamd_header);
     while (p < clamd_header + maxx)
         *p++ = ' ';
aa22174b
 }
 
 static void rm_windows(void)
 {
288057e9
     if (header_window) {
         delwin(header_window);
         header_window = NULL;
     }
     if (mem_window) {
         delwin(mem_window);
         mem_window = NULL;
     }
     if (stats_window) {
         delwin(stats_window);
         stats_window = NULL;
     }
     if (stats_head_window) {
         delwin(stats_head_window);
         stats_head_window = NULL;
     }
     if (status_bar_window) {
         delwin(status_bar_window);
         status_bar_window = NULL;
     }
aa22174b
 }
 
e7fdfa9a
 static void init_windows(int num_clamd)
aa22174b
 {
288057e9
     resize();
 
     rm_windows();
     /* non-overlapping windows */
     header_window     = subwin(stdscr, 1, maxx, 0, 0);
     stats_head_window = subwin(stdscr, num_clamd + 1, maxx, 1, 0);
     maxystats         = maxy - num_clamd - 3;
     stats_window      = subwin(stdscr, maxystats, maxx, num_clamd + 2, 0);
     status_bar_window = subwin(stdscr, 1, maxx, maxy - 1, 0);
     /* memwindow overlaps, used only in details mode */
     mem_window = derwin(stats_window, 6, 41, 1, maxx - 41);
     touchwin(stdscr);
     werase(stdscr);
     refresh();
     memset(status_bar_keys, 0, sizeof(status_bar_keys));
     status_bar_keys[0] = "H - help";
     status_bar_keys[1] = "Q - quit";
     status_bar_keys[2] = "R - reset maximums";
     if (num_clamd > 1) {
         status_bar_keys[3] = "^ - previous clamd";
         status_bar_keys[4] = "v - next clamd";
     }
aa22174b
 }
 
43ea5675
 static void init_ncurses(int num_clamd, int use_default)
aa22174b
 {
288057e9
     int default_bg = use_default ? DEFAULT_COLOR : COLOR_BLACK;
     int default_fg = use_default ? DEFAULT_COLOR : COLOR_WHITE;
     initscr();
     curses_inited = 1;
 
     start_color();
     keypad(stdscr, TRUE);            /* enable keyboard mapping */
     nonl();                          /* tell curses not to do NL->CR/NL on output */
     halfdelay(UPDATE_INTERVAL * 10); /* timeout of 2s when waiting for input*/
     noecho();                        /* dont echo input */
     curs_set(0);                     /* turn off cursor */
     if (use_default)
         use_default_colors();
 
     init_pair(header_color, COLOR_BLACK, COLOR_WHITE);
     init_pair(version_color, default_fg, default_bg);
     init_pair(error_color, COLOR_WHITE, COLOR_RED);
     init_pair(value_color, COLOR_GREEN, default_bg);
     init_pair(descr_color, COLOR_CYAN, default_bg);
     init_pair(selected_color, COLOR_BLACK, COLOR_CYAN);
     init_pair(queue_header_color, COLOR_BLACK, COLOR_GREEN);
     init_pair(activ_color, COLOR_MAGENTA, default_bg);
     init_pair(dim_color, COLOR_GREEN, default_bg);
     init_pair(red_color, COLOR_RED, default_bg);
 
     init_windows(num_clamd);
aa22174b
 }
 
 static void win_start(WINDOW *win, enum colors col)
 {
288057e9
     wattrset(win, COLOR_PAIR(col));
     wbkgd(win, COLOR_PAIR(col));
     werase(win);
aa22174b
 }
 
288057e9
 static void print_colored(WINDOW *win, const char *p)
aa22174b
 {
288057e9
     while (*p) {
         wattron(win, DESCR_ATTR);
         while (*p && !isdigit(*p))
             waddch(win, *p++);
         wattroff(win, DESCR_ATTR);
         wattron(win, VALUE_ATTR);
         while (*p && isdigit(*p))
             waddch(win, *p++);
         wattroff(win, VALUE_ATTR);
     }
aa22174b
 }
 
 static void header(void)
 {
288057e9
     size_t i, x = 0;
     time_t t;
aa22174b
 
288057e9
     win_start(header_window, header_color);
     mvwprintw(header_window, 0, 0, "  ClamdTOP version %s   ", get_version());
     time(&t);
     wprintw(header_window, "%s", ctime(&t));
     wrefresh(header_window);
e7fdfa9a
 
288057e9
     /*	win_start(version_window, version_color);
aa22174b
 	mvwprintw(version_window, 0, 0, "Connected to: ");
 	print_colored(version_window, clamd_version ? clamd_version : "Unknown");
e7fdfa9a
 	wrefresh(version_window);*/
aa22174b
 
288057e9
     werase(status_bar_window);
     for (i = 0; i < sizeof(status_bar_keys) / sizeof(status_bar_keys[0]); i++) {
         const char *s = status_bar_keys[i];
         if (!s)
             continue;
         wattron(status_bar_window, A_REVERSE);
         if (s[0] == '^') {
             mvwaddch(status_bar_window, 0, x, ACS_UARROW);
             s++;
             x++;
         } else if (s[0] == 'v') {
             mvwaddch(status_bar_window, 0, x, ACS_DARROW);
             s++;
             x++;
         }
         mvwprintw(status_bar_window, 0, x, "%s", s);
         wattroff(status_bar_window, A_REVERSE);
         x += strlen(status_bar_keys[i]) + 1;
     }
     wrefresh(status_bar_window);
aa22174b
 }
 
 static void show_bar(WINDOW *win, size_t i, unsigned live, unsigned idle,
288057e9
                      unsigned max, int blink)
aa22174b
 {
288057e9
     int y, x, z = 0;
     unsigned len   = 39;
     unsigned start = 1;
     unsigned activ = max ? ((live - idle) * (len - start - 2) + (max / 2)) / max : 0;
     unsigned dim   = max ? idle * (len - start - 2) / max : 0;
     unsigned rem   = len - activ - dim - start - 2;
 
     assert(activ + 2 < len && activ + dim + 2 < len && activ + dim + rem + 2 < len && "Invalid values");
     mvwaddch(win, i, start, '[' | A_BOLD);
     wattron(win, A_BOLD | COLOR_PAIR(activ_color));
     for (i = 0; i < activ; i++)
         waddch(win, '|');
     wattroff(win, A_BOLD | COLOR_PAIR(activ_color));
     wattron(win, A_DIM | COLOR_PAIR(dim_color));
     for (i = 0; i < dim; i++)
         waddch(win, '|');
     wattroff(win, A_DIM | COLOR_PAIR(dim_color));
     for (i = 0; i < rem; i++)
         waddch(win, ' ');
     waddch(win, ']' | A_BOLD);
     if (blink) {
         getyx(win, y, x);
         if ((x < 0) || (y < 0)) {
             return; /* if getyx() failed, nevermind the blinking */
         }
         if (x >= 2) {
             z = x - 2;
         }
         mvwaddch(win, y, z, '>' | A_BLINK | COLOR_PAIR(red_color));
         move(y, z);
     }
aa22174b
 }
 
e7fdfa9a
 /* --------------------- Error handling ---------------------*/
288057e9
 static int normal_exit         = 0;
e7fdfa9a
 static const char *exit_reason = NULL;
288057e9
 static const char *exit_func   = NULL;
 static unsigned exit_line      = 0;
e7fdfa9a
 
 static void cleanup(void)
 {
288057e9
     unsigned i;
     if (curses_inited) {
         if (status_bar_window) {
             werase(status_bar_window);
             wrefresh(status_bar_window);
         }
         rm_windows();
         endwin();
     }
     curses_inited = 0;
     for (i = 0; i < global.num_clamd; i++) {
         if (global.conn[i].sd && global.conn[i].sd != -1) {
             send_string_noreconn(&global.conn[i], "nEND\n");
             close(global.conn[i].sd);
         }
         free(global.conn[i].version);
         free(global.conn[i].remote);
     }
     free(global.all_stats);
     free(global.conn);
     free(queue_header);
     free(clamd_header);
     if (!normal_exit) {
         fprintf(stderr, "Abnormal program termination");
         if (exit_reason) fprintf(stderr, ": %s", exit_reason);
         if (exit_func) fprintf(stderr, " in %s", exit_func);
         if (exit_line) fprintf(stderr, " at line %u", exit_line);
         fputc('\n', stderr);
     }
e7fdfa9a
 }
 
879edca6
 #ifdef __GNUC__
 #define __noreturn __attribute__((noreturn))
 #else
 #define __noreturn
 #endif
e7fdfa9a
 
879edca6
 static void __noreturn exit_program(enum exit_reason reason, const char *func, unsigned line)
e7fdfa9a
 {
288057e9
     switch (reason) {
         case FAIL_CMDLINE:
             exit_reason = "Invalid command-line arguments";
             break;
         case FAIL_INITIAL_CONN:
             exit_reason = "Unable to connect to all clamds";
             break;
         case OUT_OF_MEMORY:
             exit_reason = "Out of memory";
             break;
         case RECONNECT_FAIL:
             exit_reason = "Failed to reconnect to clamd after connection was lost";
             break;
         case SIGINT_REASON:
             exit_reason = "User interrupt";
             break;
         default:
             exit_reason = "Unknown";
             break;
     }
     exit_func = func;
     exit_line = line;
     exit(reason);
e7fdfa9a
 }
 
aa22174b
 struct task {
288057e9
     char *line;
     double tim;
     int clamd_no;
aa22174b
 };
 
 static int tasks_compare(const void *a, const void *b)
 {
288057e9
     const struct task *ta = a;
     const struct task *tb = b;
     if (ta->tim < tb->tim)
         return 1;
     if (ta->tim > tb->tim)
         return -1;
     return 0;
aa22174b
 }
 
233732d7
 /* ----------- Socket routines ----------------------- */
8db0a346
 #ifdef __GNUC__
 static void print_con_info(conn_t *conn, const char *fmt, ...) __attribute__((format(printf, 2, 3)));
 #endif
1615194d
 static void print_con_info(conn_t *conn, const char *fmt, ...)
233732d7
 {
288057e9
     va_list ap;
     va_start(ap, fmt);
     if (stats_head_window) {
         char *buf = malloc(maxx);
         OOM_CHECK(buf);
         memset(buf, ' ', maxx);
         vsnprintf(buf, maxx - 1, fmt, ap);
         buf[strlen(buf)] = ' ';
         buf[maxx - 1]    = '\0';
         wattron(stats_head_window, ERROR_ATTR);
         mvwprintw(stats_head_window, conn->line, 0, "%s", buf);
         wattroff(stats_head_window, ERROR_ATTR);
         wrefresh(stats_head_window);
         free(buf);
     } else
         vfprintf(stdout, fmt, ap);
     va_end(ap);
233732d7
 }
 
0c81fc85
 char *get_ip(const char *ip)
 {
6df13d04
     char *dupip, *p1;
aad67456
     unsigned int i;
0c81fc85
 
     /*
      * Expected format of ip:
aad67456
      *     1) IPv4
      *     2) IPv4:Port
      *     3) IPv6
      *     4) [IPv6]:Port
0c81fc85
      * 
      * Use of IPv6:Port is incorrect. An IPv6 address must be enclosed in brackets.
      */
 
     dupip = strdup(ip);
     if (!(dupip))
         return NULL;
 
     if (dupip[0] == '[') {
         /* IPv6 */
         p1 = strchr(dupip, ']');
         if (!(p1)) {
             free(dupip);
             return NULL;
         }
 
         *p1 = '\0';
 
288057e9
         p1 = strdup(dupip + 1);
aad67456
         free(dupip);
         return p1;
0c81fc85
     }
 
aad67456
     p1 = dupip;
288057e9
     i  = 0;
99af390c
     while ((p1 = strchr(p1, ':'))) {
aad67456
         i++;
         p1++;
0c81fc85
     }
 
aad67456
     if (i == 0 || i > 1)
         return dupip;
0c81fc85
 
aad67456
     if (i == 1) {
288057e9
         p1  = strchr(dupip, ':');
aad67456
         *p1 = '\0';
         return dupip;
0c81fc85
     }
 
     return dupip;
 }
 
 char *get_port(const char *ip)
 {
     char *dupip, *p;
288057e9
     unsigned int offset = 0;
0c81fc85
 
     dupip = get_ip(ip);
     if (!(dupip))
         return NULL;
 
aad67456
     if (ip[0] == '[')
         offset += 2;
 
99af390c
     p = (char *)ip + strlen(dupip) + offset;
aad67456
     if (*p == ':') {
288057e9
         p = strdup(p + 1);
aad67456
         free(dupip);
         return p;
     }
0c81fc85
 
aad67456
     return NULL;
0c81fc85
 }
 
 char *make_ip(const char *host, const char *port)
 {
     char *ip;
     size_t len;
     int ipv6;
 
     len = strlen(host) + strlen(port);
 
     ipv6 = (strchr(host, ':') != NULL);
 
884814d6
     len += (ipv6 ? 4 : 3);
0c81fc85
 
     ip = calloc(1, len);
     if (!(ip))
         return NULL;
 
     snprintf(ip, len, "%s%s%s:%s", ipv6 ? "[" : "", host, ipv6 ? "]" : "", port);
 
     return ip;
 }
aa22174b
 
e419cad6
 static int make_connection_real(const char *soname, conn_t *conn)
233732d7
 {
413ffee7
     int s = -1;
33a3a884
     struct timeval tv;
288057e9
     char *port       = NULL;
     char *pt         = strdup(soname);
33a3a884
     const char *host = pt;
288057e9
     struct addrinfo hints, *res = NULL, *p;
0c81fc85
     int err;
33a3a884
 
637d2461
     OOM_CHECK(pt);
33a3a884
     conn->tcp = 0;
 
 #ifndef _WIN32
288057e9
     if (cli_is_abspath(soname) || (access(soname, F_OK) == 0)) {
33a3a884
         struct sockaddr_un addr;
 
         s = socket(AF_UNIX, SOCK_STREAM, 0);
288057e9
         if (s < 0) {
33a3a884
             perror("socket");
             return -1;
         }
 
         memset(&addr, 0, sizeof(addr));
         addr.sun_family = AF_UNIX;
         strncpy(addr.sun_path, soname, sizeof(addr.sun_path));
         addr.sun_path[sizeof(addr.sun_path) - 1] = 0x0;
 
         print_con_info(conn, "Connecting to: %s\n", soname);
         if (connect(s, (struct sockaddr *)&addr, sizeof(addr))) {
             perror("connect");
8c7ffc98
             close(s);
33a3a884
             return -1;
         }
 
         goto end;
     }
ce936887
 #endif
33a3a884
 
     memset(&hints, 0x00, sizeof(struct addrinfo));
288057e9
     hints.ai_family   = AF_UNSPEC;
33a3a884
     hints.ai_socktype = SOCK_STREAM;
288057e9
     hints.ai_flags    = AI_PASSIVE;
33a3a884
 
0c81fc85
     host = get_ip(soname);
     if (!(host))
         return -1;
 
     port = get_port(soname);
 
288057e9
     conn->tcp = 1;
33a3a884
 
0c81fc85
     print_con_info(conn, "Looking up: %s:%s\n", host, port ? port : "3310");
     if ((err = getaddrinfo(host, (port != NULL) ? port : "3310", &hints, &res))) {
         print_con_info(conn, "Could not look up %s:%s, getaddrinfo returned: %s\n", host, port ? port : "3310", gai_strerror(err));
33a3a884
         return -1;
0c81fc85
     }
33a3a884
 
     for (p = res; p != NULL; p = p->ai_next) {
         if ((s = socket(p->ai_family, p->ai_socktype, p->ai_protocol)) < 0) {
             perror("socket");
             continue;
         }
 
0c81fc85
         print_con_info(conn, "Connecting to: %s\n", soname);
33a3a884
         if (connect(s, p->ai_addr, p->ai_addrlen)) {
             perror("connect");
8c7ffc98
             close(s);
33a3a884
             continue;
         }
 
         break;
     }
 
     free(pt);
 
     if (res)
         freeaddrinfo(res);
 
     if (p == NULL)
         return -1;
 
 end:
     if (conn->remote != soname) {
         /* when we reconnect, they are the same */
         if (conn->remote)
             free(conn->remote);
 
0c81fc85
         conn->remote = make_ip(host, (port != NULL) ? port : "3310");
33a3a884
     }
aad67456
     if (port)
         free(port);
33a3a884
     conn->sd = s;
     gettimeofday(&conn->tv_conn, NULL);
288057e9
     tv.tv_sec  = 30;
33a3a884
     tv.tv_usec = 0;
     setsockopt(conn->sd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
     return 0;
233732d7
 }
 
e419cad6
 static int make_connection(const char *soname, conn_t *conn)
 {
     int rc;
 
     if ((rc = make_connection_real(soname, conn)))
33a3a884
         return rc;
 
e419cad6
     send_string(conn, "nIDSESSION\nnVERSION\n");
     free(conn->version);
     conn->version = NULL;
     if (!read_version(conn))
33a3a884
         return 0;
e419cad6
 
     /* clamd < 0.95 */
     if ((rc = make_connection_real(soname, conn)))
33a3a884
         return rc;
 
e419cad6
     send_string(conn, "nSESSION\nnVERSION\n");
     conn->version = NULL;
     if (!read_version(conn))
33a3a884
         return 0;
 
e419cad6
     return -1;
 }
 
e7fdfa9a
 static void reconnect(conn_t *conn);
233732d7
 
e7fdfa9a
 static int send_string_noreconn(conn_t *conn, const char *cmd)
233732d7
 {
288057e9
     assert(cmd);
     assert(conn && conn->sd > 0);
     return send(conn->sd, cmd, strlen(cmd), 0);
e7fdfa9a
 }
233732d7
 
e7fdfa9a
 static void send_string(conn_t *conn, const char *cmd)
 {
288057e9
     while (send_string_noreconn(conn, cmd) == -1) {
         reconnect(conn);
     }
233732d7
 }
 
e7fdfa9a
 static int tries = 0;
 static void reconnect(conn_t *conn)
 {
288057e9
     if (++tries > 3) {
         EXIT_PROGRAM(RECONNECT_FAIL);
     }
     if (conn->sd != -1)
         close(conn->sd);
     if (make_connection(conn->remote, conn) < 0) {
         print_con_info(conn, "Unable to reconnect to %s: %s", conn->remote, strerror(errno));
         EXIT_PROGRAM(RECONNECT_FAIL);
     }
     tries = 0;
e7fdfa9a
 }
 
233732d7
 static int recv_line(conn_t *conn, char *buf, size_t len)
 {
288057e9
     assert(len > 0);
     assert(conn);
     assert(buf);
 
     len--;
     if (!len || conn->sd == -1)
         return 0;
     assert(conn->sd > 0);
     while (len > 0) {
         ssize_t nread = recv(conn->sd, buf, len, MSG_PEEK);
         if (nread <= 0) {
             print_con_info(conn, "%s: %s", conn->remote, strerror(errno));
             /* it could be a timeout, be nice and send an END */
             send_string_noreconn(conn, "nEND\n");
             close(conn->sd);
             conn->sd = -1;
             return 0;
         } else {
             char *p = memchr(buf, '\n', nread);
             if (p) {
                 len = p - buf + 1;
             } else
                 len = nread;
             assert(len > 0);
             assert(len <= (size_t)nread);
             nread = recv(conn->sd, buf, len, 0);
             if (nread == -1)
                 reconnect(conn);
             else {
                 assert(nread > 0 && (size_t)nread == len);
                 buf += nread;
             }
             if (p)
                 break;
         }
     }
     *buf = '\0';
     return 1;
233732d7
 }
 
e7fdfa9a
 static void output_queue(size_t line, ssize_t max)
 {
288057e9
     ssize_t i, j;
     struct task *tasks          = global.tasks;
     struct task *filtered_tasks = calloc(global.n, sizeof(*filtered_tasks));
     OOM_CHECK(filtered_tasks);
     for (i = 0, j = 0; i < global.n; i++) {
         if (detail_selected == -1 || detail_is_selected(tasks[i].clamd_no - 1)) {
             filtered_tasks[j++] = tasks[i];
         }
     }
 
     wattron(stats_window, COLOR_PAIR(queue_header_color));
     mvwprintw(stats_window, line++, 0, "%s", queue_header);
     wattroff(stats_window, COLOR_PAIR(queue_header_color));
     if (max >= j)
         max = j;
     else
         --max;
     if (max < 0) max = 0;
     for (i = 0; i < max; i++) {
         char *cmde;
         assert(tasks);
         cmde = strchr(filtered_tasks[i].line, ' ');
         if (cmde) {
             char cmd[16];
             const char *filstart = strchr(cmde + 1, ' ');
             strncpy(cmd, filtered_tasks[i].line, sizeof(cmd) - 1);
             cmd[15] = '\0';
             if (filtered_tasks[i].line + 15 > cmde)
                 cmd[cmde - filtered_tasks[i].line] = '\0';
             if (filstart) {
                 ++filstart;
                 if (detail_selected == -1 && global.num_clamd > 1)
                     mvwprintw(stats_window, line + i, 0, "%2u %s", filtered_tasks[i].clamd_no, cmd + 1);
                 else
                     mvwprintw(stats_window, line + i, 0, " %s", cmd + 1);
                 mvwprintw(stats_window, line + i, 15, "%10.03fs", filtered_tasks[i].tim);
                 mvwprintw(stats_window, line + i, 30, "%s", filstart);
             }
         }
     }
     if (max < j) {
         /* in summary mode we can only show a max amount of tasks */
         wattron(stats_window, A_DIM | COLOR_PAIR(header_color));
         mvwprintw(stats_window, line + i, 0, "*** %u more task(s) not shown ***", (unsigned)(j - max));
         wattroff(stats_window, A_DIM | COLOR_PAIR(header_color));
     }
     free(filtered_tasks);
e7fdfa9a
 }
 
233732d7
 /* ---------------------- stats parsing routines ------------------- */
e7fdfa9a
 
288057e9
 static void parse_queue(conn_t *conn, char *buf, size_t len, unsigned idx)
aa22174b
 {
288057e9
     do {
         double tim;
         const char *t = strchr(buf, ' ');
         if (!t)
             continue;
         if (sscanf(t, "%lf", &tim) != 1)
             continue;
         ++global.n;
         global.tasks = realloc(global.tasks, sizeof(*global.tasks) * global.n);
         OOM_CHECK(global.tasks);
         global.tasks[global.n - 1].line = strdup(buf);
         OOM_CHECK(global.tasks[global.n - 1].line);
         global.tasks[global.n - 1].tim      = tim;
         global.tasks[global.n - 1].clamd_no = idx + 1;
     } while (recv_line(conn, buf, len) && buf[0] == '\t' && strcmp("END\n", buf) != 0);
aa22174b
 }
 
ca9cb072
 static unsigned biggest_mem = 0;
aa22174b
 
e7fdfa9a
 static void output_memstats(struct stats *stats)
aa22174b
 {
288057e9
     char buf[128];
     unsigned long totalmem;
     int blink = 0;
 
     werase(mem_window);
     if (stats->mem > 0 || (stats->mem >= 0 && (stats->lpoolt > 0))) {
         box(mem_window, 0, 0);
 
         if (stats->mem > 0)
             snprintf(buf, sizeof(buf), "heap %4luM mmap %4luM unused %3luM",
                      stats->lheapu / 1000, stats->lmmapu / 1000, stats->lreleasable / 1000);
         else
             snprintf(buf, sizeof(buf), "heap   N/A mmap   N/A unused  N/A");
         mvwprintw(mem_window, 1, 1, "Mem:  ");
         print_colored(mem_window, buf);
 
         mvwprintw(mem_window, 2, 1, "Libc: ");
         if (stats->mem > 0)
             snprintf(buf, sizeof(buf), "used %4luM free %4luM total %4luM",
                      stats->ltotalu / 1000, stats->ltotalf / 1000, (stats->ltotalu + stats->ltotalf) / 1000);
         else
             snprintf(buf, sizeof(buf), "used   N/A free   N/A total   N/A");
         print_colored(mem_window, buf);
 
         mvwprintw(mem_window, 3, 1, "Pool: ");
         snprintf(buf, sizeof(buf), "count %4u used %4luM total %4luM",
                  stats->pools_cnt, stats->lpoolu / 1000, stats->lpoolt / 1000);
         print_colored(mem_window, buf);
 
         totalmem = stats->lheapu + stats->lmmapu + stats->lpoolt;
         if (totalmem > biggest_mem) {
             biggest_mem = totalmem;
             blink       = 1;
         }
         show_bar(mem_window, 4, totalmem, stats->lmmapu + stats->lreleasable + stats->lpoolt - stats->lpoolu,
                  biggest_mem, blink);
     }
     wrefresh(mem_window);
aa22174b
 }
 
e7fdfa9a
 static void parse_memstats(const char *line, struct stats *stats)
 {
288057e9
     double heapu, mmapu, totalu, totalf, releasable, pools_used, pools_total;
 
     if (sscanf(line, " heap %lfM mmap %lfM used %lfM free %lfM releasable %lfM pools %u pools_used %lfM pools_total %lfM",
                &heapu, &mmapu, &totalu, &totalf, &releasable, &stats->pools_cnt, &pools_used, &pools_total) != 8) {
         if (sscanf(line, " heap N/A mmap N/A used N/A free N/A releasable N/A pools %u pools_used %lfM pools_total %lfM",
                    &stats->pools_cnt, &pools_used, &pools_total) != 3) {
             stats->mem = -1;
             return;
         }
         stats->lpoolu = pools_used * 1000;
         stats->lpoolt = pools_total * 1000;
         stats->mem    = 0;
         return;
     }
     stats->lheapu      = heapu * 1000;
     stats->lmmapu      = mmapu * 1000;
     stats->ltotalu     = totalu * 1000;
     stats->ltotalf     = totalf * 1000;
     stats->lreleasable = releasable * 1000;
     stats->lpoolu      = pools_used * 1000;
     stats->lpoolt      = pools_total * 1000;
     stats->mem         = heapu + mmapu + pools_total;
e7fdfa9a
 }
aa22174b
 
e7fdfa9a
 static int output_stats(struct stats *stats, unsigned idx)
 {
288057e9
     char buf[128];
     char timbuf[15];
     int blink = 0;
     size_t i  = 0;
     char mem[6];
     WINDOW *win = stats_head_window;
     int sel     = detail_is_selected(idx);
     char *line  = malloc(maxx + 1);
 
     OOM_CHECK(line);
 
     if (stats->mem <= 0 || stats->stats_unsupp) {
         strncpy(mem, "N/A", sizeof(mem));
         mem[sizeof(mem) - 1] = '\0';
     } else {
         char c;
         double s;
         if (stats->mem > 999.0) {
             c = 'G';
             s = stats->mem / 1024.0;
         } else {
             c = 'M';
             s = stats->mem;
         }
         snprintf(mem, sizeof(mem), "%7.3f", s);
         i = 4;
         if (mem[i - 1] == '.') i--;
         mem[i++] = c;
         mem[i]   = '\0';
     }
     i = idx + 1;
 
     if (!stats->db_time.tm_year) {
         strncpy(timbuf, "N/A", sizeof(timbuf));
         timbuf[sizeof(timbuf) - 1] = '\0';
     } else
         snprintf(timbuf, sizeof(timbuf), "%04u-%02u-%02u %02uh",
                  1900 + stats->db_time.tm_year,
                  stats->db_time.tm_mon + 1,
                  stats->db_time.tm_mday,
                  stats->db_time.tm_hour);
 
     memset(line, ' ', maxx + 1);
     if (!stats->stats_unsupp) {
         snprintf(line, maxx - 1, "%2u %02u:%02u:%02u %3u %3u %5u %5u %5s %-14s %-6s %5s %s", idx + 1, stats->conn_hr, stats->conn_min, stats->conn_sec,
                  stats->live, stats->idle,
                  stats->current_q, stats->biggest_queue,
                  mem,
                  stats->remote, stats->engine_version, stats->db_version, timbuf);
     } else {
         snprintf(line, maxx - 1, "%2u %02u:%02u:%02u N/A N/A   N/A   N/A   N/A %-14s %-6s %5s %s", idx + 1, stats->conn_hr, stats->conn_min, stats->conn_sec,
                  stats->remote, stats->engine_version, stats->db_version, timbuf);
     }
     line[maxx]         = '\0';
     line[strlen(line)] = ' ';
     if (sel) {
         wattron(win, COLOR_PAIR(selected_color));
     }
     mvwprintw(win, i, 0, "%s", line);
     if (sel) {
         wattroff(win, COLOR_PAIR(selected_color));
     }
     win = stats_window;
     i   = 0;
     if (sel && !stats->stats_unsupp) {
         memset(line, ' ', maxx + 1);
         snprintf(line, maxx - 1, "Details for Clamd version: %s", stats->version);
         line[maxx]         = '\0';
         line[strlen(line)] = ' ';
         wattron(win, COLOR_PAIR(queue_header_color));
         mvwprintw(win, i++, 0, "%s", line);
         wattroff(win, COLOR_PAIR(queue_header_color));
         mvwprintw(win, i++, 0, "Primary threads: ");
         snprintf(buf, sizeof(buf), "live %3u idle %3u max %3u", stats->prim_live, stats->prim_idle, stats->prim_max);
         print_colored(win, buf);
         show_bar(win, i++, stats->prim_live, stats->prim_idle, stats->prim_max, 0);
         /*		mvwprintw(win, i++, 0, "Multiscan pool : ");
e7fdfa9a
 		snprintf(buf, sizeof(buf), "live %3u idle %3u max %3u", stats->live, stats->idle, stats->max);
 		print_colored(win, buf);
8db0a346
 		show_bar(win, i++, stats->live, stats->idle, stats->max, 0);*/
e7fdfa9a
 
288057e9
         blink = 0;
         if (stats->current_q > stats->biggest_queue) {
             stats->biggest_queue = stats->current_q;
             blink                = 1;
         }
         mvwprintw(win, i++, 0, "Queue:");
         snprintf(buf, sizeof(buf), "%6u items %6u max", stats->current_q, stats->biggest_queue);
         print_colored(win, buf);
         show_bar(win, i++, stats->current_q, 0, stats->biggest_queue, blink);
         i += 2;
         werase(mem_window);
         output_memstats(stats);
     }
     free(line);
     return i;
e7fdfa9a
 }
 
 static void output_all(void)
 {
288057e9
     unsigned i, stats_line = 0;
     werase(stats_head_window);
     werase(stats_window);
     wattron(stats_head_window, COLOR_PAIR(queue_header_color));
     mvwprintw(stats_head_window, 0, 0, "%s", clamd_header);
     wattroff(stats_head_window, COLOR_PAIR(queue_header_color));
     for (i = 0; i < global.num_clamd; i++) {
         unsigned j = output_stats(&global.all_stats[i], i);
         if (j > stats_line)
             stats_line = j;
     }
     output_queue(stats_line, maxystats - stats_line - 1);
     wrefresh(stats_head_window);
     wrefresh(stats_window);
     if (detail_exists()) {
         /* overlaps, must be done at the end */
         wrefresh(mem_window);
     }
e7fdfa9a
 }
 
 static void parse_stats(conn_t *conn, struct stats *stats, unsigned idx)
 {
288057e9
     char buf[1025];
     size_t j;
     struct timeval tv;
     unsigned conn_dt;
     int primary = 0;
     const char *pstart, *p, *vstart;
 
     if (conn->tcp)
         stats->remote = conn->remote;
     else
         stats->remote = "local";
 
     if (!conn->version) {
         stats->engine_version = strdup("???");
         OOM_CHECK(stats->engine_version);
         return;
     }
     p = pstart = vstart = strchr(conn->version, ' ');
     if (!vstart) {
         stats->engine_version = strdup("???");
         OOM_CHECK(stats->engine_version);
         return;
     }
     /* find digit in version */
     while (*p && !isdigit(*p))
         p++;
     /* rewind to first space or dash */
     while (p > pstart && *p && *p != ' ' && *p != '-')
         p--;
     if (*p) p++;
     /* keep only base version, and cut -exp, and -gittags */
     pstart = p;
     while (*p && *p != '-' && *p != '/')
         p++;
 
     stats->engine_version = malloc(p - pstart + 1);
     OOM_CHECK(stats->engine_version);
 
     memcpy(stats->engine_version, pstart, p - pstart);
     stats->engine_version[p - pstart] = '\0';
 
     pstart = strchr(p, '/');
     if (!pstart) {
         stats->db_version = strdup("????");
         OOM_CHECK(stats->db_version);
     } else {
         pstart++;
         p = strchr(pstart, '/');
         if (!p)
             p = pstart + strlen(pstart);
         stats->db_version = malloc(p - pstart + 1);
         OOM_CHECK(stats->db_version);
         memcpy(stats->db_version, pstart, p - pstart);
         stats->db_version[p - pstart] = '\0';
         if (*p) p++;
         if (!*p || !strptime(p, "%a %b  %d %H:%M:%S %Y", &stats->db_time)) {
             memset(&stats->db_time, 0, sizeof(stats->db_time));
         }
     }
     if (maxx > 61 && strlen(stats->db_version) > (maxx - 61)) {
         stats->db_version[maxx - 61] = '\0';
     }
 
     stats->version = vstart; /* for details view */
     gettimeofday(&tv, NULL);
     tv.tv_sec -= conn->tv_conn.tv_sec;
     tv.tv_usec -= conn->tv_conn.tv_usec;
     conn_dt = tv.tv_sec + tv.tv_usec / 1e6;
 
     stats->live = stats->idle = stats->max = 0;
     stats->conn_hr                         = conn_dt / 3600;
     stats->conn_min                        = (conn_dt / 60) % 60;
     stats->conn_sec                        = conn_dt % 60;
     stats->current_q                       = 0;
     buf[sizeof(buf) - 1]                   = 0x0;
     while (recv_line(conn, buf, sizeof(buf) - 1) && strcmp("END\n", buf) != 0) {
         char *val = strchr(buf, ':');
 
         if (buf[0] == '\t') {
             parse_queue(conn, buf, sizeof(buf) - 1, idx);
             continue;
         } else if (val)
             *val++ = '\0';
         if (!strcmp("MEMSTATS", buf)) {
             parse_memstats(val, stats);
             continue;
         }
         if (!strncmp("UNKNOWN COMMAND", buf, 15)) {
             stats->stats_unsupp = 1;
             break;
         }
         for (j = 1; j < strlen(buf); j++)
             buf[j] = tolower(buf[j]);
         /*	mvwprintw(win, i, 0, "%s", buf);
aa22174b
 		if(!val) {
 			i++;
 			continue;
 		}
 		waddch(win, ':');
 		print_colored(win, val);
e7fdfa9a
 		i++;*/
288057e9
         if (!strncmp("State", buf, 5)) {
             if (strstr(val, "PRIMARY")) {
                 /* primary thread pool */
                 primary = 1;
             } else {
                 /* multiscan pool */
                 primary = 0;
             }
         }
         if (!strcmp("Threads", buf)) {
             unsigned live, idle, max;
             if (sscanf(val, " live %u idle %u max %u", &live, &idle, &max) != 3)
                 continue;
             if (primary) {
                 stats->prim_live = live;
                 stats->prim_idle = idle;
                 assert(!stats->prim_max && "There can be only one primary pool!");
                 stats->prim_max = max;
             }
             stats->live += live;
             stats->idle += idle;
             stats->max += max;
         } else if (!strcmp("Queue", buf)) {
             unsigned len;
             if (sscanf(val, "%u", &len) != 1)
                 continue;
             stats->current_q += len;
         }
     }
aa22174b
 }
 
e419cad6
 static int read_version(conn_t *conn)
aa22174b
 {
288057e9
     char buf[1024];
     unsigned i;
     if (!recv_line(conn, buf, sizeof(buf)))
         return -1;
     if (!strcmp(buf, "UNKNOWN COMMAND\n"))
         return -2;
 
     conn->version = strdup(buf);
     OOM_CHECK(conn->version);
     for (i = 0; i < strlen(conn->version); i++)
         if (conn->version[i] == '\n')
             conn->version[i] = ' ';
     return 0;
aa22174b
 }
 
1615194d
 static void sigint(int a)
 {
6df13d04
     UNUSEDPARAM(a);
288057e9
     EXIT_PROGRAM(SIGINT_REASON);
1615194d
 }
 
8db0a346
 static void help(void)
 {
     printf("\n");
e098cdc5
     printf("                       Clam AntiVirus: Monitoring Tool %s\n", get_version());
964a1e73
     printf("           By The ClamAV Team: https://www.clamav.net/about.html#credits\n");
e1cbc270
     printf("           (C) 2019 Cisco Systems, Inc.\n");
e098cdc5
     printf("\n");
     printf("    clamdtop [-hVc] [host[:port] /path/to/clamd.socket ...]\n");
     printf("\n");
     printf("    --help                 -h         Show this help\n");
8db0a346
     printf("    --version              -V         Show version\n");
     printf("    --config-file=FILE     -c FILE    Read clamd's configuration files from FILE\n");
e098cdc5
     printf("    --defaultcolors	       -d         Use default terminal colors\n");
     printf("    host[:port]                       Connect to clamd on host at port (default 3310)\n");
     printf("    /path/to/clamd.socket             Connect to clamd over a local socket\n");
8db0a346
     printf("\n");
     return;
 }
288057e9
 static int default_colors = 0;
e7fdfa9a
 /* -------------------------- Initialization ---------------- */
 static void setup_connections(int argc, char *argv[])
aa22174b
 {
33a3a884
     struct optstruct *opts;
     struct optstruct *clamd_opts;
     unsigned i;
     char *conn = NULL;
 
     opts = optparse(NULL, argc, argv, 1, OPT_CLAMDTOP, 0, NULL);
     if (!opts) {
         fprintf(stderr, "ERROR: Can't parse command line options\n");
         EXIT_PROGRAM(FAIL_CMDLINE);
     }
 
288057e9
     if (optget(opts, "help")->enabled) {
33a3a884
         optfree(opts);
         help();
         normal_exit = 1;
         exit(0);
     }
 
288057e9
     if (optget(opts, "version")->enabled) {
33a3a884
         printf("Clam AntiVirus Monitoring Tool %s\n", get_version());
         optfree(opts);
         normal_exit = 1;
         exit(0);
     }
 
288057e9
     if (optget(opts, "defaultcolors")->enabled)
33a3a884
         default_colors = 1;
 
     memset(&global, 0, sizeof(global));
     if (!opts->filename || !opts->filename[0]) {
         const struct optstruct *opt;
         const char *clamd_conf = optget(opts, "config-file")->strarg;
 
         if ((clamd_opts = optparse(clamd_conf, 0, NULL, 1, OPT_CLAMD, 0, NULL)) == NULL) {
             fprintf(stderr, "Can't parse clamd configuration file %s\n", clamd_conf);
             EXIT_PROGRAM(FAIL_CMDLINE);
         }
 
288057e9
         if ((opt = optget(clamd_opts, "LocalSocket"))->enabled) {
33a3a884
             conn = strdup(opt->strarg);
             if (!conn) {
                 fprintf(stderr, "Can't strdup LocalSocket value\n");
                 EXIT_PROGRAM(FAIL_INITIAL_CONN);
             }
         } else if ((opt = optget(clamd_opts, "TCPSocket"))->enabled) {
             char buf[512];
             const struct optstruct *opt_addr;
             const char *host = "localhost";
             if ((opt_addr = optget(clamd_opts, "TCPAddr"))->enabled) {
                 host = opt_addr->strarg;
             }
0c81fc85
             snprintf(buf, sizeof(buf), "%lld", opt->numarg);
             conn = make_ip(host, buf);
33a3a884
         } else {
             fprintf(stderr, "Can't find how to connect to clamd\n");
             EXIT_PROGRAM(FAIL_INITIAL_CONN);
         }
 
         optfree(clamd_opts);
         global.num_clamd = 1;
     } else {
         unsigned i = 0;
288057e9
         while (opts->filename[i]) {
             i++;
         }
33a3a884
         global.num_clamd = i;
     }
8db0a346
 
ce936887
 #ifdef _WIN32
33a3a884
     WSADATA wsaData;
288057e9
     if (WSAStartup(MAKEWORD(2, 2), &wsaData) != NO_ERROR) {
33a3a884
         fprintf(stderr, "Error at WSAStartup(): %d\n", WSAGetLastError());
         EXIT_PROGRAM(FAIL_INITIAL_CONN);
     }
e7fdfa9a
 #endif
33a3a884
     /* clamdtop */
288057e9
     puts("        __                    ____");
33a3a884
     puts("  _____/ /___ _____ ___  ____/ / /_____  ____");
     puts(" / ___/ / __ `/ __ `__ \\/ __  / __/ __ \\/ __ \\");
     puts("/ /__/ / /_/ / / / / / / /_/ / /_/ /_/ / /_/ /");
     puts("\\___/_/\\__,_/_/ /_/ /_/\\__,_/\\__/\\____/ .___/");
     puts("                                     /_/");
 
     global.all_stats = calloc(global.num_clamd, sizeof(*global.all_stats));
     OOM_CHECK(global.all_stats);
     global.conn = calloc(global.num_clamd, sizeof(*global.conn));
     OOM_CHECK(global.conn);
 
288057e9
     for (i = 0; i < global.num_clamd; i++) {
         const char *soname  = conn ? conn : opts->filename[i];
         global.conn[i].line = i + 1;
33a3a884
         if (make_connection(soname, &global.conn[i]) < 0) {
             EXIT_PROGRAM(FAIL_INITIAL_CONN);
         }
     }
 
     optfree(opts);
     free(conn);
e7fdfa9a
 #ifndef _WIN32
33a3a884
     signal(SIGPIPE, SIG_IGN);
     signal(SIGINT, sigint);
ce936887
 #endif
e7fdfa9a
 }
 
 static void free_global_stats(void)
 {
288057e9
     unsigned i;
     for (i = 0; i < (unsigned)global.n; i++) {
         free(global.tasks[i].line);
     }
     for (i = 0; i < global.num_clamd; i++) {
         free(global.all_stats[i].engine_version);
         free(global.all_stats[i].db_version);
     }
     free(global.tasks);
     global.tasks = NULL;
     global.n     = 0;
e7fdfa9a
 }
 
55885104
 static int help_line;
 static void explain(const char *abbrev, const char *msg)
 {
288057e9
     wattron(stdscr, A_BOLD);
     mvwprintw(stdscr, help_line++, 0, "%-15s", abbrev);
     wattroff(stdscr, A_BOLD);
     wprintw(stdscr, "  %s", msg);
55885104
 }
 
 static int show_help(void)
 {
288057e9
     int ch;
     werase(stdscr);
     help_line = 0;
 
     explain("NO", "Unique clamd number");
     explain("CONNTIME", "How long it is connected");
     explain("LIV", "Total number of live threads");
     explain("IDL", "Total number of idle threads");
     explain("QUEUE", "Number of items in queue");
     explain("MAXQ", "Maximum number of items observed in queue");
     explain("MEM", "Total memory usage (if available)");
     explain("HOST", "Which clamd, local means unix socket");
     explain("ENGINE", "Engine version");
     explain("DBVER", "Database version");
     explain("DBTIME", "Database publish time");
     explain("Primary threads", "Threadpool used to receive commands");
     explain("Multiscan pool", "Threadpool used for multiscan");
     explain("live", "Executing commands, or scanning");
     explain("idle", "Waiting for commands, will exit after idle_timeout");
     explain("max", "Maximum number of threads configured for this pool");
     explain("Queue", "Tasks queued for processing, but not yet picked up by a thread");
     explain("COMMAND", "Command this thread is executing");
     explain("QUEUEDSINCE", "How long this task is executing");
     explain("FILE", "Which file it is processing (if applicable)");
     explain("Mem", "Memory usage reported by libc");
     explain("Libc", "Used/free memory reported by libc");
     explain("Pool", "Memory usage reported by libclamav's pool");
 
     wrefresh(stdscr);
     werase(status_bar_window);
     wattron(status_bar_window, A_REVERSE);
     mvwprintw(status_bar_window, 0, 0, "Press any key to exit help");
     wattroff(status_bar_window, A_REVERSE);
     wrefresh(status_bar_window);
     /* getch() times out after a few seconds */
     do {
         ch = getch();
         /* we do need to exit on resize, because the text scroll out of
55885104
 		 * view */
288057e9
     } while (ch == -1 /*|| ch == KEY_RESIZE*/);
     return ch == KEY_RESIZE ? KEY_RESIZE : -1;
55885104
 }
 
e7fdfa9a
 int main(int argc, char *argv[])
 {
288057e9
     int ch = 0;
     struct timeval tv_last, tv;
     unsigned i;
 
     atexit(cleanup);
     setup_connections(argc, argv);
     init_ncurses(global.num_clamd, default_colors);
 
     memset(&tv_last, 0, sizeof(tv_last));
     do {
         if (toupper(ch) == 'H') {
             ch = show_help();
         }
         switch (ch) {
             case KEY_RESIZE:
                 resize();
                 endwin();
                 refresh();
                 init_windows(global.num_clamd);
                 break;
             case 'R':
             case 'r':
                 for (i = 0; i < global.num_clamd; i++)
                     global.all_stats[i].biggest_queue = 1;
                 biggest_mem = 0;
                 break;
             case KEY_UP:
                 if (global.num_clamd > 1) {
                     if (detail_selected == -1)
                         detail_selected = global.num_clamd - 1;
                     else
                         --detail_selected;
                 }
                 break;
             case KEY_DOWN:
                 if (global.num_clamd > 1) {
                     if (detail_selected == -1)
                         detail_selected = 0;
                     else {
                         if ((unsigned)++detail_selected >= global.num_clamd)
                             detail_selected = -1;
                     }
                 }
                 break;
         }
         gettimeofday(&tv, NULL);
         header();
         if (tv.tv_sec - tv_last.tv_sec >= MIN_INTERVAL) {
             free_global_stats();
             for (i = 0; i < global.num_clamd; i++) {
                 unsigned biggest_q;
                 struct stats *stats = &global.all_stats[i];
                 if (global.conn[i].sd != -1)
                     send_string(&global.conn[i], "nSTATS\n");
                 biggest_q = stats->biggest_queue;
                 memset(stats, 0, sizeof(*stats));
                 stats->biggest_queue = biggest_q;
                 parse_stats(&global.conn[i], stats, i);
             }
             if (global.tasks)
                 qsort(global.tasks, global.n, sizeof(*global.tasks), tasks_compare);
             tv_last = tv;
         }
         /* always show, so that screen resizes take effect instantly*/
         output_all();
         for (i = 0; i < global.num_clamd; i++) {
             if (global.conn[i].sd == -1)
                 reconnect(&global.conn[i]);
         }
     } while (toupper(ch = getch()) != 'Q');
     free_global_stats();
     normal_exit = 1;
     return 0;
aa22174b
 }