clamav/clamdtop/clamdtop.c
Val S. aadf25df6a
Fix static analysis code quality issues (#1582)
`libclamav/libmspack.c`: Initialize variables before first `goto done;`
to fix unitialized variable use in an error condition.

`libclamav/others.c`: Explicitly ignore return values for calls to add
JSON values when subsequent calls don't depend on them.
If we were to add error handling here, the only thing we'd do is debug-
log it. I don't think it's worth adding the extra lines of code.

`libclamav/unarj.c`: Removed dead code.
The `status` variable is immediately set afterwards based on whether or
not any files may be extracted.

`libclamav/unzip.c`: Removed dead code.
The `ret` variable is checked immediately after being set, above. This
check after the `do`-`while()` loop is dead code.

`sigtool/sigtool.c`: Fix potential NULL deref in error handling.
This is a fix for the same issue as was fixed in a previous commit.
I somehow overlooked this one. Copy/paste bug.

`libclamav/pdfdecode.c`: Fix leaked `stream` memory when
`filter_lzwdecode()` fails.

`clamdtop/clamdtop.c`: Fix possible NULL dereference if `strchr` returns
NULL in `read_version()` and `check_stats_available()`.

`libclamav/rtf.c`: Fix memory leak in `rtf_object_process()` if
`cli_gentemp_with_prefix()` fails.
Also change empty for-loop to resolve clang-format weirdness and make it
more obvious the for-loop has no body.

`libclamav/aspack.c`: Ensure that `endoff - old` is not negative in
`build_decrypt_array()` before passing to `CLI_ISCONTAINED()` which expects
unsigned values.

`libclamav/upx.c`: Fix integer overflow checks in multiple functions.

`libclamav/vba_extract.c`: Set `entries` pointer back to NULL after free in
`word_read_macro_entry()` error condition.

`libclamav/unzip.c`: Remove logic to return `CL_EMAXFILES` from
`index_local_file_headers()`. It seems it only overwrote the status when
not `CL_SUCCESS` in which case it could be overriding a more serious failure.
Further, updates to the how the ZIP parser works has made it so this needs
to return `CL_SUCCESS` in order for the caller to at least scan the files
found so far.
Finally, the calling function has checks of its own to make sure we don't
exceeds the max-files limit.

`libclamav/unzip.c`: Fix issue where `cli_append_potentially_unwanted()` in
`index_local_file_headers()` might overwrite an error in `status` with
`CL_CLEAN`. Instead, it now checks the return value and only overwrites the
`CL_EFORMAT` status with a different value if not `CL_SUCCESS`.

`libclamav/unzip.c`: Fix a potential leak with `combined_catalogue` and
`temp_catalogue` in an error condition. We should always free them if not NULL,
not just if the function failed. And to make this safe, we must set
`combined_catalogue` to NULL when we give ownership to `*catalogue`.

`libclamav/scanners.c`: Fix a potential leak in error handling for the
`cli_ole2_tempdir_scan_vba()` function.

CLAM-2768
2025-10-02 11:46:14 -04:00

1667 lines
48 KiB
C

/*
* ClamdTOP
*
* Copyright (C) 2013-2025 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
* Copyright (C) 2008-2013 Sourcefire, Inc.
*
* 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.
*/
#define _GNU_SOURCE
#define __EXTENSIONS
#define GCC_PRINTF
#define GCC_SCANF
#ifdef HAVE_CONFIG_H
#include "clamav-config.h"
#endif
#ifdef HAVE_STDINT_H
#include <stdint.h>
#endif
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include CURSES_INCLUDE
#include <time.h>
#include <ctype.h>
#include <signal.h>
#ifdef _WIN32
#include <windows.h>
#include <winsock2.h>
/* this is not correct, perhaps winsock errors are not mapped on errno */
#define herror perror
#else
#include <netdb.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <sys/time.h>
#endif
#include <assert.h>
#include <errno.h>
#include "platform.h"
// libclamav
#include "clamav.h"
// common
#include "optparser.h"
#include "misc.h"
/* Types, prototypes and globals*/
typedef struct connection {
int sd;
char *remote;
int tcp;
struct timeval tv_conn;
char *version;
int line;
} conn_t;
struct global_stats {
struct task *tasks;
ssize_t n;
struct stats *all_stats;
size_t num_clamd;
conn_t *conn;
};
struct stats {
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 */
double heapu, mmapu, totalu, totalf, releasable, pools_used, pools_total;
unsigned pools_cnt;
};
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);
static int read_version(conn_t *conn);
static int check_stats_available(conn_t *conn);
char *get_ip(const char *ip);
char *get_port(const char *ip);
char *make_ip(const char *host, const char *port);
enum exit_reason {
FAIL_CMDLINE = 1,
FAIL_INITIAL_CONN,
OUT_OF_MEMORY,
RECONNECT_FAIL,
SIGINT_REASON
};
static void exit_program(enum exit_reason reason, const char *func, unsigned line);
#if __GNUC__ >= 3
#define EXIT_PROGRAM(r) exit_program(r, __PRETTY_FUNCTION__, __LINE__);
#else
#define EXIT_PROGRAM(r) exit_program(r, "<unknown>", __LINE__);
#endif
#define OOM_CHECK(p) \
do { \
if (!p) EXIT_PROGRAM(OUT_OF_MEMORY); \
} while (0)
static struct global_stats global;
static SCREEN *curses_scr = NULL;
static int curses_inited = 0;
static int maxystats = 0;
static int detail_selected = -1;
static int detail_exists(void)
{
return global.num_clamd != 1;
}
static int detail_is_selected(int idx)
{
if (!detail_exists()) {
assert(idx == 0);
return 1;
}
return idx == detail_selected;
}
/* ---------------------- NCurses routines -----------------*/
enum colors {
header_color = 1,
version_color,
error_color,
value_color,
descr_color,
selected_color,
queue_header_color,
activ_color,
dim_color,
red_color,
};
#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)
#define ERROR_ATTR A_BOLD | COLOR_PAIR(error_color)
static WINDOW *header_window = NULL;
static WINDOW *stats_head_window = NULL;
static WINDOW *stats_window = NULL;
static WINDOW *status_bar_window = NULL;
static WINDOW *mem_window = NULL;
static char *status_bar_keys[10];
static unsigned maxy = 0, maxx = 0;
static char *queue_header = NULL;
static char *multi_queue_header = NULL;
static char *clamd_header = NULL;
#define CMDHEAD " COMMAND QUEUEDSINCE FILE"
#define CMDHEAD2 "NO COMMAND QUEUEDSINCE FILE"
/*
* 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 ENGINE DBVER DBTIME HOST"
static void resize(void)
{
char *p;
int new_maxy, new_maxx;
getmaxyx(stdscr, new_maxy, new_maxx);
if (new_maxy == -1 || new_maxx == -1) {
fprintf(stderr, "Failed to get terminal size\n");
return;
}
if ((unsigned int)new_maxy == maxy && (unsigned int)new_maxx == maxx) {
// no change
return;
}
maxx = (unsigned int)new_maxx;
maxy = (unsigned int)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, 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++ = ' ';
if (global.num_clamd > 1) {
free(multi_queue_header);
multi_queue_header = malloc(maxx + 1);
OOM_CHECK(multi_queue_header);
assert(multi_queue_header);
strncpy(multi_queue_header, CMDHEAD2, maxx);
multi_queue_header[maxx] = '\0';
p = multi_queue_header + strlen(multi_queue_header);
while (p < multi_queue_header + maxx)
*p++ = ' ';
}
}
static void rm_windows(void)
{
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;
}
}
static void init_windows(int num_clamd)
{
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";
}
}
static void init_ncurses(int num_clamd, int use_default)
{
int default_bg = use_default ? DEFAULT_COLOR : COLOR_BLACK;
int default_fg = use_default ? DEFAULT_COLOR : COLOR_WHITE;
/* newterm() allows us to free curses-allocated memory with delscreen() */
if (!(curses_scr = newterm(NULL, stdout, stdin))) {
fprintf(stderr, "Failed to initialize curses\n");
exit(EXIT_FAILURE);
}
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(); /* don't 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);
}
static void win_start(WINDOW *win, enum colors col)
{
wattrset(win, COLOR_PAIR(col));
wbkgd(win, COLOR_PAIR(col));
werase(win);
}
static void print_colored(WINDOW *win, const char *p)
{
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);
}
}
static void header(void)
{
size_t i, x = 0;
time_t t;
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);
/*
win_start(version_window, version_color);
mvwprintw(version_window, 0, 0, "Connected to: ");
print_colored(version_window, clamd_version ? clamd_version : "Unknown");
wrefresh(version_window);
*/
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(s) + 1;
}
wrefresh(status_bar_window);
}
static void show_bar(WINDOW *win, size_t i, unsigned live, unsigned idle,
unsigned max, int blink)
{
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);
}
}
/* --------------------- Error handling ---------------------*/
static int normal_exit = 0;
static const char *exit_reason = NULL;
static const char *exit_func = NULL;
static unsigned exit_line = 0;
static void cleanup(void)
{
unsigned i;
if (curses_inited) {
if (status_bar_window) {
werase(status_bar_window);
wrefresh(status_bar_window);
}
rm_windows();
endwin();
delscreen(curses_scr);
}
curses_inited = 0;
for (i = 0; i < global.num_clamd; i++) {
if (global.conn[i].sd && global.conn[i].sd != -1) {
(void)send_string_noreconn(&global.conn[i], "nEND\n");
#ifndef _WIN32
close(global.conn[i].sd);
#else
closesocket(global.conn[i].sd);
#endif
}
free(global.conn[i].version);
free(global.conn[i].remote);
}
free(global.all_stats);
free(global.conn);
free(queue_header);
if (global.num_clamd > 1)
free(multi_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);
}
}
#ifdef __GNUC__
#define __noreturn __attribute__((noreturn))
#else
#define __noreturn
#endif
static void __noreturn exit_program(enum exit_reason reason, const char *func, unsigned line)
{
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);
}
struct task {
char *line;
double tim;
int clamd_no;
};
static int tasks_compare(const void *a, const void *b)
{
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;
}
/* ----------- Socket routines ----------------------- */
#ifdef __GNUC__
static void print_con_info(conn_t *conn, const char *fmt, ...) __attribute__((format(printf, 2, 3)));
#endif
static void print_con_info(conn_t *conn, const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
if (stats_head_window) {
char *buf = malloc(maxx + 1);
char *nl = NULL;
OOM_CHECK(buf);
memset(buf, ' ', maxx + 1);
vsnprintf(buf, maxx + 1, fmt, ap);
if ((nl = strrchr(buf, '\n')) != NULL)
*nl = ' ';
buf[strlen(buf)] = ' ';
buf[maxx] = '\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);
}
char *get_ip(const char *ip)
{
char *dupip = NULL;
char *p1 = NULL;
unsigned int i;
/*
* Expected format of ip:
* 1) IPv4
* 2) IPv4:Port
* 3) IPv6
* 4) [IPv6]:Port
*
* 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';
p1 = strdup(dupip + 1);
free(dupip);
dupip = NULL;
return p1;
}
p1 = dupip;
i = 0;
while ((p1 = strchr(p1, ':'))) {
i++;
p1++;
}
if (i == 1) {
p1 = strchr(dupip, ':');
*p1 = '\0';
}
return dupip;
}
char *get_port(const char *ip)
{
char *dupip, *p;
unsigned int offset = 0;
dupip = get_ip(ip);
if (!(dupip))
return NULL;
if (ip[0] == '[')
offset += 2;
p = (char *)ip + strlen(dupip) + offset;
if (*p == ':') {
p = strdup(p + 1);
free(dupip);
return p;
}
free(dupip);
return NULL;
}
char *make_ip(const char *host, const char *port)
{
char *ip;
size_t len;
int ipv6;
if (!host || !port) {
return NULL;
}
len = strlen(host) + strlen(port);
ipv6 = (strchr(host, ':') != NULL);
len += (ipv6 ? 4 : 3);
ip = calloc(1, len);
if (!(ip))
return NULL;
snprintf(ip, len, "%s%s%s:%s", ipv6 ? "[" : "", host, ipv6 ? "]" : "", port);
return ip;
}
static int make_connection_real(const char *soname, conn_t *conn)
{
int s = -1;
struct timeval tv;
char *port = NULL;
char *pt = NULL;
char *host = pt;
struct addrinfo hints, *res = NULL, *p;
int err;
int ret = 0;
pt = strdup(soname);
OOM_CHECK(pt);
conn->tcp = 0;
#ifndef _WIN32
if (cli_is_abspath(soname) || (access(soname, F_OK) == 0)) {
struct sockaddr_un addr;
s = socket(AF_UNIX, SOCK_STREAM, 0);
if (s < 0) {
perror("socket");
ret = -1;
goto done;
}
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");
close(s);
ret = -1;
goto done;
}
goto end;
}
#endif
memset(&hints, 0x00, sizeof(struct addrinfo));
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = AI_PASSIVE;
host = get_ip(soname);
if (!(host)) {
ret = -1;
goto done;
}
port = get_port(soname);
conn->tcp = 1;
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));
ret = -1;
goto done;
}
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;
}
print_con_info(conn, "Connecting to: %s\n", soname);
if (connect(s, p->ai_addr, p->ai_addrlen)) {
perror("connect");
#ifndef _WIN32
close(s);
#else
closesocket(s);
#endif
continue;
}
break;
}
if (p == NULL) {
ret = -1;
goto done;
}
end:
conn->sd = s;
gettimeofday(&conn->tv_conn, NULL);
tv.tv_sec = 30;
tv.tv_usec = 0;
setsockopt(conn->sd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
if (conn->remote != soname) {
/* when we reconnect, they are the same */
if (NULL != conn->remote) {
free(conn->remote);
conn->remote = NULL;
}
conn->remote = make_ip(host, (port != NULL) ? port : "3310");
}
done:
if (NULL != res) {
freeaddrinfo(res);
res = NULL;
}
if (NULL != pt) {
free(pt);
pt = NULL;
}
if (NULL != host) {
free(host);
host = NULL;
}
if (NULL != port) {
free(port);
port = NULL;
}
return ret;
}
static int make_connection(const char *soname, conn_t *conn)
{
int rc;
int rv;
if (!soname) {
return -1;
}
if ((rc = make_connection_real(soname, conn)))
return rc;
send_string(conn, "nIDSESSION\nnVERSION\n");
free(conn->version);
conn->version = NULL;
rv = read_version(conn);
if (rv == -3) {
print_con_info(conn, "VERSION command unavailable, consider enabling it in the clamd configuration.\n");
EXIT_PROGRAM(FAIL_INITIAL_CONN);
} else if (!rv) {
// check if STATS command is available
if (check_stats_available(conn)) {
return 0;
} else {
print_con_info(conn, "STATS command unavailable, consider enabling it in the clamd configuration.\n");
EXIT_PROGRAM(FAIL_INITIAL_CONN);
}
}
/* clamd < 0.95 */
if ((rc = make_connection_real(soname, conn)))
return rc;
send_string(conn, "nSESSION\nnVERSION\n");
conn->version = NULL;
if (!read_version(conn))
return 0;
return -1;
}
static void reconnect(conn_t *conn);
static int send_string_noreconn(conn_t *conn, const char *cmd)
{
assert(cmd);
assert(conn && conn->sd > 0);
return send(conn->sd, cmd, strlen(cmd), 0);
}
static void send_string(conn_t *conn, const char *cmd)
{
while (send_string_noreconn(conn, cmd) == -1) {
reconnect(conn);
}
}
static int tries = 0;
static void reconnect(conn_t *conn)
{
if (++tries > 3) {
EXIT_PROGRAM(RECONNECT_FAIL);
}
if (conn->sd != -1) {
#ifndef _WIN32
close(conn->sd);
#else
closesocket(conn->sd);
#endif
}
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;
}
static int recv_line(conn_t *conn, char *buf, size_t len)
{
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 */
(void)send_string_noreconn(conn, "nEND\n");
#ifndef _WIN32
close(conn->sd);
#else
closesocket(conn->sd);
#endif
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;
}
static void output_queue(size_t line, ssize_t max)
{
ssize_t i, j;
int tasks_truncd = 0;
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));
if (detail_selected == -1 && global.num_clamd > 1)
mvwprintw(stats_window, line, 0, "%s", multi_queue_header);
else
mvwprintw(stats_window, line, 0, "%s", queue_header);
wattroff(stats_window, COLOR_PAIR(queue_header_color));
if (max < j) {
--max;
tasks_truncd = 1;
}
if (max < 0) max = 0;
for (i = 0; i < j && 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) {
size_t oldline = ++line;
char *nl = strrchr(++filstart, '\n');
if (nl != NULL)
*nl = '\0';
wattron(stats_window, A_BOLD);
if (detail_selected == -1 && global.num_clamd > 1)
mvwprintw(stats_window, line, 0, "%2u %s", filtered_tasks[i].clamd_no, cmd + 1);
else
mvwprintw(stats_window, line, 0, " %s", cmd + 1);
wattroff(stats_window, A_BOLD);
mvwprintw(stats_window, line, 15, "%10.03fs", filtered_tasks[i].tim);
mvwprintw(stats_window, line, 30, "%s", filstart);
line = getcury(stats_window);
if (line > oldline)
max -= line - oldline;
if (!tasks_truncd && max < j) {
--max;
tasks_truncd = 1;
}
}
}
}
if (tasks_truncd) {
/* in summary mode we can only show a max amount of tasks */
wattron(stats_window, A_DIM | COLOR_PAIR(header_color));
mvwprintw(stats_window, maxystats - 1, 0, "*** %u more task(s) not shown ***", (unsigned)(j - i));
wattroff(stats_window, A_DIM | COLOR_PAIR(header_color));
}
free(filtered_tasks);
}
/* ---------------------- stats parsing routines ------------------- */
static void parse_queue(conn_t *conn, char *buf, size_t len, unsigned idx)
{
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);
}
static unsigned biggest_mem = 0;
static void output_memstats(struct stats *stats)
{
char buf[128];
unsigned long totalmem;
int blink = 0;
werase(mem_window);
if (stats->mem > 0 || (stats->mem >= 0 && (stats->pools_total > 0))) {
box(mem_window, 0, 0);
if (stats->mem > 0)
snprintf(buf, sizeof(buf), "heap %4.0fM mmap %4.0fM unused%4.0fM",
stats->heapu, stats->mmapu, stats->releasable);
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 %4.0fM free %4.0fM total %4.0fM",
stats->totalu, stats->totalf, stats->totalu + stats->totalf);
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 %4.0fM total %4.0fM",
stats->pools_cnt, stats->pools_used, stats->pools_total);
print_colored(mem_window, buf);
totalmem = (stats->heapu + stats->mmapu + stats->pools_total) * 1000;
if (totalmem > biggest_mem) {
biggest_mem = totalmem;
blink = 1;
}
show_bar(mem_window, 4, totalmem,
(stats->mmapu + stats->releasable + stats->pools_total - stats->pools_used) * 1000,
biggest_mem, blink);
}
wrefresh(mem_window);
}
static void parse_memstats(const char *line, struct stats *stats)
{
if (sscanf(line, " heap %lfM mmap %lfM used %lfM free %lfM releasable %lfM pools %u pools_used %lfM pools_total %lfM",
&stats->heapu, &stats->mmapu, &stats->totalu, &stats->totalf, &stats->releasable,
&stats->pools_cnt, &stats->pools_used, &stats->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, &stats->pools_used, &stats->pools_total) != 3) {
stats->mem = -1;
return;
}
stats->mem = 0;
return;
}
stats->mem = stats->heapu + stats->mmapu + stats->pools_total;
}
static int output_stats(struct stats *stats, unsigned idx)
{
char buf[128];
char timbuf[14];
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);
int len = 0;
OOM_CHECK(line);
if (stats->mem <= 0 || stats->stats_unsupp) {
strncpy(mem, "N/A", sizeof(mem));
mem[sizeof(mem) - 1] = '\0';
} else {
const char *format;
char c;
double s;
if (stats->mem >= 1024) {
c = 'G';
s = stats->mem / 1024.0;
} else {
c = 'M';
s = stats->mem;
}
if (s >= 99.95)
format = "%.0f%c";
else if (s >= 9.995)
format = "%.1f%c";
else
format = "%.2f%c";
snprintf(mem, sizeof(mem), format, s, c);
mem[sizeof(mem) - 1] = '\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-%02uT%02u",
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) {
len = snprintf(line, maxx + 1, "%2u %02u:%02u:%02u %3u %3u %5u %5u %5s %-7s %5s %-13s %s",
idx + 1, stats->conn_hr, stats->conn_min, stats->conn_sec,
stats->live, stats->idle,
stats->current_q, stats->biggest_queue,
mem,
stats->engine_version, stats->db_version, timbuf, stats->remote);
} else {
len = snprintf(line, maxx + 1, "%2u %02u:%02u:%02u N/A N/A N/A N/A N/A %-7s %5s %-13s %s",
idx + 1, stats->conn_hr, stats->conn_min, stats->conn_sec,
stats->engine_version, stats->db_version, timbuf, stats->remote);
}
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));
}
if ((unsigned)len > maxx) {
wattron(win, A_DIM | COLOR_PAIR(header_color));
mvwprintw(win, i, maxx - 3, "...");
wattroff(win, A_DIM | COLOR_PAIR(header_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 : ");
snprintf(buf, sizeof(buf), "live %3u idle %3u max %3u", stats->live, stats->idle, stats->max);
print_colored(win, buf);
show_bar(win, i++, stats->live, stats->idle, stats->max, 0);
*/
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;
}
static void output_all(void)
{
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);
}
}
static void parse_stats(conn_t *conn, struct stats *stats, unsigned idx)
{
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 != '/')
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);
if(!val) {
i++;
continue;
}
waddch(win, ':');
print_colored(win, val);
i++;
*/
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;
}
}
}
static int read_version(conn_t *conn)
{
char buf[1024];
unsigned i;
if (!recv_line(conn, buf, sizeof(buf)))
return -1;
if (!strcmp(buf, "UNKNOWN COMMAND\n"))
return -2;
char *p = strchr(buf, ':');
if (NULL == p)
return -1;
// check if VERSION command is available
if (!strcmp(p, ": COMMAND UNAVAILABLE\n"))
return -3;
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;
}
static int check_stats_available(conn_t *conn)
{
char buf[1024];
send_string(conn, "nSTATS\n");
if (!recv_line(conn, buf, sizeof(buf)))
return 0;
char *p = strchr(buf, ':');
if (NULL == p)
return 0;
if (!strcmp(p, ": COMMAND UNAVAILABLE\n"))
return 0;
return 1;
}
static void sigint(int a)
{
UNUSEDPARAM(a);
EXIT_PROGRAM(SIGINT_REASON);
}
static void help(void)
{
printf("\n");
printf(" Clam AntiVirus: Monitoring Tool %s\n", get_version());
printf(" By The ClamAV Team: https://www.clamav.net/about.html#credits\n");
printf(" (C) 2025 Cisco Systems, Inc.\n");
printf("\n");
printf(" clamdtop [-hVc] [host[:port] /path/to/clamd.sock ...]\n");
printf("\n");
printf(" --help -h Show this help\n");
printf(" --version -V Show version\n");
printf(" --config-file=FILE -c FILE Read clamd's configuration files from FILE\n");
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.sock Connect to clamd over a local socket\n");
printf("\n");
return;
}
static int default_colors = 0;
/* -------------------------- Initialization ---------------- */
static void setup_connections(int argc, char *argv[])
{
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);
}
if (optget(opts, "help")->enabled) {
optfree(opts);
help();
normal_exit = 1;
exit(0);
}
if (optget(opts, "version")->enabled) {
printf("Clam AntiVirus Monitoring Tool %s\n", get_version());
optfree(opts);
normal_exit = 1;
exit(0);
}
if (optget(opts, "defaultcolors")->enabled)
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);
}
if ((opt = optget(clamd_opts, "LocalSocket"))->enabled) {
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;
}
snprintf(buf, sizeof(buf), "%lld", opt->numarg);
conn = make_ip(host, buf);
} 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;
while (opts->filename[i]) {
i++;
}
global.num_clamd = i;
}
#ifdef _WIN32
WSADATA wsaData;
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != NO_ERROR) {
fprintf(stderr, "Error at WSAStartup(): %d\n", WSAGetLastError());
EXIT_PROGRAM(FAIL_INITIAL_CONN);
}
#endif
/* clamdtop */
puts(" __ ____");
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);
for (i = 0; i < global.num_clamd; i++) {
const char *soname;
if (!conn && !opts->filename) {
soname = NULL;
} else {
soname = conn ? conn : opts->filename[i];
}
global.conn[i].line = i + 1;
if (make_connection(soname, &global.conn[i]) < 0) {
EXIT_PROGRAM(FAIL_INITIAL_CONN);
}
}
optfree(opts);
free(conn);
#ifndef _WIN32
signal(SIGPIPE, SIG_IGN);
signal(SIGINT, sigint);
#endif
}
static void free_global_stats(void)
{
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;
}
static int help_line;
static void explain(const char *abbrev, const char *msg)
{
wattron(stdscr, A_BOLD);
mvwprintw(stdscr, help_line++, 0, "%-15s", abbrev);
wattroff(stdscr, A_BOLD);
wprintw(stdscr, " %s", msg);
}
static int show_help(void)
{
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("ENGINE", "Engine version");
explain("DBVER", "Database version");
explain("DBTIME", "Database publish time");
explain("HOST", "Which clamd, local means unix socket");
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
* view */
} while (ch == -1 /*|| ch == KEY_RESIZE*/);
return ch == KEY_RESIZE ? KEY_RESIZE : -1;
}
int main(int argc, char *argv[])
{
int ch = 0;
struct timeval tv_last, tv;
unsigned i;
#ifdef _WIN32
SetConsoleOutputCP(CP_UTF8);
#endif
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;
}