clamav中clamdscan --version 不生效

自己使用源码编译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(&current_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(&current_time);
    logg("--- Stopped at %s", cli_ctime(&current_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;
}

整个过程基本就这样的一个流程。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值