PHP时区数据库损坏错误


PHP Timezone database is corrupt error

我有一个wordpress网站今天突然停止工作。当我查看日志时,我看到错误:

[error][client 50.78.108.177]PHP致命错误:strtotime():时区数据库已损坏-这应该永远不会发生!

在谷歌上阅读后,一个人说他们在/usr/share/zoneinfo中发现了权限问题。我试着将权限更改为777775770,但仍然会出现同样的错误。我在Ubuntu 10.04.3 LTS上运行php php 5.3.2。任何建议或建议都将是有益的。如果所有其他操作都失败了,我将尝试降级到php的早期版本,但在此之前我想尝试其他操作。

谢谢,Timnit

更新
以防万一:错误指向下面函数中的strtotime

function mysql2date( $dateformatstring, $mysqlstring, $translate = true ) {
    $m = $mysqlstring;
    if ( empty( $m ) )
            return false;
    if ( 'G' == $dateformatstring )
            return strtotime( $m . ' +0000' );
    $i = strtotime( $m );
    if ( 'U' == $dateformatstring )
            return $i;
    if ( $translate )
            return date_i18n( $dateformatstring, $i );
    else
            return date( $dateformatstring, $i );
}

更新#2:
现在,我已经通过简单地具有return false;以上的函数而不执行任何操作来解决问题。然而,我仍然没有弄清楚问题的根本原因。

更新#3:

var_dump($dateformatstring)

string(5)"d.m.y"string(1)"m"string(5string(5)"d.m.y"string(1)"m"

var_dump($mysqlstring)

string(19)"2011-10-20 05:35:01"string(19string(19)"2011-10-20 05:25:22"string(19string(19)"2011-10-19 05:10:06"string(19

更新#4
下面还有另一个生成错误日志的代码片段:

PHP致命错误:date():时区数据库已损坏-这应该永远不要发生!在/srv/www/motionwink.com/public_html/wp-admin/includes/class-wp-filesystem-directive.php中在第346行,referer:wp_root_directory/wp-admin/plugins.php?plugin_status=升级

309         function dirlist($path, $include_hidden = true, $recursive = false) {
  310                 if ( $this->is_file($path) ) {
  311                         $limit_file = basename($path);
  312                         $path = dirname($path);
  313                 } else {
  314                         $limit_file = false;
  315                 }
  316 
  317                 if ( ! $this->is_dir($path) )
  318                         return false;
  319 
  320                 $dir = @dir($path);
  321                 if ( ! $dir )
  322                         return false;
  323 
  324                 $ret = array();
  325 
  326                 while (false !== ($entry = $dir->read()) ) {
  327                         $struc = array();
  328                         $struc['name'] = $entry;
  329 
  330                         if ( '.' == $struc['name'] || '..' == $struc['name'] )
  331                                 continue;
  332 
  333                         if ( ! $include_hidden && '.' == $struc['name'][0] )
  334                                 continue;
  335 
  336                         if ( $limit_file && $struc['name'] != $limit_file)
  337                                 continue;
  338 
  339                         $struc['perms']         = $this->gethchmod($path.'/'.$entry);
  340                         $struc['permsn']  = $this->getnumchmodfromh($struc['perms']);
  341                         $struc['number']        = false;
  342                         $struc['owner']         = $this->owner($path.'/'.$entry);
  343                         $struc['group']         = $this->group($path.'/'.$entry);
  344                         $struc['size']          = $this->size($path.'/'.$entry);
  345                         $struc['lastmodunix']= $this->mtime($path.'/'.$entry);
  346                         $struc['lastmod']   = date('M j',$struc['lastmodunix']);
  347                         $struc['time']          = date('h:i:s',$struc['lastmodunix']);
  348                  $struc['type']          = $this->is_dir($path.'/'.$entry) ?   'd:'f';
  349 

更新#5:
执行php -i | fgrep -i date返回

构建日期=>2011年12月13日18:43:02

date
date/time support => enabled
date.default_latitude => 31.7667 => 31.7667
date.default_longitude => 35.2333 => 35.2333
date.sunrise_zenith => 90.583333 => 90.583333
date.sunset_zenith => 90.583333 => 90.583333
date.timezone => no value => no value

然后我编辑了php.ini文件,将时区设置为"美国/洛杉矶",得到了这个输出

date/time support => enabled
date.default_latitude => 31.7667 => 31.7667
date.default_longitude => 35.2333 => 35.2333
date.sunrise_zenith => 90.583333 => 90.583333
date.sunset_zenith => 90.583333 => 90.583333
date.timezone => America/Los_Angeles => America/Los_Angeles

然后我迅速重启。我仍然收到错误

在chroot模式下使用php-fpm时也可能出现此问题,在这种情况下,解决方案是在chroot目录中创建/usr/share/zoneinfo/Europe之类的东西,然后将TZ文件复制到其中,例如伦敦

根本原因:无法打开其中一个zoneinfo文件

也是由引起的:打开的文件太多。

我今天在Ubuntu 14.04.01-LTS"Trusty Tahr"上遇到了同样的问题,并尝试了其他答案,但没有任何好处。权限还可以,文件在那里,内容如预期。

最后,我决定从命令行线束中运行该脚本,这样我就可以尝试使用strace了。结果是:

openat(AT_FDCWD, "/usr/share/zoneinfo/", O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC) = -1 EMFILE (Too many open files)
open("/usr/share/zoneinfo/zone.tab", O_RDONLY) = -1 EMFILE (Too many open files)
stat("/usr/share/zoneinfo/Europe/Rome", {st_mode=S_IFREG|0644, st_size=2652, ...}) = 0
open("/usr/share/zoneinfo/Europe/Rome", O_RDONLY) = -1 EMFILE (Too many open files)
write(1, "'nFatal error: Unknown: Timezone "..., 104) = 104

发生了什么

当PHP"访问zoneinfo数据库"时,它实际上试图打开一个目录和一些文件。如果其中一些操作失败,将显示"zoneinfo corrupt"消息,但这只是意味着PHP进程无法打开这些文件:

  • 他们不在那里(chroot监狱,zoneinfo安装错误)
  • 他们不在那里,也不应该在:"欧洲/罗姆"不是一个有效的时区,而是一个拼写错误
  • 他们在那里,但权限不对
  • 他们在那里,但该过程未经授权(SELinux、AppArmor…)
  • 他们在那里,但fopen操作暂时不起作用

我的案例是最后一个:真正的问题是脚本打开了太多的临时文件,并在运行时保持打开状态。同时可以打开的文件数量是有限制的,而zoneinfo文件是众所周知的最后一根稻草。一个快速修复程序暂时解决了这个问题,而我将"文件太多"的问题交给了负责的开发人员。

事实上,我怀疑这个指向PHP不断打开和关闭zoneinfo数据库,而不是缓存,但这是另一天的调查。

间歇性错误"打开文件的数量"是每个进程,而不是每个PHP脚本。因此,有两种(至少)情况可能导致难以诊断,可能是间歇性/不可重复的错误:

  • 一些长时间运行的进程(例如在Racket下)造成的缓慢资源泄漏
  • 被同一进程中运行的另一个脚本或子例程占用资源,并且可能甚至与PHP根本无关

一个PHP脚本,无论对错,都可以分配800个文件,直到它遇到另一个分配了224个文件的子进程为止。每个进程已达到1024个打开文件的限制,在这种情况下进程会因一个神秘的错误而失败(这只是指一长串并发原因中的最后一个症状)。

Apache:网站太多了

使用mod_php5运行的Apache将导致Apache进程打开PHP访问的文件。但是Apache进程也保持其日志文件的打开状态,并且每个进程都有一个对每个日志文件的句柄。

因此,如果你有200个网站,每个网站都有一个独立的access_log,比如/var/www/somesite/logs/access_log,那么每个进程都将从已经用于内务管理的210个句柄开始,剩下800个句柄供PHP免费使用。

如果脚本需要同时分配900个临时文件,这可能会导致开发服务器(有一个站点)工作,而生产服务器(安装了200个站点)不工作的情况。

脏诊断(在Unix/Linux上)glob /proc/self/fdcount()结果。虽然很丑陋,但它给出了实际打开的文件描述符的大致数字。

快速而肮脏的修复(在Unix/Linux上):增加每个进程打开文件的fdlimit,使其达到1024(当然你需要是root)。这更像是服务器故障的问题。

问题是文件权限。我给了apache2用户read&执行对usr/share/zoneinfo和etc/localtime的访问。以前,我还没有将本地时间的家长设置为正确的权限。即,我只更改了localtime和zoneinfo的权限,而没有更改它们的父目录的权限。真愚蠢!从一个问题中走出来,重新回到问题中去,总是很有用的。

你提到"降级",你最近升级了吗?在PHP 5.3.x中,您必须在PHP.ini文件中为date.timezone设置一个有效值。

如果您最近没有升级,请尝试通过重新安装tzdata软件包来解决此问题。我只在CentOS上工作,所以我不确定Ubuntu软件包管理器的名字是什么,但我很确定tzdata是跨发行版的标准配置。

$ -> yum reinstall tzdata # switch 'yum' for Ubuntu package manager
$ -> rm -f /etc/localtime
$ -> ln -sf /usr/share/zoneinfo/UTC /etc/localtime # 'UTC' can be replaced with what you prefer
$ -> date # check to see that it stuck

在此之后,您可能需要重新启动httpd,以确保获取时区信息。

--编辑

罪魁祸首似乎是您的date_i18n()函数,它总是被调用,除非调用代码专门传递了第三个参数"false"。我在$translate设置为false的情况下运行了一些测试数据,并且运行得很好。

function mysql2date( $dateformatstring, $mysqlstring, $translate = true ) {
    $translate = false;
    ...
    if ( $translate )
        return 'date_i18n would have been called';
        //return date_i18n( $dateformatstring, $i );
    ...
}
$testPatterns = array(
    array(
        'dateformatstring'  => 'd.m.y',
        'mysqlstring'       => '2011-10-20 05:35:01'
    ),
    array(
        'dateformatstring'  => 'm',
        'mysqlstring'       => '2011-10-20 05:35:01'
    ),
    array(
        'dateformatstring'  => 'd.m.y',
        'mysqlstring'       => '2011-10-20 05:25:22'
    )
);
foreach ($testPatterns as $testPattern) {
    // Not passing arg to over-ride $translate, forces call to date_i18n()
    var_dump(mysql2date($testPattern['dateformatstring'], $testPattern['mysqlstring']));
    // Forcing $translate to false, makes date() call which works fine
    var_dump(mysql2date($testPattern['dateformatstring'], $testPattern['mysqlstring'], false));
}

我更改了GMT设置的本地时间文件,如mv/usr/share/zoneinfo/亚洲/卡拉奇当地时间然后发生followinf错误。

date_default_timezone_get():时区数据库已损坏-这应该永远不会发生!

解决方案:恢复您的本地时间设置。提示:始终保留旧本地时间的备份副本,以便您可以恢复任何预期的操作系统/软件问题

这可能会帮助您PHP–设置时区