刺猬@http://blog.csdn.net/littlehedgehog
[获取配置文件信息]
说来比较打击积极性的是我们还得做一番比较麻烦的但是却又没多少挑战性的事业,因为如果clamd这个服务端全部都用命令行来指定信息的话,Clamav估计会没有市场,因为打字就会累死管理员。我们要设置比如像多久升级一次啊,升级服务器在哪里下载这些东西这些乱七八糟的东西,我们都要给程序指定。去看看配置文件吧,想想看如果那些都要靠人来输入,这容易让人抓狂。但是配置文件这个是必须要看的。我们捡一个例子来说:
- # Maximal size of the log file.
- # Value of 0 disables the limit.
- # You may use 'M' or 'm' for megabytes (1M = 1m = 1048576 bytes)
- # and 'K' or 'k' for kilobytes (1K = 1k = 1024 bytes). To specify the size
- # in bytes just don't use modifiers.
- # Default: 1M
- #LogFileMaxSize 2M
就看最后一行了吧,这里是设置log日志文件的大小,参数就是LogFileMaxSize,而参数值就是2M,中间用空格隔开。这里设置log文件因为clamd是后台作业的,它就像哑巴一样,没有终端可以提供给它打印信息。当然它在后台还是可以打开文件的,所以就让它往log文件里面写内容,我们事后来查。
这里是分析的主函数,贴着方便分析。
- //分析配置文件 message表示是否要显示信息
- struct cfgstruct *parsecfg(const char *cfgfile, int messages)
- {
- char buff[LINE_LENGTH], *name, *arg;
- FILE *fs;
- int line = 0, i, found, ctype, calc;
- struct cfgstruct *copt = NULL;
- struct cfgoption *pt;
- struct cfgoption cfg_options[] =
- {
- {"LogFile", OPT_STR
- },
- {"LogFileUnlock", OPT_NOARG},
- {"LogFileMaxSize", OPT_COMPSIZE},
- {"LogTime", OPT_NOARG},
- {"LogClean", OPT_NOARG},
- {"LogVerbose", OPT_NOARG}, /* clamd + freshclam */
- {"LogSyslog", OPT_NOARG},
- {"LogFacility", OPT_STR},
- {"PidFile", OPT_STR},
- {"TemporaryDirectory", OPT_STR},
- {"DisableDefaultScanOptions", OPT_NOARG},
- {"ScanPE", OPT_NOARG},
- {"DetectBrokenExecutables", OPT_NOARG},
- {"ScanMail", OPT_NOARG},
- {"MailFollowURLs", OPT_NOARG},
- {"ScanHTML", OPT_NOARG},
- {"ScanOLE2", OPT_NOARG},
- {"ScanArchive", OPT_NOARG},
- {"ScanRAR", OPT_NOARG},
- {"ArchiveMaxFileSize", OPT_COMPSIZE},
- {"ArchiveMaxRecursion", OPT_NUM},
- {"ArchiveMaxFiles", OPT_NUM},
- {"ArchiveMaxCompressionRatio", OPT_NUM},
- {"ArchiveLimitMemoryUsage", OPT_NOARG},
- {"ArchiveBlockEncrypted", OPT_NOARG},
- {"ArchiveBlockMax", OPT_NOARG},
- {"DataDirectory", OPT_STR}, /* obsolete */
- {"DatabaseDirectory", OPT_STR}, /* clamd + freshclam */
- {"TCPAddr", OPT_STR},
- {"TCPSocket", OPT_NUM},
- {"LocalSocket", OPT_STR},
- {"MaxConnectionQueueLength", OPT_NUM},
- {"StreamMaxLength", OPT_COMPSIZE},
- {"StreamMinPort", OPT_NUM},
- {"StreamMaxPort", OPT_NUM},
- {"MaxThreads", OPT_NUM},
- {"ReadTimeout", OPT_NUM},
- {"IdleTimeout", OPT_NUM},
- {"MaxDirectoryRecursion", OPT_NUM},
- {"FollowDirectorySymlinks", OPT_NOARG},
- {"FollowFileSymlinks", OPT_NOARG},
- {"ExitOnOOM", OPT_NOARG},
- {"Foreground", OPT_NOARG}, /* clamd + freshclam */
- {"Debug", OPT_NOARG},
- {"LeaveTemporaryFiles", OPT_NOARG},
- {"FixStaleSocket", OPT_NOARG},
- {"User", OPT_STR},
- {"AllowSupplementaryGroups", OPT_NOARG},
- {"SelfCheck", OPT_NUM},
- {"VirusEvent", OPT_FULLSTR},
- {"ClamukoScanOnLine", OPT_NOARG}, /* old name */
- {"ClamukoScanOnAccess", OPT_NOARG},
- {"ClamukoScanOnOpen", OPT_NOARG},
- {"ClamukoScanOnClose", OPT_NOARG},
- {"ClamukoScanOnExec", OPT_NOARG},
- {"ClamukoIncludePath", OPT_STR},
- {"ClamukoExcludePath", OPT_STR},
- {"ClamukoMaxFileSize", OPT_COMPSIZE},
- {"ClamukoScanArchive", OPT_NOARG},
- {"DatabaseOwner", OPT_STR}, /* freshclam */
- {"Checks", OPT_NUM}, /* freshclam */
- {"UpdateLogFile", OPT_STR}, /* freshclam */
- {"DNSDatabaseInfo", OPT_STR}, /* freshclam */
- {"DatabaseMirror", OPT_STR}, /* freshclam */
- {"MaxAttempts", OPT_NUM}, /* freshclam */
- {"HTTPProxyServer", OPT_STR}, /* freshclam */
- {"HTTPProxyPort", OPT_NUM}, /* freshclam */
- {"HTTPProxyUsername", OPT_STR}, /* freshclam */
- {"HTTPProxyPassword", OPT_STR}, /* freshclam */
- {"NotifyClamd", OPT_OPTARG}, /* freshclam */
- {"OnUpdateExecute", OPT_FULLSTR}, /* freshclam */
- {"OnErrorExecute", OPT_FULLSTR}, /* freshclam */
- {"LocalIPAddress", OPT_STR}, /* freshclam */
- {0, 0}
- };
- if ((fs = fopen(cfgfile, "r")) == NULL)
- return NULL;
- while (fgets(buff, LINE_LENGTH, fs)) //每次读一行
- {
- line++;
- if (buff[0] == '#')
- continue;
- if (!strncmp("Example", buff, 7)) //clamd.conf 里面本来有一个example,提供给用户照着样子设置 同时就要求用户设置好了后要把example注释掉(前面加一个#)
- {
- if (messages)
- fprintf(stderr, "ERROR: Please edit the example config file %s./n", cfgfile);
- fclose(fs);
- return NULL;
- }
- if ((name = cli_strtok(buff, 0, " /r/n")))
- {
- arg = cli_strtok(buff, 1, " /r/n");
- found = 0;
- for (i = 0; ; i++)
- {
- pt = &cfg_options[i]; //从选项数组中依次寻找
- if (pt->name)
- {
- if (!strcmp(name, pt->name))
- {
- found = 1;
- switch (pt->argtype)
- {
- case OPT_STR: //字符串参数
- if (!arg)
- {
- if (messages)
- fprintf(stderr, "ERROR: Parse error at line %d: Option %s requires string as argument./n", line, name);
- fclose(fs);
- return NULL;
- }
- copt = regcfg(copt, name, arg, 0);
- break;
- case OPT_FULLSTR: //占一行的字符串参数
- if (!arg)
- {
- if (messages)
- fprintf(stderr, "ERROR: Parse error at line %d: Option %s requires string as argument./n", line, name);
- fclose(fs);
- return NULL;
- }
- /* FIXME: this one is an ugly hack of the above case */
- free(arg);
- arg = strstr(buff, " ");
- arg = strdup(++arg);
- copt = regcfg(copt, name, arg, 0);
- break;
- case OPT_NUM: //参数是数字
- if (!arg || !isnumb(arg))
- {
- if (messages)
- fprintf(stderr, "ERROR: Parse error at line %d: Option %s requires numerical argument./n", line, name);
- fclose(fs);
- return NULL;
- }
- copt = regcfg(copt, name, NULL, atoi(arg));
- free(arg);
- break;
- case OPT_COMPSIZE: //表示转换Kb和Mb到byte 这里是处理用户设置的一些文件大小选项
- if (!arg)
- {
- if (messages)
- fprintf(stderr, "ERROR: Parse error at line %d: Option %s requires argument./n", line, name);
- fclose(fs);
- return NULL;
- }
- ctype = tolower(arg[strlen(arg) - 1]); //全设置为小写 方便下面比较
- if (ctype == 'm' || ctype == 'k')
- {
- char *cpy = (char *) mcalloc(strlen(arg), sizeof(char));
- strncpy(cpy, arg, strlen(arg) - 1);
- if (!isnumb(cpy)) //如果不是数字
- {
- if (messages)
- fprintf(stderr, "ERROR: Parse error at line %d: Option %s requires numerical (raw/K/M) argument./n", line, name);
- fclose(fs);
- return NULL;
- }
- if (ctype == 'm') //mb转化为b
- calc = atoi(cpy) * 1024 * 1024;
- else
- calc = atoi(cpy) * 1024;
- free(cpy);
- }
- else
- {
- if (!isnumb(arg))
- {
- if (messages)
- fprintf(stderr, "ERROR: Parse error at line %d: Option %s requires numerical (raw/K/M) argument./n", line, name);
- fclose(fs);
- return NULL;
- }
- calc = atoi(arg);
- }
- copt = regcfg(copt, name, NULL, calc);
- free(arg);
- break;
- case OPT_NOARG:
- if (arg)
- {
- if (messages)
- fprintf(stderr, "ERROR: Parse error at line %d: Option %s doesn't support arguments (got '%s')./n", line, name, arg);
- fclose(fs);
- return NULL;
- }
- copt = regcfg(copt, name, NULL, 0);
- break;
- case OPT_OPTARG:
- copt = regcfg(copt, name, arg, 0);
- break;
- default:
- if (messages)
- fprintf(stderr, "ERROR: Parse error at line %d: Option %s is of unknown type %d/n", line, name, pt->argtype);
- free(name);
- free(arg);
- break;
- }
- }
- }
- else
- break;
- }
- if (!found)
- {
- if (messages)
- fprintf(stderr, "ERROR: Parse error at line %d: Unknown option %s./n", line, name);
- fclose(fs);
- return NULL;
- }
- }
- }
- fclose(fs);
- return copt;
- }
这里作者在解析配置文件时用了自己的一个函数:char *cli_strtok(const char *line, int fieldno, const char *delim) 注意这个函数的用意是指在line中查找第fieldno个指定字符分解符delim,这里的delim是个字符串。为什么不用strtok呢,因为strtok只能提供分界字符,而这里我们需要的是多个可能的分界符。仍举刚才的例子: LogFileMaxSize 2M ,这里我们要挖掘出参数LogFileMaxSize ,参数值2M,中间可能是隔一个空格,也可能是其它字符,比如’/r'等,所以我们传递进去分界符是" /r/n". 看看源代码吧
- /*
- * char *cli_strok(const char *line, int fieldno, char *delim)
- * Return a copy of field <fieldno> from the string <line>, where
- * fields are delimited by any char from <delim>, or NULL if <line>
- * doesn't have <fieldno> fields or not enough memory is available.
- * The caller has to free() the result afterwards.
- */
- char *cli_strtok(const char *line, int fieldno, const char *delim)
- {
- int counter = 0, i, j;
- char *buffer = NULL;
- /* step to arg # <fieldno> */
- for (i=0; line[i] && counter != fieldno; i++) //count 是计数器 这个其实不说也知道
- {
- if (strchr(delim, line[i])) //注意 是在delim里面查找当前字符line[i]
- {
- counter++;
- while(line[i+1] && strchr(delim, line[i+1])) //这里是过滤多个 比如你连续输入了多个空格
- {
- i++;
- }
- }
- }
- if (!line[i])
- {
- /* end of buffer before field reached */
- return NULL;
- }
- for (j=i; line[j]; j++) //这里的i已经是空格后面一个字符了 这里我们是为了选取从这个空格到下个空格之间的字符串
- {
- if (strchr(delim, line[j]))
- {
- break;
- }
- }
- if (i == j)
- {
- return NULL;
- }
- buffer = malloc(j-i+1); //开辟空间
- if(!buffer)
- return NULL;
- strncpy(buffer, line+i, j-i);
- buffer[j-i] = '/0';
- return buffer;
- }
同样的在处理配置文件时候我们也是采用了链表的形式,把参数和参数值以及参数类别加入节点在组装成链表. 来先看看节点,这个稍有不同.
- struct cfgstruct
- {
- char *optname; //选项名
- char *strarg; //参数(字符串)
- int numarg; //参数(数字) 参数有字符串和数字两种形式 所以我们要分开装
- struct cfgstruct *nextarg; //下一个参数值
- struct cfgstruct *next; //下一个参数 这两者又是什么关系? I hear you ask 注意 一个参数可能有多个参数值 所以这里作者这样设置
- /*
- --->(参数类型一)--->(参数类型二)--->(参数类型三)....
- |
- |
- 参数一
- |
- |
- 参数二
- 可以这么理解 next是连接参数主干的 而nextarg是分支
- */
- };
因为一个参数可能有多个参数值,所以形成这种变态的树形结构. 但是无伤大雅的,我们接着看如何把节点加入链表,作者命名为register,比较专业...
- struct cfgstruct *regcfg(struct cfgstruct *copt, char *optname, char *strarg, int numarg)
- {
- struct cfgstruct *newnode, *pt;
- newnode = (struct cfgstruct *) mmalloc(sizeof(struct cfgstruct));
- newnode->optname = optname;
- newnode->nextarg = NULL;
- newnode->next = NULL;
- /*下面是根据不同类型参数值(数字还是字符串 To be or Not to be ?)*/
- if (strarg)
- newnode->strarg = strarg;
- else
- {
- newnode->strarg = NULL;
- newnode->numarg = numarg;
- }
- if ((pt = cfgopt(copt, optname))) //在链表中搜索
- {
- while (pt->nextarg) //如果找到了一个和该参数相同类型的节点 那我们加入到它的nextarg中去 在分支上做文章
- pt = pt->nextarg;
- pt->nextarg = newnode;
- return copt;
- }
- else //没找到 在链表主干上添上一笔
- {
- newnode->next = copt;
- return newnode; //特别要注意这个返回值 它表明我们这个链表是个栈链表 也就是所caller里面的那个copt永远都是指向链表开头的 这也就是为什么我们能搜索的原因(cfpopt)
- }
- }
我最后专门强调了这个返回值,因为这个是一个单链表,我们必须要把握住链表的头部,俗话说擒贼先擒王嘛. 比如这里我们要搜索,那肯定要从头部开始了.
- struct cfgstruct *cfgopt(const struct cfgstruct *copt, const char *optname)
- {
- struct cfgstruct *handler;
- handler = (struct cfgstruct *) copt; //从链表开头搜索
- /*为了找到这个参数 我们不惜用上了死循环*/
- while (1)
- {
- if (handler)
- {
- if (handler->optname)
- if (!strcmp(handler->optname, optname)) //参数名相同 表示找到了
- return handler;
- }
- else
- break; //链表为空 说明一个问题——到头了
- handler = handler->next;
- }
- return NULL;
- }