清空哈西表,为下面即将进行的排序工作做好准备。关于哈西表,这是数据结构中常用的一种用来快速排序的结构,如果不清楚,可以参考相关书籍,比如清华的<<数据结构>>教材或者<<数据结构的C++实现>>等书。
if (verbose>1)
{
uname(&system_info);
printf("Webalizer V%s-%s (%s %s) %s ",
version,editlvl,system_info.sysname,
system_info.release,language);
}
这一段,是打印有关系统的信息和webalizer程序的信息(可以参考uname的函数说明)。#ifndef USE_DNS
if (strstr(argv[0],"webazolver")!=0)
{
printf("DNS support not present, aborting... ");
exit(1);
}#endif /* USE_DNS */
这一段,回忆我们在看README文件的时候,曾经提到过可以在编译的时候设置选项开关来设定DNS支持,在源代码中可以看到多次这样的代码段出现,如果不指定DNS支持,这些代码段则会出现(ifdef)或者不出现(ifndef).下面略过这些代码段,不再重复。/* open log file */
if (gz_log)
{
gzlog_fp = gzopen(log_fname,"rb");
if (gzlog_fp==Z_NULL)
{
/* Error: Can't open log file ... */
fprintf(stderr, "%s %s ",msg_log_err,log_fname);
exit(1);
}
}
else
{
if (log_fname)
{
log_fp = fopen(log_fname,"r");
if (log_fp==NULL)
{
/* Error: Can't open log file ... */
fprintf(stderr, "%s %s ",msg_log_err,log_fname);
exit(1);
}
}
}
这一段,回忆在README文件中曾经读到过,如果log文件是gzip压缩格式,则用gzopen函数打开(可以猜想gz***是一套针对gzip压缩格式的实时解压缩函数),如果不是,则用fopen打开。
/* switch directories if needed */
if (out_dir)
{
if (chdir(out_dir) != 0)
{
/* Error: Can't change directory to ... */
fprintf(stderr, "%s %s ",msg_dir_err,out_dir);
exit(1);
}
}
同样,回忆在README文件中读到过,如果参数行有-o out_dir, 则将输出结果到该目录,否则,则输出到当前目录。在这一段中,如果输出目录不存在(chdir(out_dir) != 0)则出错。#ifdef USE_DNS
if (strstr(argv[0],"webazolver")!=0)
{
if (!dns_children) dns_children=5; /* default dns children if needed */
if (!dns_cache)
{
/* No cache file specified, aborting... */
fprintf(stderr,"%s ",msg_dns_nocf); /* Must have a cache file */
exit(1);
}
}......
在上面曾经提到过,这是DNS解析的代码部分,可以略过不看,不会影响对整个程序的理解。
/* prep hostname */
if (!hname)
{
if (uname(&system_info)) hname="localhost";
else hname=system_info.nodename;
}
这一段继续处理参数做准备工作。如果在命令行中指定了hostname(机器名)则采用指定的名称,否则调用uname查找机器名,如果没有,则用localhost来作为机器名。(同样在README中说得很详细)
/* get past history */
if (ignore_hist) {if (verbose>1) printf("%s ",msg_ign_hist); }
else get_history();
如果在命令行中指定了忽略历史文件,则不读取历史文件,否则调用get_history()来读取历史数据。在这里,我们可以回想在README文件中同样说过这一细节,在命令行或者配置文件中都能指定这一开关。需要说明的是,我们在这里并不一定需要去看get_history这一函数,因为从函数的名称,README文件和程序注释都能很清楚的得知这一函数的功能,不一定要去看代码。而如果要猜想的话,也可以想到,history是webalizer在上次运行的时候记录下来的一个文件,而这个文件则是去读取它,并将它的数据包括到这次的分析中去。不信,我们可以来看看。void get_history(){
int i,numfields;
FILE *hist_fp;
char buffer[BUFSIZE];/* first initalize internal array */
for (i=0;i<12;i++)
{
hist_month[i]=hist_year[i]=hist_fday[i]=hist_lday[i]=0;
hist_hit[i]=hist_files[i]=hist_site[i]=hist_page[i]=hist_visit[i]=0;
hist_xfer[i]=0.0;
}hist_fp=fopen(hist_fname,"r");if (hist_fp)
{
if (verbose>1) printf("%s %s ",msg_get_hist,hist_fname);
while ((fgets(buffer,BUFSIZE,hist_fp)) != NULL)
{
i = atoi(buffer) -1;
if (i>11)
{
if (verbose)
fprintf(stderr,"%s (mth=%d) ",msg_bad_hist,i+1);
continue;
}/* month# year# requests files sites xfer firstday lastday */
numfields = sscanf(buffer,"%d %d %lu %lu %lu %lf %d %d %lu %lu",
&hist_month[i],
&hist_year[i],
&hist_hit[i],
&hist_files[i],
&hist_site[i],
&hist_xfer[i],
&hist_fday[i],
&hist_lday[i],
&hist_page[i],
&hist_visit[i]);if (numfields==8) /* kludge for reading 1.20.xx history files */
{
hist_page[i] = 0;
hist_visit[i] = 0;
}
}
fclose(hist_fp);
}
else if (verbose>1) printf("%s ",msg_no_hist);}/*********************************************//* PUT_HISTORY - write out history file *//*********************************************/void put_history(){
int i;
FILE *hist_fp;hist_fp = fopen(hist_fname,"w");if (hist_fp)
{
if (verbose>1) printf("%s ",msg_put_hist);
for (i=0;i<12;i++)
{
if ((hist_month[i] != 0) && (hist_hit[i] != 0))
{
fprintf(hist_fp,"%d %d %lu %lu %lu %.0f %d %d %lu %lu ",
hist_month[i],
hist_year[i],
hist_hit[i],
hist_files[i],
hist_site[i],
hist_xfer[i],
hist_fday[i],
hist_lday[i],
hist_page[i],
hist_visit[i]);
}
}
fclose(hist_fp);
}
else
if (verbose)
fprintf(stderr,"%s %s ",msg_hist_err,hist_fname);}
在preserve.c中,这两个函数是成对出现的。get_history()读取文件中的数据,并将其记录到hist_开头的一些数组中去。而put_history()则是将一些数据记录到同样的数组中去。我们可以推测得知,hist_数组是全局变量(在函数中没有定义),也可以查找源代码验证。同样,我们可以找一找put_history()出现的地方,来验证刚才的推测是否正确。在webalizer.c的1311行,出现:month_update_exit(rec_tstamp); /* calculate exit pages */
write_month_html(); /* write monthly HTML file */
write_main_index(); /* write main HTML file */
put_history(); /* write history */
可以知道,推测是正确的。再往下读代码,if (incremental) /* incremental processing? */
{
if ((i=restore_state())) /* restore internal data structs */
{
/* Error: Unable to restore run data (error num) */
/* if (verbose) fprintf(stderr,"%s (%d) ",msg_bad_data,i); */
fprintf(stderr,"%s (%d) ",msg_bad_data,i);
exit(1);
}
......
}
同样,这也是处理命令行和做数据准备,而且和get_history(), put_history()有些类似,读者可以自己练习一下。下面,终于进入了程序的主体部分, 在做完了命令行分析,数据准备之后,开始从日志文件中读取数据并做分析了。/*********************************************/
/* MAIN PROCESS LOOP - read through log file */
/*********************************************/while ( (gz_log)?(our_gzgets(gzlog_fp,buffer,BUFSIZE) != Z_NULL):
(fgets(buffer,BUFSIZE,log_fname?log_fp:stdin) != NULL))
我看到这里的时候,颇有一些不同意作者的这种写法。这一段while中的部分写的比较复杂而且效率不高。因为从程序推断和从他的代码看来,作者是想根据日志文件的类型不同来采用不同的方法读取文件,如果是gzip格式,则用our_gzgets来读取其中一行,如果是普通的文本文件格式,则用fgets()来读取。但是,这段代码是写在while循环中的,每次读取一行就要重复判断一次,明显是多余的而且降低了程序的性能。可以在while循环之前做一次这样的判断,然后就不用重复了。total_rec++;
if (strlen(buffer) == (BUFSIZE-1))
{
if (verbose)
{
fprintf(stderr,"%s",msg_big_rec);
if (debug_mode) fprintf(stderr,": %s",buffer);
else fprintf(stderr," ");
}total_bad++; /* bump bad record counter *//* get the rest of the record */
while ( (gz_log)?(our_gzgets(gzlog_fp,buffer,BUFSIZE)!=Z_NULL):
(fgets(buffer,BUFSIZE,log_fname?log_fp:stdin)!=NULL))
{
if (strlen(buffer) < BUFSIZE-1)
{
if (debug_mode && verbose) fprintf(stderr,"%s ",buffer);
break;
}
if (debug_mode && verbose) fprintf(stderr,"%s",buffer);
}
continue; /* go get next record if any */
}