Clamav杀毒软件源码分析笔记[八]
刺猬@http://blog.csdn.net/littlehedgehog
[command]
上回说到主循环(accept_th),这是一个死循环,因为我们Clamd在没有什么特殊的情况下是一直阻塞地苦苦等待在等待有客户端发出请求,然后安排好线程派发(dispatch)工作,接着我们的注意力便专注于线程的运作,这里的command是我们的重点研究对象.
说来command这个函数集众功能于一身,貌似强大无比,但自然也是个程序逻辑中转站,下面来看看里面的部分代码:
- retval = poll_fd(desc, timeout);
这里采用了多路复用的代码, 说来多路复用本身还有两套实现方案,这是unix阵营分裂的产物吧. 我这里只看看select,有如下资料:
select系统调用是用来让我们的程序监视多个文件句柄(file descriptor)的状态变化的。程序会停在select这里等待,直到被监视的文件句柄有某一个或多个发生了状态改变。
文件在句柄在Linux里很多,如果你man某个函数,在函数返回值部分说到成功后有一个文件句柄被创建的都是的,如man socket可以看到“On success, a file descriptor for the new socket is returned.”而man 2 open可以看到“open() and creat() return the new file descriptor”,其实文件句柄就是一个整数,看socket函数的声明就明白了:
int socket(int domain, int type, int protocol);
select函数原型如下:
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
函数的最后一个参数timeout显然是一个超时时间值,其类型是struct timeval *,即一个struct timeval结构的变量的指针,所以我们在程序里要申明一个struct timeval tv;然后把变量tv的地址&tv传递给select函数。struct timeval结构如下:
- struct timeval {
- long tv_sec;
- long tv_usec;
- };
第2、3、4三个参数是一样的类型: fd_set *,即我们在程序里要申明几个fd_set类型的变量,比如rdfds, wtfds, exfds,然后把这个变量的地址&rdfds, &wtfds, &exfds 传递给select函数。这三个参数都是一个句柄的集合,第一个rdfds是用来保存这样的句柄的:当句柄的状态变成可读的时系统就会告诉select函数返回,同理第二个wtfds是指有句柄状态变成可写的时系统就会告诉select函数返回,同理第三个参数exfds是特殊情况,即句柄上有特殊情况发生时系统会告诉select函数返回。特殊情况比如对方通过一个socket句柄发来了紧急数据。如果我们程序里只想检测某个socket是否有数据可读,我们可以这样:
- fd_set rdfds;
- struct timeval tv;
- int ret;
- FD_ZERO(&rdfds);
- FD_SET(socket, &rdfds);
- tv.tv_sec = 1;
- tv.tv_usec = 500;
- ret = select(socket + 1, &rdfds, NULL, NULL, &tv);
- if(ret < 0) perror("select");
- else if(ret == 0) printf("超时/n");
- else {
- printf("ret=%d/n", ret);
-
- if(FD_ISSET(socket, &rdfds)) {
-
- recv(...);
- }
- }
- int sa, sb, sc;
- sa = socket(...);
- connect(sa,...);
- sb = socket(...);
- connect(sb,...);
- sc = socket(...);
- connect(sc,...);
- FD_SET(sa, &rdfds);
- FD_SET(sb, &rdfds);
- FD_SET(sc, &rdfds);
- 在使用select函数之前,一定要找到3个句柄中的最大值是哪个,我们一般定义一个变量来保存最大值,取得最大socket值如下:
- int maxfd = 0;
- if(sa > maxfd) maxfd = sa;
- if(sb > maxfd) maxfd = sb;
- if(sc > maxfd) maxfd = sc;
- 然后调用select函数:
- ret = select(maxfd + 1, &rdfds, NULL, NULL, &tv);
- 同样的道理,如果我们要检测用户是否按了键盘进行输入,我们就应该把标准输入0这个句柄放到select里来检测,如下:
- FD_ZERO(&rdfds);
- FD_SET(0, &rdfds);
- tv.tv_sec = 1;
- tv.tv_usec = 0;
- ret = select(1, &rdfds, NULL, NULL, &tv);
- if(ret < 0) perror("select");
- else if(ret == 0) printf("超时/n");
- else {
- scanf("%s", buf);
- }
貌似把多路复用说的过多,但这文章主要是为我读书笔记所用,贴上代码,方便我后来温故时习也.
多路复用只是一个开端,打劫之前,踩点所用,下面我们才来看看用户会告知什么消息. 这就比如说,我们从主进程中截获用户一小道消息,只是可能客户端有任务而已,遂分配线程确认工作.
确认工作:
- while ((bread = readsock(desc, buff, 1024)) == -1 && errno == EINTR);
只此一句,绝无多言,这里通过read已连接的套接口描述符,我们将客户请求装入buffer缓存区中. 这里的readsock貌似库函数,其实不然,里面封装着recvmsg,在这里就毋庸多言了. 这里用户请求已截获至buffer缓存,接下来就是分析buffer中所含命令了.好在客户/服务通信暗号也只是我们一家所定,还好不复杂,只需字符串一一匹配即可. 如下代码:
- if (!strncmp(buff, CMD1, strlen(CMD1)))
- {
- if (scan(buff + strlen(CMD1) + 1, NULL, root, limits, options, copt, desc, 0) == -2)
- if (cfgopt(copt, "ExitOnOOM"))
- return COMMAND_SHUTDOWN;
- }
搜寻buffer中是否含CMD1字符串,作者也很好心的加了注释CMD1表示命令"扫描". 里面绝大部分都是客户端的扫描要求, 但也有其它的一些命令字符串,比如:
- else if (!strncmp(buff, CMD5, strlen(CMD5)))
- {
- mdprintf(desc, "PONG/n");
- }
客户端发送"ping",服务端要应答"pong",这样表明连接畅通. 像这样简单的处理恐怕也只有这一项功能了.回头我们还是来看看病毒扫描吧.