自己使用源码编译clamav后,运行clamd。然后运行clamdscan --version,发现输出为ClamAV 0.104.2。而我的 /var/lib/clamav/目录下有对应的病毒库。数据不对。
然后使用gdb -p pid 来调试 clamd。在parse_command处设置断点。发现没有断住。
netstat -nlp |grep clam
tcp 0 0 0.0.0.0:3310 0.0.0.0:* LISTEN 281004/./clamd/clam
tcp6 0 0 :::3310 :::* LISTEN 281004/./clamd/clam
有在监控端口。
继续定位,直接gdb ./clamdscan/clamdscan 在print_server_version函数上设置断点。r --version 运行。跟踪代码,进入到dconnect报错返回了。具体因为如果开启了TCPSocket,需要同步开启TCPAddr。否则连接时,检查会失败。代码如下:
/* Connects to clamd
* Returns a FD or -1 on error */
int dconnect()
{
int sockd, res;
const struct optstruct *opt;
struct addrinfo hints, *info, *p;
char port[10];
char *ipaddr;
#ifndef _WIN32
opt = optget(clamdopts, "LocalSocket");
if (opt->enabled) {
if ((sockd = socket(AF_UNIX, SOCK_STREAM, 0)) >= 0) {
if (connect(sockd, (struct sockaddr *)&nixsock, sizeof(nixsock)) == 0)
return sockd;
else {
logg("!Could not connect to clamd on LocalSocket %s: %s\n", opt->strarg, strerror(errno));
close(sockd);
}
}
}
#endif
snprintf(port, sizeof(port), "%lld", optget(clamdopts, "TCPSocket")->numarg);
opt = optget(clamdopts, "TCPAddr");//此处做检查
while (opt) {
if (opt->enabled) {
ipaddr = NULL;
if (opt->strarg)
ipaddr = (!strcmp(opt->strarg, "any") ? NULL : opt->strarg);
memset(&hints, 0x00, sizeof(struct addrinfo));
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
if ((res = getaddrinfo(ipaddr, port, &hints, &info))) {
logg("!Could not lookup %s: %s\n", ipaddr ? ipaddr : "", gai_strerror(res));
opt = opt->nextarg;
continue;
}
for (p = info; p != NULL; p = p->ai_next) {
if ((sockd = socket(p->ai_family, p->ai_socktype, p->ai_protocol)) < 0) {
logg("!Can't create the socket: %s\n", strerror(errno));
continue;
}
if (connect(sockd, p->ai_addr, p->ai_addrlen) < 0) {
logg("!Could not connect to clamd on %s: %s\n", opt->strarg, strerror(errno));
closesocket(sockd);
continue;
}
freeaddrinfo(info);
return sockd;
}
freeaddrinfo(info);
}
opt = opt->nextarg;
}
return -1;
}
或者直接开启LocalSocket配置项。
下面我们开启TCPAddr配置项。再重新运行clamd。然后运行./clamdscan/clamdscan --version。
输出如下:
ClamAV 0.104.2/26538/Wed May 11 01:06:03 2022
接下来我们进行简单的源码分析--version整个流程:
clamdscan相关的代码在clamdscan目录下,clamdscan与clamd运行时不带-c指定配置文件时都使用/usr/local/etc/clamd.conf。main函数在clamdscan.c 中。
main:
int main(int argc, char **argv)
{
int ds, dms, ret, infected = 0, err = 0;
struct timeval t1, t2;
time_t date_start, date_end;
struct optstruct *opts;
const struct optstruct *opt;
char buffer[26];
#ifndef _WIN32
struct sigaction sigact;
#endif
if ((opts = optparse(NULL, argc, argv, 1, OPT_CLAMDSCAN, OPT_CLAMSCAN, NULL)) == NULL) {
mprintf("!Can't parse command line options\n");
exit(2);
}
if (optget(opts, "help")->enabled) {
optfree(opts);
help();
}
if ((clamdopts = optparse(optget(opts, "config-file")->strarg, 0, NULL, 1, OPT_CLAMD, 0, NULL)) == NULL) {
logg("!Can't parse clamd configuration file %s\n", optget(opts, "config-file")->strarg);
optfree(opts);
exit(2);
}
if (optget(opts, "verbose")->enabled) {
mprintf_verbose = 1;
logg_verbose = 1;
}
if (optget(opts, "quiet")->enabled)
mprintf_quiet = 1;
if (optget(opts, "stdout")->enabled)
mprintf_stdout = 1;
if (optget(opts, "version")->enabled) {//version命令行参数是否启动。我们这里为启动
print_server_version(opts);//主要逻辑在这个函数中。
optfree(opts);
optfree(clamdopts);
exit(0);
}
......
}
main=>print_server_version:
static void print_server_version(const struct optstruct *opt)
{
if (get_clamd_version(opt)) {//如果返回非0,表示与clamd连接失败
/* can't get version from server, fallback */
printf("ClamAV %s\n", get_version());//打印程序的版本信息
}
}
main=>print_server_version=>get_clamd_version:
int get_clamd_version(const struct optstruct *opts)
{
char *buff;
int len, sockd;
struct RCVLN rcv;
const char zVERSION[] = "zVERSION";//待发送给clamd的内容, 'z'表示\0结束
isremote(opts);//根据配置检查是使用本地连接还是tcp网络连接,我们这里为网络连接
if ((sockd = dconnect()) < 0) return 2;//继续获取配置信息,发起连接
recvlninit(&rcv, sockd);
if (sendln(sockd, zVERSION, sizeof(zVERSION))) {//发送内容至clamd
closesocket(sockd);
return 2;
}
while ((len = recvln(&rcv, &buff, NULL))) {//读取clamd返回的版本信息
if (len == -1) {
logg("!Error occurred while receiving version information.\n");
break;
}
printf("%s\n", buff);
}
closesocket(sockd);//关闭连接
return 0;
}
检查是用Unix socket 还是tcp。
main=>print_server_version=>get_clamd_version=>isremote:
/* Inits the communication layer
* Returns 0 if clamd is local, non zero if clamd is remote */
static int isremote(const struct optstruct *opts)
{
int s, ret;
const struct optstruct *opt;
char *ipaddr, port[10];
struct addrinfo hints, *info, *p;
int res;
UNUSEDPARAM(opts);
#ifndef _WIN32
if ((opt = optget(clamdopts, "LocalSocket"))->enabled) {//开启了本地连接,返回0
memset((void *)&nixsock, 0, sizeof(nixsock));
nixsock.sun_family = AF_UNIX;
strncpy(nixsock.sun_path, opt->strarg, sizeof(nixsock.sun_path));
nixsock.sun_path[sizeof(nixsock.sun_path) - 1] = '\0';
return 0;
}
#endif
if (!(opt = optget(clamdopts, "TCPSocket"))->enabled)
return 0;
snprintf(port, sizeof(port), "%lld", optget(clamdopts, "TCPSocket")->numarg);//获取配置的端口号
opt = optget(clamdopts, "TCPAddr");//tcp连接的IP地址
while (opt) {
ipaddr = NULL;
if (opt->strarg)
ipaddr = (!strcmp(opt->strarg, "any") ? NULL : opt->strarg);
memset(&hints, 0x00, sizeof(struct addrinfo));
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = AI_PASSIVE;
if ((res = getaddrinfo(ipaddr, port, &hints, &info))) {
logg("!Can't lookup clamd hostname: %s\n", gai_strerror(res));
opt = opt->nextarg;
continue;
}
for (p = info; p != NULL; p = p->ai_next) {
if ((s = socket(p->ai_family, p->ai_socktype, p->ai_protocol)) < 0) {
logg("isremote: socket() returning: %s.\n", strerror(errno));
continue;
}
switch (p->ai_family) {
case AF_INET:
((struct sockaddr_in *)(p->ai_addr))->sin_port = htons(INADDR_ANY);
break;
case AF_INET6:
((struct sockaddr_in6 *)(p->ai_addr))->sin6_port = htons(INADDR_ANY);
break;
default:
break;
}
ret = bind(s, p->ai_addr, p->ai_addrlen);
if (ret) {
if (errno == EADDRINUSE) {
/*
* If we can't bind, then either we're attempting to listen on an IP that isn't
* ours or that clamd is already listening on.
*/
closesocket(s);
freeaddrinfo(info);
return 0;
}
closesocket(s);
freeaddrinfo(info);
return 1;//网络检查成功,返回1
}
closesocket(s);
}
freeaddrinfo(info);
opt = opt->nextarg;
}
return 0;
}
发起连接,成功时返回socket fd,然后发送内容。
main=>print_server_version=>get_clamd_version=>dconnect:
/* Connects to clamd
* Returns a FD or -1 on error */
int dconnect()
{
int sockd, res;
const struct optstruct *opt;
struct addrinfo hints, *info, *p;
char port[10];
char *ipaddr;
#ifndef _WIN32
opt = optget(clamdopts, "LocalSocket");
if (opt->enabled) {
if ((sockd = socket(AF_UNIX, SOCK_STREAM, 0)) >= 0) {//本地连接,建立unix socket
if (connect(sockd, (struct sockaddr *)&nixsock, sizeof(nixsock)) == 0)
return sockd;//返回socket fd
else {
logg("!Could not connect to clamd on LocalSocket %s: %s\n", opt->strarg, strerror(errno));
close(sockd);
}
}
}
#endif
snprintf(port, sizeof(port), "%lld", optget(clamdopts, "TCPSocket")->numarg);
opt = optget(clamdopts, "TCPAddr");
while (opt) {
if (opt->enabled) {
ipaddr = NULL;
if (opt->strarg)
ipaddr = (!strcmp(opt->strarg, "any") ? NULL : opt->strarg);
memset(&hints, 0x00, sizeof(struct addrinfo));
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
if ((res = getaddrinfo(ipaddr, port, &hints, &info))) {//地址解析
logg("!Could not lookup %s: %s\n", ipaddr ? ipaddr : "", gai_strerror(res));
opt = opt->nextarg;
continue;
}
for (p = info; p != NULL; p = p->ai_next) {
if ((sockd = socket(p->ai_family, p->ai_socktype, p->ai_protocol)) < 0) {
logg("!Can't create the socket: %s\n", strerror(errno));
continue;
}
if (connect(sockd, p->ai_addr, p->ai_addrlen) < 0) {//发起连接
logg("!Could not connect to clamd on %s: %s\n", opt->strarg, strerror(errno));
closesocket(sockd);
continue;
}
freeaddrinfo(info);
return sockd;//连接成功,返回socktfd
}
freeaddrinfo(info);
}
opt = opt->nextarg;
}
return -1;
}
发送 zVERSION
main=>print_server_version=>get_clamd_version=>sendln:
/* Sends bytes over a socket
* Returns 0 on success */
int sendln(int sockd, const char *line, unsigned int len)
{
while (len) {
int sent = send(sockd, line, len, 0);//循环发送内容,保证发送完整信息
if (sent <= 0) {
if (sent && errno == EINTR) continue;
logg("!Can't send to clamd: %s\n", strerror(errno));
return 1;
}
line += sent;
len -= sent;
}
return 0;
}
等待接收数据。
main=>print_server_version=>get_clamd_version=>recvln:
/* Receives a full (terminated with \0) line from a socket
* Sets rbol to the begin of the received line, and optionally
* reol to the end of line.
* Should be called repeatedly until all input is consumed
* Returns:
* - the length of the line (a positive number) on success
* - 0 if the connection is closed
* - -1 on error
*/
int recvln(struct RCVLN *s, char **rbol, char **reol)
{
char *eol;
while (1) {
if (!s->r) {
s->r = recv(s->sockd, s->cur, sizeof(s->buf) - (s->cur - s->buf), 0);
if (s->r <= 0) {
if (s->r && errno == EINTR) {
s->r = 0;
continue;
}
if (s->r || s->cur != s->buf) {
*s->cur = '\0';
if (strcmp(s->buf, "UNKNOWN COMMAND\n"))
logg("!Communication error\n");
else
logg("!Command rejected by clamd (wrong clamd version?)\n");
return -1;
}
return 0;
}
}
if ((eol = memchr(s->cur, 0, s->r))) {
int ret = 0;
eol++;
s->r -= eol - s->cur;
*rbol = s->bol;
if (reol) *reol = eol;
ret = eol - s->bol;
if (s->r)
s->bol = s->cur = eol;
else
s->bol = s->cur = s->buf;
return ret;
}
s->r += s->cur - s->bol;
if (!eol && s->r == sizeof(s->buf)) {
logg("!Overlong reply from clamd\n");
return -1;
}
if (!eol) {
if (s->buf != s->bol) { /* old memmove sux */
memmove(s->buf, s->bol, s->r);
s->bol = s->buf;
}
s->cur = &s->bol[s->r];
s->r = 0;
}
}
}
这些都是基本socket通信的api,很简单。将这个函数返回的内容打印出来buff参数。
下面分析clamd的代码:
同样clamd的代码基本都在clamd目录下。我们先进入clamd.c的main函数。
main:
int main(int argc, char **argv)
{
static struct cl_engine *engine = NULL;
const struct optstruct *opt;
#ifndef _WIN32
struct passwd *user = NULL;
struct sigaction sa;
int dropPrivRet = 0;
#endif
#if defined(C_LINUX) || (defined(RLIMIT_DATA) && defined(C_BSD))
struct rlimit rlim;
#endif
time_t currtime;
const char *dbdir, *cfgfile;
char *pua_cats = NULL, *pt;
int ret, tcpsock = 0, localsock = 0, min_port, max_port;
unsigned int sigs = 0;
int *lsockets = NULL;
unsigned int nlsockets = 0;
unsigned int dboptions = 0;
unsigned int i;
int j;
int num_fd;
pid_t parentPid = getpid();
#ifdef C_LINUX
STATBUF sb;
#endif
pid_t mainpid = 0;
mode_t old_umask = 0;
const char *user_name = NULL;
......
/* check socket type */
if (optget(opts, "TCPSocket")->enabled)
tcpsock = 1;
if (optget(opts, "LocalSocket")->enabled)
localsock = 1;
logg("#Received %d file descriptor(s) from systemd.\n", num_fd);
if (!tcpsock && !localsock && num_fd == 0) {//如果tcp和unix都没开启,打印日志,退出
logg("!Please define server type (local and/or TCP).\n");
ret = 1;
break;
}
......
if (tcpsock || num_fd > 0) {
opt = optget(opts, "TCPAddr");//读取tcp配置项
if (opt->enabled) {
int breakout = 0;
while (opt && opt->strarg) {
char *ipaddr = (!strcmp(opt->strarg, "all") ? NULL : opt->strarg);
if (tcpserver(&lsockets, &nlsockets, ipaddr, opts) == -1) {//监听端口
ret = 1;
breakout = 1;
break;
}
opt = opt->nextarg;
}
if (breakout)
break;
} else {
if (tcpserver(&lsockets, &nlsockets, NULL, opts) == -1) {
ret = 1;
break;
}
}
}
......
ret = recvloop(lsockets, nlsockets, engine, dboptions, opts);//接收连接函数
......
}
这个main函数很庞大,我只列出了部分相关代码。
接着看看监听函数代码:
main=>tcpserver:
int tcpserver(int **lsockets, unsigned int *nlsockets, char *ipaddr, const struct optstruct *opts)
{
struct addrinfo hints, *info, *p;
char host[NI_MAXHOST], serv[NI_MAXSERV];
int *sockets;
int sockfd = 0, backlog;
int *t;
char *estr, port[10];
int yes = 1;
int res;
unsigned int i = 0;
int num_fd;
sockets = *lsockets;
num_fd = sd_listen_fds(0);
if (num_fd > 2) {
logg("!TCP: Received more than two file descriptors from systemd.\n");
return -1;
} else if (num_fd > 0) {
/* use socket passed by systemd */
int i;
for (i = 0; i < num_fd; i += 1) {
sockfd = SD_LISTEN_FDS_START + i;
if (sd_is_socket(sockfd, AF_INET, SOCK_STREAM, 1) == 1) {
/* correct socket */
logg("#TCP: Received AF_INET SOCK_STREAM socket from systemd.\n");
break;
} else if (sd_is_socket(sockfd, AF_INET6, SOCK_STREAM, 1) == 1) {
/* correct socket */
logg("#TCP: Received AF_INET6 SOCK_STREAM socket from systemd.\n");
break;
} else {
/* wrong socket */
sockfd = -2;
}
}
if (sockfd == -2) {
logg("#TCP: No tcp AF_INET/AF_INET6 SOCK_STREAM socket received from systemd.\n");
return -2;
}
t = realloc(sockets, sizeof(int) * (*nlsockets + 1));
if (!(t)) {
return -1;
}
sockets = t;
sockets[*nlsockets] = sockfd;
(*nlsockets)++;
*lsockets = sockets;
return 0;
}
/* create socket */
snprintf(port, sizeof(port), "%lld", optget(opts, "TCPSocket")->numarg);
memset(&hints, 0x00, sizeof(struct addrinfo));
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = AI_PASSIVE;
#ifdef AI_ADDRCONFIG
hints.ai_flags |= AI_ADDRCONFIG;
#endif
if ((res = getaddrinfo(ipaddr, port, &hints, &info))) {
logg("!TCP: getaddrinfo failed: %s\n", gai_strerror(res));
return -1;
}
for (p = info; p != NULL; p = p->ai_next, i++) {
t = realloc(sockets, sizeof(int) * (*nlsockets + 1));
if (!(t)) {
for (i = 0; i < *nlsockets; i++)
close(sockets[i]);
freeaddrinfo(info);
return -1;
}
sockets = t;
if ((sockfd = socket(p->ai_family, p->ai_socktype, p->ai_protocol)) == -1) {
estr = strerror(errno);
logg("!TCP: socket() error: %s\n", estr);
continue;
}
if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, (void *)&yes, sizeof(yes)) == -1) {
logg("!TCP: setsocktopt(SO_REUSEADDR) error: %s\n", strerror(errno));
}
#ifdef IPV6_V6ONLY
if (p->ai_family == AF_INET6 &&
setsockopt(sockfd, IPPROTO_IPV6, IPV6_V6ONLY, &yes, sizeof(yes)) == -1) {
estr = strerror(errno);
logg("!TCP: setsocktopt(IPV6_V6ONLY) error: %s\n", estr);
}
#endif /* IPV6_V6ONLY */
#ifdef HAVE_GETNAMEINFO
if ((res = getnameinfo(p->ai_addr, p->ai_addrlen, host, sizeof(host),
serv, sizeof(serv), NI_NUMERICHOST | NI_NUMERICSERV))) {
logg("!TCP: getnameinfo failed: %s\n", gai_strerror(res));
host[0] = '\0';
serv[0] = '\0';
}
#else
if (ipaddr) {
strncpy(host, ipaddr, sizeof(host));
host[sizeof(host) - 1] = '\0';
} else
host[0] = '\0';
snprintf(serv, sizeof(serv), "%u", (unsigned int)(optget(opts, "TCPSocket")->numarg));
#endif
if (bind(sockfd, p->ai_addr, p->ai_addrlen) == -1) {
estr = strerror(errno);
logg("!TCP: Cannot bind to [%s]:%s: %s\n", host, serv, estr);
closesocket(sockfd);
continue;
}
logg("#TCP: Bound to [%s]:%s\n", host, serv);//这个绑定的IP地址端口,我们可以看/tmp/clamd.log, 我们这里为TCP: Bound to [localhost]:3310
backlog = optget(opts, "MaxConnectionQueueLength")->numarg;
logg("#TCP: Setting connection queue length to %d\n", backlog);
if (listen(sockfd, backlog) == -1) {
estr = strerror(errno);
logg("!TCP: Cannot listen on [%s]:%s: %s\n", host, serv, estr);
closesocket(sockfd);
continue;
}
sockets[*nlsockets] = sockfd;
(*nlsockets)++;
}
freeaddrinfo(info);
*lsockets = sockets;//最后通过第一个参数,返回监听的socket fd
return 0;
}
recvloop使用前面打开的监听fd进行接收连接。代码也挺长的。
main=>recvloop:
int recvloop(int *socketds, unsigned nsockets, struct cl_engine *engine, unsigned int dboptions, const struct optstruct *opts)
{
......
pthread_t accept_th;
pthread_mutex_t fds_mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t recvfds_mutex = PTHREAD_MUTEX_INITIALIZER;
struct acceptdata acceptdata = ACCEPTDATA_INIT(&fds_mutex, &recvfds_mutex);
struct fd_data *fds = &acceptdata.recv_fds;//fds就是接收连接的fd
......
for (i = 0; i < nsockets; i++)
if (fds_add(&acceptdata.fds, socketds[i], 1, 0) == -1) {//将监听fd添加到acceptdata.fds中
logg("!fds_add failed\n");
cl_engine_free(engine);
return 1;
}
#ifdef _WIN32
event_wake_accept = CreateEvent(NULL, TRUE, FALSE, NULL);
event_wake_recv = CreateEvent(NULL, TRUE, FALSE, NULL);
#else
if (pipe(acceptdata.syncpipe_wake_recv) == -1 ||
(pipe(acceptdata.syncpipe_wake_accept) == -1)) {
logg("!pipe failed\n");
exit(-1);
}
syncpipe_wake_recv_w = acceptdata.syncpipe_wake_recv[1];
if (fds_add(fds, acceptdata.syncpipe_wake_recv[0], 1, 0) == -1 ||
fds_add(&acceptdata.fds, acceptdata.syncpipe_wake_accept[0], 1, 0)) {
logg("!failed to add pipe fd\n");
exit(-1);
}
#endif
if (pthread_create(&accept_th, NULL, acceptloop_th, &acceptdata)) {//创建并启动accept线程,这个线程会接收clamdscan发起的连接并通知recvloop进行处理。
logg("!pthread_create failed\n");
exit(-1);
}
time(&start_time);
for (;;) {
int new_sd;
/* Block waiting for connection on any of the sockets */
pthread_mutex_lock(fds->buf_mutex);
fds_cleanup(fds);
/* signal that we can accept more connections */
if (fds->nfds <= (unsigned)max_queue)
pthread_cond_signal(&acceptdata.cond_nfds);
new_sd = fds_poll_recv(fds, selfchk ? (int)selfchk : -1, 1, event_wake_recv);
#ifdef _WIN32
ResetEvent(event_wake_recv);
#else
if (!fds->nfds) {
/* at least the dummy/sync pipe should have remained */
logg("!All recv() descriptors gone: fatal\n");
pthread_mutex_lock(&exit_mutex);
progexit = 1;
pthread_mutex_unlock(&exit_mutex);
pthread_mutex_unlock(fds->buf_mutex);
break;
}
#endif
if (new_sd == -1 && errno != EINTR) {
logg("!Failed to poll sockets, fatal\n");
pthread_mutex_lock(&exit_mutex);
progexit = 1;
pthread_mutex_unlock(&exit_mutex);
}
if (fds->nfds) i = (rr_last + 1) % fds->nfds;
for (j = 0; j < fds->nfds && new_sd >= 0; j++, i = (i + 1) % fds->nfds) {
size_t pos = 0;
int error = 0;
struct fd_buf *buf = &fds->buf[i];
if (!buf->got_newdata)
continue;
#ifndef _WIN32
if (buf->fd == acceptdata.syncpipe_wake_recv[0]) {
/* dummy sync pipe, just to wake us */
if (read(buf->fd, buff, sizeof(buff)) < 0) {
logg("^Syncpipe read failed\n");
}
continue;
}
#endif
if (buf->got_newdata == -1) {
if (buf->mode == MODE_WAITREPLY) {
logg("$mode WAIT_REPLY -> closed\n");
buf->fd = -1;
thrmgr_group_terminate(buf->group);
thrmgr_group_finished(buf->group, EXIT_ERROR);
continue;
} else {
logg("$client read error or EOF on read\n");
error = 1;
}
}
if (buf->fd != -1 && buf->got_newdata == -2) {
logg("$Client read timed out\n");
mdprintf(buf->fd, "COMMAND READ TIMED OUT\n");
error = 1;
}
rr_last = i;
if (buf->mode == MODE_WAITANCILL) {
buf->mode = MODE_COMMAND;
logg("$mode -> MODE_COMMAND\n");
}
while (!error && buf->fd != -1 && buf->buffer && pos < buf->off &&
buf->mode != MODE_WAITANCILL) {
client_conn_t conn;
const char *cmd = NULL;
int rc;
/* New data available to read on socket. */
memset(&conn, 0, sizeof(conn));
conn.scanfd = buf->recvfd;
buf->recvfd = -1;
conn.sd = buf->fd;
conn.options = &options;
conn.opts = opts;
conn.thrpool = thr_pool;
conn.engine = engine;
conn.group = buf->group;
conn.id = buf->id;
conn.quota = buf->quota;
conn.filename = buf->dumpname;
conn.mode = buf->mode;
conn.term = buf->term;
/* Parse & dispatch command */
cmd = parse_dispatch_cmd(&conn, buf, &pos, &error, opts, readtimeout);//解析连接发送过来的数据并在内部决定是否进行执行,或者分发
if (conn.mode == MODE_COMMAND && !cmd)
break;
if (!error) {
if (buf->mode == MODE_WAITREPLY && buf->off) {
/* Client is not supposed to send anything more */
logg("^Client sent garbage after last command: %lu bytes\n", (unsigned long)buf->off);
buf->buffer[buf->off] = '\0';
logg("$Garbage: %s\n", buf->buffer);
error = 1;
} else if (buf->mode == MODE_STREAM) {
rc = handle_stream(&conn, buf, opts, &error, &pos, readtimeout);
if (rc == -1)
break;
else
continue;
}
}
if (error && error != CL_ETIMEOUT) {
conn_reply_error(&conn, "Error processing command.");
}
}
if (error) {
if (buf->dumpfd != -1) {
close(buf->dumpfd);
if (buf->dumpname) {
cli_unlink(buf->dumpname);
free(buf->dumpname);
}
buf->dumpfd = -1;
}
thrmgr_group_terminate(buf->group);
if (thrmgr_group_finished(buf->group, EXIT_ERROR)) {
if (buf->fd < 0) {
logg("$Skipping shutdown of bad socket after error (FD %d)\n", buf->fd);
} else {
logg("$Shutting down socket after error (FD %d)\n", buf->fd);
shutdown(buf->fd, 2);
closesocket(buf->fd);
}
} else
logg("$Socket not shut down due to active tasks\n");
buf->fd = -1;
}
}
pthread_mutex_unlock(fds->buf_mutex);
/* handle progexit */
pthread_mutex_lock(&exit_mutex);
if (progexit) {
pthread_mutex_unlock(&exit_mutex);
pthread_mutex_lock(fds->buf_mutex);
if (sd_listen_fds(0) == 0) {
/* only close the sockets, when not using systemd socket activation */
for (i = 0; i < fds->nfds; i++) {
if (fds->buf[i].fd == -1)
continue;
thrmgr_group_terminate(fds->buf[i].group);
if (thrmgr_group_finished(fds->buf[i].group, EXIT_ERROR)) {
logg("$Shutdown closed fd %d\n", fds->buf[i].fd);
shutdown(fds->buf[i].fd, 2);
closesocket(fds->buf[i].fd);
fds->buf[i].fd = -1;
}
}
}
pthread_mutex_unlock(fds->buf_mutex);
break;
}
pthread_mutex_unlock(&exit_mutex);
/* SIGHUP */
if (sighup) {
logg("SIGHUP caught: re-opening log file.\n");
logg_close();
sighup = 0;
if (!logg_file && (opt = optget(opts, "LogFile"))->enabled)
logg_file = opt->strarg;
}
/* SelfCheck */
if (selfchk) {
time(¤t_time);
if ((current_time - start_time) >= (time_t)selfchk) {
if (need_db_reload()) {
pthread_mutex_lock(&reload_mutex);
reload = 1;
pthread_mutex_unlock(&reload_mutex);
}
time(&start_time);
}
}
/* DB reload */
pthread_mutex_lock(&reload_mutex);
if (reload) {
pthread_mutex_unlock(&reload_mutex);
/* Reload was requested */
pthread_mutex_lock(&reload_stage_mutex);
if (reload_stage == RELOAD_STAGE__IDLE) {
/* Reloading not already taking place */
reload_stage = RELOAD_STAGE__RELOADING;
pthread_mutex_unlock(&reload_stage_mutex);
if (CL_SUCCESS != reload_db(&engine, dboptions, opts, thr_pool)) {//--reload参数的效果,后面有时间会分析相关源码
logg("^Database reload setup failed, keeping the previous instance\n");
pthread_mutex_lock(&reload_mutex);
reload = 0;
pthread_mutex_unlock(&reload_mutex);
pthread_mutex_lock(&reload_stage_mutex);
reload_stage = RELOAD_STAGE__IDLE;
pthread_mutex_unlock(&reload_stage_mutex);
}
pthread_mutex_lock(&reload_stage_mutex);
}
if (reload_stage == RELOAD_STAGE__NEW_DB_AVAILABLE) {
/* New database available */
if (g_newengine) {
/* Reload succeeded */
logg("Activating the newly loaded database...\n");
thrmgr_setactiveengine(g_newengine);
if (optget(opts, "ConcurrentDatabaseReload")->enabled) {
/* If concurrent database reload, we now need to free the old engine. */
cl_engine_free(engine);
}
engine = g_newengine;
g_newengine = NULL;
} else {
logg("^Database reload failed, keeping the previous instance\n");
}
reload_stage = RELOAD_STAGE__IDLE;
pthread_mutex_unlock(&reload_stage_mutex);
pthread_mutex_lock(&reload_mutex);
reload = 0;
pthread_mutex_unlock(&reload_mutex);
time(&reloaded_time);
} else {
pthread_mutex_unlock(&reload_stage_mutex);
}
} else {
pthread_mutex_unlock(&reload_mutex);
}
}
pthread_mutex_lock(&exit_mutex);
progexit = 1;
pthread_mutex_unlock(&exit_mutex);
#ifdef _WIN32
SetEvent(event_wake_accept);
#else
if (write(acceptdata.syncpipe_wake_accept[1], "", 1) < 0) {
logg("^Write to syncpipe failed\n");
}
#endif
/* Destroy the thread manager.
* This waits for all current tasks to end
*/
logg("*Waiting for all threads to finish\n");
thrmgr_destroy(thr_pool);
if (engine) {
thrmgr_setactiveengine(NULL);
cl_engine_free(engine);
}
pthread_join(accept_th, NULL);
fds_free(fds);
pthread_mutex_destroy(fds->buf_mutex);
pthread_cond_destroy(&acceptdata.cond_nfds);
#ifdef _WIN32
CloseHandle(event_wake_accept);
CloseHandle(event_wake_recv);
#else
close(acceptdata.syncpipe_wake_accept[1]);
close(acceptdata.syncpipe_wake_recv[1]);
#endif
if (dbstat.entries)
cl_statfree(&dbstat);
if (sd_listen_fds(0) == 0) {
/* only close the sockets, when not using systemd socket activation */
logg("*Shutting down the main socket%s.\n", (nsockets > 1) ? "s" : "");
for (i = 0; i < nsockets; i++)
shutdown(socketds[i], 2);
}
if ((opt = optget(opts, "PidFile"))->enabled) {
if (unlink(opt->strarg) == -1)
logg("!Can't unlink the pid file %s\n", opt->strarg);
else
logg("Pid file removed.\n");
}
time(¤t_time);
logg("--- Stopped at %s", cli_ctime(¤t_time, timestr, sizeof(timestr)));
return ret;
}
将监听的fd添加到acceptdata.fds
main=>recvloop=>fds_add
int fds_add(struct fd_data *data, int fd, int listen_only, int timeout)
{
struct fd_buf *buf;
unsigned n;
if (fd < 0) {//fd合法检查
logg("!add_fd: invalid fd passed to add_fd\n");
return -1;
}
/* we may already have this fd, if
* the old FD got closed, and the kernel reused the FD */
for (n = 0; n < data->nfds; n++)//检查内核是否重用了fd,并累加已有多少个fd,为下面realloc做准备
if (data->buf[n].fd == fd) {
/* clear stale data in buffer */
if (buf_init(&data->buf[n], listen_only, timeout) < 0)
return -1;
return 0;
}
n++;
buf = realloc(data->buf, n * sizeof(*buf));//分配缓冲区对象
if (!buf) {
logg("!add_fd: Memory allocation failed for fd_buf\n");
return -1;
}
data->buf = buf;
data->nfds = n;
data->buf[n - 1].buffer = NULL;
if (buf_init(&data->buf[n - 1], listen_only, timeout) < 0)//给每个fd分配缓冲区PATH_MAX + 8 + 1
return -1;
data->buf[n - 1].fd = fd;//保存fd,在acceptloop_th中会用到
return 0;
}
main=>recvloop=>acceptloop_th:
static void *acceptloop_th(void *arg)
{
char buff[BUFFSIZE + 1];
size_t i;
struct acceptdata *data = (struct acceptdata *)arg;
struct fd_data *fds = &data->fds;//监听端口的fd数组
struct fd_data *recv_fds = &data->recv_fds;//接受连接的fd数组
int max_queue = data->max_queue;
int commandtimeout = data->commandtimeout;
pthread_mutex_lock(fds->buf_mutex);
for (;;) {
/* Block waiting for data to become available for reading */
int new_sd = fds_poll_recv(fds, -1, 0, event_wake_accept);//使用poll机制来处理fd的读写事件
#ifdef _WIN32
ResetEvent(event_wake_accept);
#endif
/* TODO: what about sockets that get rm-ed? */
if (!fds->nfds) {
/* no more sockets to poll, all gave an error */
logg("!Main socket gone: fatal\n");
break;
}
if (new_sd == -1 && errno != EINTR) {
logg("!Failed to poll sockets, fatal\n");
pthread_mutex_lock(&exit_mutex);
progexit = 1;
pthread_mutex_unlock(&exit_mutex);
break;
}
/* accept() loop */
for (i = 0; i < fds->nfds && new_sd >= 0; i++) {//循环accept连接
struct fd_buf *buf = &fds->buf[i];
if (!buf->got_newdata)
continue;
#ifndef _WIN32
if (buf->fd == data->syncpipe_wake_accept[0]) {
/* dummy sync pipe, just to wake us */
if (read(buf->fd, buff, sizeof(buff)) < 0) {
logg("^Syncpipe read failed\n");
}
continue;
}
#endif
if (buf->got_newdata == -1) {
logg("$Acceptloop closed FD: %d\n", buf->fd);
shutdown(buf->fd, 2);
closesocket(buf->fd);
buf->fd = -1;
continue;
}
/* don't accept unlimited number of connections, or
* we'll run out of file descriptors */
pthread_mutex_lock(recv_fds->buf_mutex);
while (recv_fds->nfds > (unsigned)max_queue) {
pthread_mutex_lock(&exit_mutex);
if (progexit) {
pthread_mutex_unlock(&exit_mutex);
break;
}
pthread_mutex_unlock(&exit_mutex);
pthread_cond_wait(&data->cond_nfds, recv_fds->buf_mutex);
}
pthread_mutex_unlock(recv_fds->buf_mutex);
pthread_mutex_lock(&exit_mutex);
if (progexit) {
pthread_mutex_unlock(&exit_mutex);
break;
}
pthread_mutex_unlock(&exit_mutex);
/* listen only socket */
new_sd = accept(fds->buf[i].fd, NULL, NULL);//accept连接
if (new_sd >= 0) {
int ret, flags;
#ifdef F_GETFL
flags = fcntl(new_sd, F_GETFL, 0);
if (flags != -1) {
if (fcntl(new_sd, F_SETFL, flags | O_NONBLOCK) == -1) {
logg("^Can't set socket to nonblocking mode, errno %d\n",
errno);
}
} else {
logg("^Can't get socket flags, errno %d\n", errno);
}
#else
logg("^Nonblocking sockets not available!\n");
#endif
logg("$Got new connection, FD %d\n", new_sd);
pthread_mutex_lock(recv_fds->buf_mutex);
ret = fds_add(recv_fds, new_sd, 0, commandtimeout);//将新连接添加到recv_fds中
pthread_mutex_unlock(recv_fds->buf_mutex);
if (ret == -1) {
logg("!fds_add failed\n");
closesocket(new_sd);
continue;
}
/* notify recvloop */
#ifdef _WIN32
SetEvent(event_wake_recv);
#else
if (write(data->syncpipe_wake_recv[1], "", 1) == -1) {//通知recvloop
logg("!write syncpipe failed\n");
continue;
}
#endif
} else if (errno != EINTR) {
/* very bad - need to exit or restart */
logg("!accept() failed: %s\n", cli_strerror(errno, buff, BUFFSIZE));
/* give the poll loop a chance to close disconnected FDs */
break;
}
}
/* handle progexit */
pthread_mutex_lock(&exit_mutex);
if (progexit) {
pthread_mutex_unlock(&exit_mutex);
break;
}
pthread_mutex_unlock(&exit_mutex);
}
pthread_mutex_unlock(fds->buf_mutex);
if (sd_listen_fds(0) == 0) {
/* only close the sockets, when not using systemd socket activation */
for (i = 0; i < fds->nfds; i++) {
if (fds->buf[i].fd == -1)
continue;
logg("$Shutdown: closed fd %d\n", fds->buf[i].fd);
shutdown(fds->buf[i].fd, 2);
closesocket(fds->buf[i].fd);
}
}
fds_free(fds);
pthread_mutex_destroy(fds->buf_mutex);
pthread_mutex_lock(&exit_mutex);
progexit = 1;
pthread_mutex_unlock(&exit_mutex);
#ifdef _WIN32
SetEvent(event_wake_recv);
#else
if (write(data->syncpipe_wake_recv[1], "", 1) < 0) {
logg("$Syncpipe write failed\n");
}
#endif
return NULL;
}
我们继续看recvloop 的for循环代码:
main=>recvloop:
for (;;) {
int new_sd;
/* Block waiting for connection on any of the sockets */
pthread_mutex_lock(fds->buf_mutex);
fds_cleanup(fds);//fds为接收端的fd
/* signal that we can accept more connections */
if (fds->nfds <= (unsigned)max_queue)//如果未达到阈值,通知accept线程继续接受新连接
pthread_cond_signal(&acceptdata.cond_nfds);
new_sd = fds_poll_recv(fds, selfchk ? (int)selfchk : -1, 1, event_wake_recv);//获取新连接的fd
#ifdef _WIN32
ResetEvent(event_wake_recv);
#else
if (!fds->nfds) {
/* at least the dummy/sync pipe should have remained */
logg("!All recv() descriptors gone: fatal\n");
pthread_mutex_lock(&exit_mutex);
progexit = 1;
pthread_mutex_unlock(&exit_mutex);
pthread_mutex_unlock(fds->buf_mutex);
break;
}
#endif
if (new_sd == -1 && errno != EINTR) {
logg("!Failed to poll sockets, fatal\n");
pthread_mutex_lock(&exit_mutex);
progexit = 1;
pthread_mutex_unlock(&exit_mutex);
}
if (fds->nfds) i = (rr_last + 1) % fds->nfds;
for (j = 0; j < fds->nfds && new_sd >= 0; j++, i = (i + 1) % fds->nfds) {
size_t pos = 0;
int error = 0;
struct fd_buf *buf = &fds->buf[i];//找到关联的buf,内部的buffer有接收到的内容。例如"zVERSION"
if (!buf->got_newdata)
continue;
#ifndef _WIN32
if (buf->fd == acceptdata.syncpipe_wake_recv[0]) {
/* dummy sync pipe, just to wake us */
if (read(buf->fd, buff, sizeof(buff)) < 0) {
logg("^Syncpipe read failed\n");
}
continue;
}
#endif
if (buf->got_newdata == -1) {
if (buf->mode == MODE_WAITREPLY) {
logg("$mode WAIT_REPLY -> closed\n");
buf->fd = -1;
thrmgr_group_terminate(buf->group);
thrmgr_group_finished(buf->group, EXIT_ERROR);
continue;
} else {
logg("$client read error or EOF on read\n");
error = 1;
}
}
if (buf->fd != -1 && buf->got_newdata == -2) {
logg("$Client read timed out\n");
mdprintf(buf->fd, "COMMAND READ TIMED OUT\n");
error = 1;
}
rr_last = i;
if (buf->mode == MODE_WAITANCILL) {
buf->mode = MODE_COMMAND;
logg("$mode -> MODE_COMMAND\n");
}
while (!error && buf->fd != -1 && buf->buffer && pos < buf->off &&
buf->mode != MODE_WAITANCILL) {
client_conn_t conn;
const char *cmd = NULL;
int rc;
/* New data available to read on socket. */
memset(&conn, 0, sizeof(conn));
conn.scanfd = buf->recvfd;
buf->recvfd = -1;
conn.sd = buf->fd;//这个fd为,接收连接的fd,版本信息通过它返回
conn.options = &options;
conn.opts = opts;
conn.thrpool = thr_pool;
conn.engine = engine;
conn.group = buf->group;
conn.id = buf->id;
conn.quota = buf->quota;
conn.filename = buf->dumpname;
conn.mode = buf->mode;
conn.term = buf->term;
/* Parse & dispatch command */
cmd = parse_dispatch_cmd(&conn, buf, &pos, &error, opts, readtimeout);
......
}
main=>recvloop=>parse_dispatch_cmd:
static const char *parse_dispatch_cmd(client_conn_t *conn, struct fd_buf *buf, size_t *ppos, int *error, const struct optstruct *opts, int readtimeout)
{
const char *cmd = NULL;
int rc;
size_t cmdlen;
char term;
int oldstyle;
size_t pos = *ppos;
/* Parse & dispatch commands */
while ((conn->mode == MODE_COMMAND) &&
(cmd = get_cmd(buf, pos, &cmdlen, &term, &oldstyle)) != NULL) {//解析命令
const char *argument;
enum commands cmdtype;
if (conn->group && oldstyle) {
logg("$Received oldstyle command inside IDSESSION: %s\n", cmd);
conn_reply_error(conn, "Only nCMDS\\n and zCMDS\\0 are accepted inside IDSESSION.");
*error = 1;
break;
}
cmdtype = parse_command(cmd, &argument, oldstyle);
logg("$got command %s (%u, %u), argument: %s\n",
cmd, (unsigned)cmdlen, (unsigned)cmdtype, argument ? argument : "");//调试日志,如果logg_verbose>=2 就会打印
if (cmdtype == COMMAND_FILDES) {
if (buf->buffer + buf->off <= cmd + strlen("FILDES\n")) {
/* we need the extra byte from recvmsg */
conn->mode = MODE_WAITANCILL;
buf->mode = MODE_WAITANCILL;
/* put term back */
buf->buffer[pos + cmdlen] = term;
cmdlen = 0;
logg("$RECVTH: mode -> MODE_WAITANCILL\n");
break;
}
/* eat extra \0 for controlmsg */
cmdlen++;
logg("$RECVTH: FILDES command complete\n");
}
conn->term = term;
buf->term = term;
if ((rc = execute_or_dispatch_command(conn, cmdtype, argument)) < 0) {//执行会分发命令,重点函数
logg("!Command dispatch failed\n");
if (rc == -1 && optget(opts, "ExitOnOOM")->enabled) {
pthread_mutex_lock(&exit_mutex);
progexit = 1;
pthread_mutex_unlock(&exit_mutex);
}
*error = 1;
}
......
}
main=>recvloop=>parse_dispatch_cmd=>get_cmd:
/*
* zCOMMANDS are delimited by \0
* nCOMMANDS are delimited by \n
* Old-style non-prefixed commands are one packet, optionally delimited by \n,
* with trailing \r|\n ignored
*/
static const char *get_cmd(struct fd_buf *buf, size_t off, size_t *len, char *term, int *oldstyle)
{
char *pos;
if (!buf->off || off >= buf->off) {
*len = 0;
return NULL;
}
*term = '\n';
switch (buf->buffer[off]) {
/* commands terminated by delimiters */
case 'z'://'z' 表示时字符串,以\0结尾
*term = '\0';
/* fall-through */
case 'n':
pos = memchr(buf->buffer + off, *term, buf->off - off);
if (!pos) {
/* we don't have another full command yet */
*len = 0;
return NULL;
}
*pos = '\0';
if (*term) {
*len = cli_chomp(buf->buffer + off);
} else {
*len = pos - buf->buffer - off;
}
*oldstyle = 0;
return buf->buffer + off + 1;
default:
/* one packet = one command */
if (off)
return NULL;
pos = memchr(buf->buffer, '\n', buf->off);
if (pos) {
*len = pos - buf->buffer;
*pos = '\0';
} else {
*len = buf->off;
buf->buffer[buf->off] = '\0';
}
cli_chomp(buf->buffer);
*oldstyle = 1;
return buf->buffer;
}
}
main=>recvloop=>parse_dispatch_cmd=>parse_command:
#define CMD7 "VERSION"
static struct {
const char *cmd;
const size_t len;
enum commands cmdtype;
int need_arg;
int support_old;
int enabled;
} commands[] = {
{CMD1, sizeof(CMD1) - 1, COMMAND_SCAN, 1, 1, 0},
{CMD3, sizeof(CMD3) - 1, COMMAND_SHUTDOWN, 0, 1, 0},
{CMD4, sizeof(CMD4) - 1, COMMAND_RELOAD, 0, 1, 0},
{CMD5, sizeof(CMD5) - 1, COMMAND_PING, 0, 1, 0},
{CMD6, sizeof(CMD6) - 1, COMMAND_CONTSCAN, 1, 1, 0},
/* must be before VERSION, because they share common prefix! */
{CMD18, sizeof(CMD18) - 1, COMMAND_COMMANDS, 0, 0, 1},
{CMD7, sizeof(CMD7) - 1, COMMAND_VERSION, 0, 1, 1},//我们的例子中返回COMMAND_VERSION
{CMD10, sizeof(CMD10) - 1, COMMAND_END, 0, 0, 1},
{CMD11, sizeof(CMD11) - 1, COMMAND_SHUTDOWN, 0, 1, 1},
{CMD13, sizeof(CMD13) - 1, COMMAND_MULTISCAN, 1, 1, 1},
{CMD14, sizeof(CMD14) - 1, COMMAND_FILDES, 0, 1, FEATURE_FDPASSING},
{CMD15, sizeof(CMD15) - 1, COMMAND_STATS, 0, 0, 1},
{CMD16, sizeof(CMD16) - 1, COMMAND_IDSESSION, 0, 0, 1},
{CMD17, sizeof(CMD17) - 1, COMMAND_INSTREAM, 0, 0, 1},
{CMD19, sizeof(CMD19) - 1, COMMAND_DETSTATSCLEAR, 0, 1, 1},
{CMD20, sizeof(CMD20) - 1, COMMAND_DETSTATS, 0, 1, 1},
{CMD21, sizeof(CMD21) - 1, COMMAND_ALLMATCHSCAN, 1, 0, 1}};
enum commands parse_command(const char *cmd, const char **argument, int oldstyle)
{
size_t i;
*argument = NULL;
for (i = 0; i < sizeof(commands) / sizeof(commands[0]); i++) {
const size_t len = commands[i].len;
if (!strncmp(cmd, commands[i].cmd, len)) {
const char *arg = cmd + len;
if (commands[i].need_arg) {
if (!*arg) { /* missing argument */
logg("$Command %s missing argument!\n", commands[i].cmd);
return COMMAND_UNKNOWN;
}
*argument = arg + 1;
} else {
if (*arg) { /* extra stuff after command */
logg("$Command %s has trailing garbage!\n", commands[i].cmd);
return COMMAND_UNKNOWN;
}
*argument = NULL;
}
if (oldstyle && !commands[i].support_old) {
logg("$Command sent as old-style when not supported: %s\n", commands[i].cmd);
return COMMAND_UNKNOWN;
}
return commands[i].cmdtype;
}
}
return COMMAND_UNKNOWN;
}
根据返回值cmdtype,进一步调用execute_or_dispatch_command处理。
main=>recvloop=>parse_dispatch_cmd=>execute_or_dispatch_command:
/* returns:
* <0 for error
* -1 out of memory
* -2 other
* 0 for async dispatched
* 1 for command completed (connection can be closed)
*/
int execute_or_dispatch_command(client_conn_t *conn, enum commands cmd, const char *argument)
{
int desc = conn->sd;
char term = conn->term;
const struct cl_engine *engine = conn->engine;
/* execute commands that can be executed quickly on the recvloop thread,
* these must:
* - not involve any operation that can block for a long time, such as disk
* I/O
* - send of atomic message is allowed.
* Dispatch other commands */
if (conn->group) {
switch (cmd) {
case COMMAND_FILDES:
case COMMAND_SCAN:
case COMMAND_END:
case COMMAND_INSTREAM:
case COMMAND_INSTREAMSCAN:
case COMMAND_VERSION:
case COMMAND_PING:
case COMMAND_STATS:
case COMMAND_COMMANDS:
/* These commands are accepted inside IDSESSION */
break;
default:
/* these commands are not recognized inside an IDSESSION */
conn_reply_error(conn, "Command invalid inside IDSESSION.");
logg("$SESSION: command is not valid inside IDSESSION: %d\n", cmd);
conn->group = NULL;
return 1;
}
}
switch (cmd) {
case COMMAND_SHUTDOWN:
pthread_mutex_lock(&exit_mutex);
progexit = 1;
pthread_mutex_unlock(&exit_mutex);
return 1;
case COMMAND_RELOAD:
pthread_mutex_lock(&reload_mutex);
reload = 1;
pthread_mutex_unlock(&reload_mutex);
mdprintf(desc, "RELOADING%c", term);
/* we set reload flag, and we'll reload before closing the
* connection */
return 1;
case COMMAND_PING:
if (conn->group)
mdprintf(desc, "%u: PONG%c", conn->id, term);
else
mdprintf(desc, "PONG%c", term);
return conn->group ? 0 : 1;
case COMMAND_VERSION: {//我们会执行此处的代码。
if (conn->group)
mdprintf(desc, "%u: ", conn->id);
print_ver(desc, conn->term, engine);
return conn->group ? 0 : 1;
}
......
}
}
main=>recvloop=>parse_dispatch_cmd=>execute_or_dispatch_command=>print_ver:
static int print_ver(int desc, char term, const struct cl_engine *engine)
{
uint32_t ver;
ver = cl_engine_get_num(engine, CL_ENGINE_DB_VERSION, NULL);//获取引擎版本信息
if (ver) {
char timestr[32];
const char *tstr;
time_t t;
t = cl_engine_get_num(engine, CL_ENGINE_DB_TIME, NULL);
tstr = cli_ctime(&t, timestr, sizeof(timestr));
/* cut trailing \n */
timestr[strlen(tstr) - 1] = '\0';
return mdprintf(desc, "ClamAV %s/%u/%s%c", get_version(), (unsigned int)ver, tstr, term);//将版本信息发送给clamdscan
}
return mdprintf(desc, "ClamAV %s%c", get_version(), term);
}
main=>recvloop=>parse_dispatch_cmd=>execute_or_dispatch_command=>print_ver=>mdprintf:
int mdprintf(int desc, const char *str, ...)
{
......
while (todo > 0) {
ret = send(desc, buff, bytes, 0);//发送版本信息
if (ret < 0) {
struct timeval tv;
if (errno != EWOULDBLOCK)
break;
/* didn't send anything yet */
#ifdef CL_THREAD_SAFE
pthread_mutex_unlock(&mdprintf_mutex);
#endif
tv.tv_sec = 0;
tv.tv_usec = mprintf_send_timeout * 1000;
do {
fd_set wfds;
FD_ZERO(&wfds);
FD_SET(desc, &wfds);
ret = select(desc + 1, NULL, &wfds, NULL, &tv);
} while (ret < 0 && errno == EINTR);
#ifdef CL_THREAD_SAFE
pthread_mutex_lock(&mdprintf_mutex);
#endif
if (!ret) {
/* timed out */
ret = -1;
break;
}
} else {
todo -= ret;
buff += ret;
}
}
#ifdef CL_THREAD_SAFE
pthread_mutex_unlock(&mdprintf_mutex);
#endif
if (len > sizeof(buffer))
free(abuffer);
return ret < 0 ? -1 : bytes;
}
整个过程基本就这样的一个流程。