Programming 版 (精华区)
发信人: worldguy (蓝色天际), 信区: Programming
标 题: [转]如何阅读源代码(3)
发信站: 哈工大紫丁香 (2004年04月16日20:51:11 星期五), 站内信件
/* prep hostname */
if (!hname)
{
if (uname(&system_info)) hname="localhost";
else hname=system_info.nodename;
}
这一段继续处理参数做准备工作。如果在命令行中指定了hostname(机器名)则采用指定
的名称,否则调用uname查找机器名,如果没有,则用localhost来作为机器名。(同样在RE
ADME中说得很详细)
/* 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 */
}
这一段代码,读入一行,如果这一行超过了程序允许的最大字符数(则是错误的日志数
据纪录),则跳过本行剩下的数据,忽略掉(continue进行下一次循环)。同时把total_b
ad增加一个。如果没有超过程序允许的最大字符数(则是正确的日志数据纪录),则
/* got a record... */
strcpy(tmp_buf, buffer); /* save buffer in case of error */
if (parse_record(buffer)) /* parse the record */
将该数据拷贝到一个缓冲区中,然后调用parse_record()进行处理。我们可以同样的推
测一下,get_record()是这个程序的一个主要处理部分,分析了日志数据。在parse_recor
d.c中,有此函数,
/*********************************************/
/* PARSE_RECORD - uhhh, you know... */
/*********************************************/
int parse_record(char *buffer)
{
/* clear out structure */
memset(&log_rec,0,sizeof(struct log_struct));
/*
log_rec.hostname[0]=0;
log_rec.datetime[0]=0;
log_rec.url[0]=0;
log_rec.resp_code=0;
log_rec.xfer_size=0;
log_rec.refer[0]=0;
log_rec.agent[0]=0;
log_rec.srchstr[0]=0;
log_rec.ident[0]=0;
*/
#ifdef USE_DNS
memset(&log_rec.addr,0,sizeof(struct in_addr));
#endif
/* call appropriate handler */
switch (log_type)
{
default:
case LOG_CLF: return parse_record_web(buffer); break; /* clf */
case LOG_FTP: return parse_record_ftp(buffer); break; /* ftp */
case LOG_SQUID: return parse_record_squid(buffer); break; /* squid */
}
}
可以看到,log_rec是一个全局变量,该函数根据日志文件的类型,分别调用三种不同的
分析函数。在webalizer.h中,找到该变量的定义,从结构定义中可以看到,结构定义了一
个日志文件所可能包含的所有信息(参考CLF,FTP, SQUID日志文件的格式说明)。
/* log record structure */
struct log_struct { char hostname[MAXHOST]; /* hostname */
char datetime[29]; /* raw timestamp */
char url[MAXURL]; /* raw request field */
int resp_code; /* response code */
u_long xfer_size; /* xfer size in bytes */
#ifdef USE_DNS
struct in_addr addr; /* IP address structure */
#endif /* USE_DNS */
char refer[MAXREF]; /* referrer */
char agent[MAXAGENT]; /* user agent (browser) */
char srchstr[MAXSRCH]; /* search string */
char ident[MAXIDENT]; }; /* ident string (user) */
extern struct log_struct log_rec;
先看一下一个parser.c用的内部函数,然后再来以parse_record_web()为例子看看这个
函数是怎么工作的,parse_record_ftp, parse_record_squid留给读者自己分析作为练习
。
/*********************************************/
/* FMT_LOGREC - terminate log fields w/zeros */
/*********************************************/
void fmt_logrec(char *buffer)
{
char *cp=buffer;
int q=0,b=0,p=0;
while (*cp != '')
{
/* break record up, terminate fields with '' */
switch (*cp)
{
case ' ': if (b || q || p) break; *cp=''; break;
case '"': q^=1; break;
case '[': if (q) break; b++; break;
case ']': if (q) break; if (b>0) b--; break;
case '(': if (q) break; p++; break;
case ')': if (q) break; if (p>0) p--; break;
}
cp++;
}
}
从parser.h头文件中就可以看到,这个函数是一个内部函数,这个函数把一行字符串中
间的空格字符用''字符(结束字符)来代替,同时考虑了不替换在双引号,方括号,圆括
号中间的空格字符以免得将一行数据错误的分隔开了。(请参考WEB日志的文件格式,可以
更清楚的理解这一函数)
int parse_record_web(char *buffer)
{
int size;
char *cp1, *cp2, *cpx, *eob, *eos;
size = strlen(buffer); /* get length of buffer */
eob = buffer+size; /* calculate end of buffer */
fmt_logrec(buffer); /* seperate fields with 's */
/* HOSTNAME */
cp1 = cpx = buffer; cp2=log_rec.hostname;
eos = (cp1+MAXHOST)-1;
if (eos >= eob) eos=eob-1;
while ( (*cp1 != '') && (cp1 != eos) ) *cp2++ = *cp1++;
*cp2 = '';
if (*cp1 != '')
{
if (verbose)
{
fprintf(stderr,"%s",msg_big_host);
if (debug_mode) fprintf(stderr,": %s ",cpx);
else fprintf(stderr," ");
}
while (*cp1 != '') cp1++;
}
if (cp1 < eob) cp1++;
/* skip next field (ident) */
while ( (*cp1 != '') && (cp1 < eob) ) cp1++;
if (cp1 < eob) cp1++;
/* IDENT (authuser) field */
cpx = cp1;
cp2 = log_rec.ident;
eos = (cp1+MAXIDENT-1);
if (eos >= eob) eos=eob-1;
while ( (*cp1 != '[') && (cp1 < eos) ) /* remove embeded spaces */
{
if (*cp1=='') *cp1=' ';
*cp2++=*cp1++;
}
*cp2--='';
if (cp1 >= eob) return 0;
/* check if oversized username */
if (*cp1 != '[')
{
if (verbose)
{
fprintf(stderr,"%s",msg_big_user);
if (debug_mode) fprintf(stderr,": %s ",cpx);
else fprintf(stderr," ");
}
while ( (*cp1 != '[') && (cp1 < eob) ) cp1++;
}
/* strip trailing space(s) */
while (*cp2==' ') *cp2--='';
/* date/time string */
cpx = cp1;
cp2 = log_rec.datetime;
eos = (cp1+28);
if (eos >= eob) eos=eob-1;
while ( (*cp1 != '') && (cp1 != eos) ) *cp2++ = *cp1++;
*cp2 = '';
if (*cp1 != '')
{
if (verbose)
{
fprintf(stderr,"%s",msg_big_date);
if (debug_mode) fprintf(stderr,": %s ",cpx);
else fprintf(stderr," ");
}
while (*cp1 != '') cp1++;
}
if (cp1 < eob) cp1++;
/* minimal sanity check on timestamp */
if ( (log_rec.datetime[0] != '[') ||
(log_rec.datetime[3] != '/') ||
(cp1 >= eob)) return 0;
/* HTTP request */
cpx = cp1;
cp2 = log_rec.url;
eos = (cp1+MAXURL-1);
if (eos >= eob) eos = eob-1;
while ( (*cp1 != '') && (cp1 != eos) ) *cp2++ = *cp1++;
*cp2 = '';
if (*cp1 != '')
{
if (verbose)
{
fprintf(stderr,"%s",msg_big_req);
if (debug_mode) fprintf(stderr,": %s ",cpx);
else fprintf(stderr," ");
}
while (*cp1 != '') cp1++;
}
if (cp1 < eob) cp1++;
if ( (log_rec.url[0] != '"') ||
(cp1 >= eob) ) return 0;
/* response code */
log_rec.resp_code = atoi(cp1);
/* xfer size */
while ( (*cp1 != '') && (cp1 < eob) ) cp1++;
if (cp1 < eob) cp1++;
if (*cp1<'0'||*cp1>'9') log_rec.xfer_size=0;
else log_rec.xfer_size = strtoul(cp1,NULL,10);
/* done with CLF record */
if (cp1>=eob) return 1;
while ( (*cp1 != '') && (*cp1 != ' ') && (cp1 < eob) ) cp1++;
if (cp1 < eob) cp1++;
/* get referrer if present */
cpx = cp1;
cp2 = log_rec.refer;
eos = (cp1+MAXREF-1);
if (eos >= eob) eos = eob-1;
while ( (*cp1 != '') && (*cp1 != ' ') && (cp1 != eos) ) *cp2++ = *cp1++;
*cp2 = '';
if (*cp1 != '')
{
if (verbose)
{
fprintf(stderr,"%s",msg_big_ref);
if (debug_mode) fprintf(stderr,": %s ",cpx);
else fprintf(stderr," ");
}
while (*cp1 != '') cp1++;
}
if (cp1 < eob) cp1++;
cpx = cp1;
cp2 = log_rec.agent;
eos = cp1+(MAXAGENT-1);
if (eos >= eob) eos = eob-1;
while ( (*cp1 != '') && (cp1 != eos) ) *cp2++ = *cp1++;
*cp2 = '';
return 1; /* maybe a valid record, return with TRUE */
}
该函数,一次读入一行(其实是一段日志数据中间的一个域,因为该行数据已经被fmt_l
o
grec分开成多行数据了。根据CLF中的定义,检查该数据并将其拷贝到log_rec结构中去
,
如果检查该数据有效,则返回1。回到主程序,
/* convert month name to lowercase */
for (i=4;i<7;i++)
log_rec.datetime[i]=tolower(log_rec.datetime[i]);
/* get year/month/day/hour/min/sec values */
for (i=0;i<12;i++)
{
if (strncmp(log_month[i],&log_rec.datetime[4],3)==0)
{ rec_month = i+1; break; }
}
rec_year=atoi(&log_rec.datetime[8]); /* get year number (int) */
rec_day =atoi(&log_rec.datetime[1]); /* get day number */
rec_hour=atoi(&log_rec.datetime[13]); /* get hour number */
rec_min =atoi(&log_rec.datetime[16]); /* get minute number */
rec_sec =atoi(&log_rec.datetime[19]); /* get second number */
....
在parse_record分析完数据之后,做日期的分析,把日志中的月份等数据转换成机器可
读
(可理解)的数据,并存入到log_rec中去。
if ((i>=12)||(rec_min>59)||(rec_sec>59)||(rec_year<1990))
{
total_bad++; /* if a bad date, bump counter */
if (verbose)
{
fprintf(stderr,"%s: %s [%lu]",
msg_bad_date,log_rec.datetime,total_rec);
......
如果日期,时间错误,则把total_bad计数器增加1,并且打印错误信息到标准错误输出。
good_rec = 1;
/* get current records timestamp (seconds since epoch) */
req_tstamp=cur_tstamp;
rec_tstamp=((jdate(rec_day,rec_month,rec_year)-epoch)*86400)+
(rec_hour*3600)+(rec_min*60)+rec_sec;
/* Do we need to check for duplicate records? (incremental mode) */
if (check_dup)
{
/* check if less than/equal to last record processed */
if ( rec_tstamp <= cur_tstamp )
{
/* if it is, assume we have already processed and ignore it */
total_ignore++;
continue;
}
else
{
/* if it isn't.. disable any more checks this run */
check_dup=0;
/* now check if it's a new month */
if (cur_month != rec_month)
{
clear_month();
cur_sec = rec_sec; /* set current counters */
cur_min = rec_min;
cur_hour = rec_hour;
cur_day = rec_day;
cur_month = rec_month;
cur_year = rec_year;
cur_tstamp= rec_tstamp;
f_day=l_day=rec_day; /* reset first and last day */
}
}
}
/* check for out of sequence records */
if (rec_tstamp/3600 < cur_tstamp/3600)
{
if (!fold_seq_err && ((rec_tstamp+SLOP_VAL)/3600<cur_tstamp/3600) )
{ total_ignore++; continue; }
else
{
rec_sec = cur_sec; /* if folding sequence */
rec_min = cur_min; /* errors, just make it */
rec_hour = cur_hour; /* look like the last */
rec_day = cur_day; /* good records timestamp */
rec_month = cur_month;
rec_year = cur_year;
rec_tstamp= cur_tstamp;
}
}
cur_tstamp=rec_tstamp; /* update current timestamp */
如果该日期、时间没有错误,则该数据是一个好的数据,将good_record计数器加1,并
且检查时间戳,和数据是否重复数据。这里有一个函数,jdate()在主程序一开头我们就遇
到了,当时跳了过去没有深究,这里留给读者做一个练习。(提示:该函数根据一个日期
产生一个字符串,这个字符串是惟一的,可以检查时间的重复性,是一个通用函数,可以
在别的程序中拿来使用)
/*********************************************/
/* DO SOME PRE-PROCESS FORMATTING */
/*********************************************/
/* fix URL field */
cp1 = cp2 = log_rec.url;
/* handle null '-' case here... */
if (*++cp1 == '-') { *cp2++ = '-'; *cp2 = ''; }
else
{
/* strip actual URL out of request */
while ( (*cp1 != ' ') && (*cp1 != '') ) cp1++;
if (*cp1 != '')
{
/* scan to begin of actual URL field */
while ((*cp1 == ' ') && (*cp1 != '')) cp1++;
/* remove duplicate / if needed */
if (( *cp1=='/') && (*(cp1+1)=='/')) cp1++;
while ((*cp1 != ' ')&&(*cp1 != '"')&&(*cp1 != ''))
*cp2++ = *cp1++;
*cp2 = '';
}
}
/* un-escape URL */
unescape(log_rec.url);
/* check for service (ie: http://) and lowercase if found */
if ( (cp2=strstr(log_rec.url,"://")) != NULL)
{
cp1=log_rec.url;
while (cp1!=cp2)
{
if ( (*cp1>='A') && (*cp1<='Z')) *cp1 += 'a'-'A';
cp1++;
}
}
/* strip query portion of cgi scripts */
cp1 = log_rec.url;
while (*cp1 != '')
if (!isurlchar(*cp1)) { *cp1 = ''; break; }
else cp1++;
if (log_rec.url[0]=='')
{ log_rec.url[0]='/'; log_rec.url[1]=''; }
/* strip off index.html (or any aliases) */
lptr=index_alias;
while (lptr!=NULL)
{
if ((cp1=strstr(log_rec.url,lptr->string))!=NULL)
{
if ((cp1==log_rec.url)||(*(cp1-1)=='/'))
{
*cp1='';
if (log_rec.url[0]=='')
{ log_rec.url[0]='/'; log_rec.url[1]=''; }
break;
}
}
lptr=lptr->next;
}
/* unescape referrer */
unescape(log_rec.refer);
......
这一段,做了一些URL字符串中的字符转换工作,很长,我个人认为为了程序的模块化,
结构化和可复用性,应该将这一段代码改为函数,避免主程序体太长,造成可读性不强和
没有移植性,和不够结构化。跳过这一段乏味的代码,进入到下面一个部分---后处理。
if (gz_log) gzclose(gzlog_fp);
else if (log_fname) fclose(log_fp);
if (good_rec) /* were any good records? */
{
tm_site[cur_day-1]=dt_site; /* If yes, clean up a bit */
tm_visit[cur_day-1]=tot_visit(sd_htab);
t_visit=tot_visit(sm_htab);
if (ht_hit > mh_hit) mh_hit = ht_hit;
if (total_rec > (total_ignore+total_bad)) /* did we process any? */
{
if (incremental)
{
if (save_state()) /* incremental stuff */
{
/* Error: Unable to save current run data */
if (verbose) fprintf(stderr,"%s ",msg_data_err);
unlink(state_fname);
}
}
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 */
}
end_time = times(&mytms); /* display timing totals? */
if (time_me' '(verbose>1))
{
printf("%lu %s ",total_rec, msg_records);
if (total_ignore)
{
printf("(%lu %s",total_ignore,msg_ignored);
if (total_bad) printf(", %lu %s) ",total_bad,msg_bad);
else printf(") ");
}
else if (total_bad) printf("(%lu %s) ",total_bad,msg_bad);
/* get processing time (end-start) */
temp_time = (float)(end_time-start_time)/CLK_TCK;
printf("%s %.2f %s", msg_in, temp_time, msg_seconds);
/* calculate records per second */
if (temp_time)
i=( (int)( (float)total_rec/temp_time ) );
else i=0;
if ( (i>0) && (i<=total_rec) ) printf(", %d/sec ", i);
else printf(" ");
}
这一段,做了一些后期的处理。接下来的部分,我想在本文中略过,留给感兴趣的读者
自己去做分析。原因有两点:
1、这个程序在前面结构化比较强,而到了结构上后面有些乱,虽然代码效率还是比较高
,但是可重用性不够强, 限于篇幅,我就不再一一解释了。
2、前面分析程序过程中,也对后面的代码做了一些预测和估计,也略微涉及到了后面的
代码,而且读者可以根据上面提到的原则来自己分析代码,也作为一个实践吧。
--
No more you blind me heaven,
no more you bury me hell,
be powderize indifferent gods,
be lighted all the hears.
※ 来源:·哈工大紫丁香 bbs.hit.edu.cn·[FROM: 202.118.239.18]
※ 修改:·worldguy 於 04月16日20:51:27 修改本文·[FROM: 202.118.239.18]
Powered by KBS BBS 2.0 (http://dev.kcn.cn)
页面执行时间:208.129毫秒