下面都是Inmoreliu的总结,我只是抄袭了,如果哪天我集结出版了,我一定付他稿酬。首先要说明一下chroot函数是一个UNIX函数,用于改变整个程序的虚拟的根目录,这个函数的最主要的用途是保证运行系统的安全,降低被攻击的可能。
问题现象:
由于anna MM反映无法更新pet_def.db3文件,于是到dlsvrd上跟了一把日志。发现输出的日志时间不太对,也没有太在意,以为可能是谁把dlsvrd停了,ps一把,发现dlsvrd进程还在,而且是几天以前了,感觉有点奇怪,于是重启了dlsvrd,发现时间又正常了,于是让anna再测一次,猛然发现正确的时间突然又不对了,而且正好和现在的时间相差8小时,日志时间从09:48倒回到01:48。Date发现,系统时钟仍然是09:48,有意思。初步认为是程序中的什么地方将时区进行了修改。
定位过程:根据日志打出的上下文去查代码,发现在此期间只有format_gmt_time一个函数进行了实际的操作,函数代码如下:
size_t DlsvrTask::format_gmt_time(time_t timestamp, char *buff, size_t buflen)
{
struct tm tm;
gmtime_r(×tamp, &tm);
return strftime(buff, buflen, “%a, %d %b %Y %X GMT”, &tm);
}
Gmtime_r:将timestamp解析成GMT然后将结果保存到tm中,线程安全(dlsvrd采用了多线程)。
Strftime:将tm已指定的格式和长度输入到buff中。
理论上来说,这两个函数都没有可能去修改时区,由于该函数位于子线程中,所以sail认为可能和多线程有关系,于是将这段代码单独移到主循环中(线程初始化之后),发现仍然会出现这个问题。同时分别将这两个函数进行屏蔽,发现当屏蔽strftime时不会出现此现象,断定是由于strftime造成的时区变化。
但是sail单独写了一段测试代码发现,无论怎么调strftime和gmtime_r,都不会产生时区变化,于是又怀疑是多线程的原因,将gmtime_r和strftime移到线程初始化之前,发现问题依旧,无语。但无意间将这两个函数移到InitInstance的最开始,发现没有出现时区变化的问题,神了。那么从InitInstance的最开始到线程初始化之间的代码和strftime共同作用的结果,且存在顺序问题。
无奈,只好老老实实、一个一个比较测试程序和InitInstance的最开始到线程初始化之间代码的差异。
发现将strftime和gmtime_r移到下面一段代码之前和之后,输出结果明显不同,同时,执行和不执行strftime,也会影响时区(也难怪sail在windows在调了半天dlsvrd也没有重现这个问题)
#ifndef WIN32
//chroot
ret = chroot(InstOfDlsvrSvrConfig::instance()->root_path_.c_str());
if (ret != 0)
{
ACE_DEBUG((LM_ERROR,”chroot failed: %u | %s/n”, ACE_OS::last_error(), strerror(ACE_OS::last_error())));
return ret;
}
#endif
于是自己写了一段测试代码(见附件),发现是由chroot和strftime一起调用引起的问题。
原因:
strftime在《The Single UNIX ® Specification, Version 2 Copyright © 1997 The Open Group》有如下一段解释:
“Local timezone information is used as though strftime() called tzset().”
即strftime会通过tzset来获取时区的信息。
在man tzset中有这样一段解释:
“If the TZ variable does not appear in the environment, the tzname variable is initialized with the best approximation of local wall clock time, as specified by the tzfile(5)-format file localtime found in the system timezone directory (see below). (One also often sees /etc/localtime used here, a symlink to the right file in the system timezone directory.)”
简单来说,就是tzset是通过TZ环境变量或是/etc/localtime来设置timezone,而我们目前没有设置TZ变量,所以实际是通过/etc/localtime来设置的。由于我们没有在InstOfDlsvrSvrConfig::instance()->root_path_下设置/etc目录,所以在chroot后在调用strftime,tzset会找不到localtime,因此将timezone设置成0.这样,日志的时间就从原来的CST变成了GMT。
修改方案:
方案一:使用TZ的环境变量。
方案二:由于dlsvrd已经对字符”..”进行了屏蔽,那么其实chroot也就不那么必要了,初步考虑是将chroot去掉。
思考:
Q:为什么strftime会调用tzset?
A:因为strftime有一个参数%Z,用于格式化时区,而其无法通过tm参数来获得时区的信息,所以必须通过tzset来获得timezone,这个是可以理解。但是我们在程序中其实并没有用到%Z这个参数, 那么理论上来说,不应该去调tzset,这个有点想不通。
Q:linux下的3个时间文件的作用
A:
/etc/sysconfig/clock - this is a short text file that defines the timezone, whether or not the hardware clock is using UTC, and an ARC option that is only relevant to DEC systems.
/etc/localtime - this is a symbolic link to the appropriate time zone file in /usr/share/zoneinfo
/usr/share/zoneinfo - this directory contains the time zone files that were compiled by zic. These are binary files and cannot be viewed with a text viewer. The files contain information such as rules about DST. They allow the kernel to convert UTC UNIX time into appropriate local dates and times.
如果只关注timezone的话,简单而言,3个文件作用如下:
/etc/sysconfig/clock是定义timezone的文本文件
/etc/localtime 是zoneinfo下一个文件的硬连接,这个文件由/etc/sysconfig/clock中的ZONE选项指定
/usr/share/zoneinfo 这个目录下存储了各个时区与UTC转换的相关规则和信息。
那么,为什么需要三个这样的文件呢?两个应该是完全够用的。如果没有clock,那么程序每次到localtime去查询转换规则即可,如果没有localtime,那么每次到clock去查询ZONE选项,再通过ZONE去定位/usr/share/zoneinfo目录下的相关文件,查询转换规则,这样也是可以的。由此可以感觉到/etc/sysconfig/clock和/etc/localtime至少有一个是多余的,不知道为什么会设计如此。Google了很久,未果,待续……