chroot導致日誌時間出錯的BUG

下面都是Inmoreliu的總結,我只是抄襲了,如果哪天我集結出版了,我一定付他稿酬。首先要說明一下chroot函數是一個UNIX函數,用於改變整個程序的虛擬的根目錄,這個函數的最主要的用途是保證運行系統的安全,降低被攻擊的可能。

問題現象:

由於anna MM反映無法更新pet_def.db3文件,於是到dlsvrd上跟了一把日誌。發現輸出的日誌時間不太對,也沒有太在意,以爲可能是誰把dlsvrd停了,ps一把,發現dlsvrd進程還在,而且是幾天以前了,感覺有點奇怪,於是重啓了dlsvrd,發現時間又正常了,於是讓anna再測一次,猛然發現正確的時間突然又不對了,而且正好和現在的時間相差8小時,日誌時間從0948倒回到0148Date發現,系統時鐘仍然是0948,有意思。初步認爲是程序中的什麼地方將時區進行了修改。

定位過程:根據日誌打出的上下文去查代碼,發現在此期間只有format_gmt_time一個函數進行了實際的操作,函數代碼如下:

size_t DlsvrTask::format_gmt_time(time_t timestamp, char *buff, size_t buflen)

{

struct tm tm;

gmtime_r(&timestamp, &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單獨寫了一段測試代碼發現,無論怎麼調strftimegmtime_r,都不會產生時區變化,於是又懷疑是多線程的原因,將gmtime_rstrftime移到線程初始化之前,發現問題依舊,無語。但無意間將這兩個函數移到InitInstance的最開始,發現沒有出現時區變化的問題,神了。那麼從InitInstance的最開始到線程初始化之間的代碼和strftime共同作用的結果,且存在順序問題。

無奈,只好老老實實、一個一個比較測試程序和InitInstance的最開始到線程初始化之間代碼的差異。

發現將strftimegmtime_r移到下面一段代碼之前和之後,輸出結果明顯不同,同時,執行和不執行strftime,也會影響時區(也難怪sailwindows在調了半天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

於是自己寫了一段測試代碼(見附件),發現是由chrootstrftime一起調用引起的問題。

原因:

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後在調用strftimetzset會找不到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了很久,未果,待續……

 

發佈了79 篇原創文章 · 獲贊 32 · 訪問量 56萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章