PHPfilectime()函数

filectime() 函数是PHP中用于获取文件inode修改时间的内置函数。它返回文件inode最后被改变的时间戳,这包括文件权限、所有权、链接数等元数据的改变时间。

重要区别

注意:在Unix/Linux系统中,filectime() 返回的是inode修改时间(change time)。而在Windows系统中,它返回的是文件的创建时间(creation time)。这与 filemtime()(文件内容修改时间)和 fileatime()(文件访问时间)有本质区别。

语法

int filectime ( string $filename )

参数说明

参数 描述 类型 是否必需
filename 要检查的文件路径 string

返回值

  • 成功时返回文件inode修改时间的Unix时间戳
  • 失败时返回 FALSE

注意事项

  • 在Unix/Linux系统中,filectime() 返回的是inode状态改变时间,不是文件创建时间
  • 在Windows系统中,filectime() 返回的是文件创建时间
  • inode修改时间会在以下情况更新:
    • 文件权限改变
    • 文件所有者改变
    • 文件链接数改变
    • 文件内容修改(因为inode会记录修改时间)
  • 使用 clearstatcache() 清除文件状态缓存以获得最新信息
  • 对于不存在的文件或权限不足的文件,函数会返回FALSE

示例代码

示例1:基本使用 - 获取文件inode修改时间
<?php
$filename = 'test.txt';

// 获取文件的inode修改时间
$ctime = filectime($filename);

if ($ctime !== false) {
    echo "文件inode修改时间戳: " . $ctime . "<br>";
    echo "格式化时间: " . date('Y-m-d H:i:s', $ctime);
} else {
    echo "无法获取文件inode修改时间";
}
?>
示例2:比较三种文件时间
<?php
function compareFileTimes($filename) {
    if (!file_exists($filename)) {
        return "文件不存在: {$filename}";
    }

    // 清除文件状态缓存
    clearstatcache(true, $filename);

    // 获取三种时间
    $atime = fileatime($filename); // 最后访问时间
    $mtime = filemtime($filename); // 最后修改时间(内容)
    $ctime = filectime($filename); // inode修改时间

    echo "<h4>文件: {$filename}</h4>";
    echo "<table class='table table-bordered'>";
    echo "<tr><th>时间类型</th><th>时间戳</th><th>格式化时间</th><th>描述</th></tr>";
    echo "<tr><td>访问时间 (atime)</td><td>{$atime}</td><td>" . date('Y-m-d H:i:s', $atime) . "</td><td>文件最后被读取的时间</td></tr>";
    echo "<tr><td>修改时间 (mtime)</td><td>{$mtime}</td><td>" . date('Y-m-d H:i:s', $mtime) . "</td><td>文件内容最后被修改的时间</td></tr>";
    echo "<tr><td>改变时间 (ctime)</td><td>{$ctime}</td><td>" . date('Y-m-d H:i:s', $ctime) . "</td><td>文件inode最后被改变的时间</td></tr>";
    echo "</table>";

    // 分析时间关系
    if ($ctime >= $mtime) {
        echo "<p class='text-info'><i class='fas fa-info-circle'></i> ctime ≥ mtime 是正常的,因为内容修改会导致inode记录变化</p>";
    }
}

// 使用示例
compareFileTimes('data.txt');
?>
示例3:检测文件权限或所有权变化
<?php
class FileChangeMonitor {
    private $monitor_file = 'file_changes.json';

    public function __construct() {
        if (!file_exists($this->monitor_file)) {
            file_put_contents($this->monitor_file, '{}');
        }
    }

    public function monitorFile($filename) {
        if (!file_exists($filename)) {
            return ['error' => "文件不存在: {$filename}"];
        }

        clearstatcache(true, $filename);

        // 获取当前状态
        $current_state = [
            'ctime' => filectime($filename),
            'perms' => fileperms($filename),
            'owner' => fileowner($filename),
            'size' => filesize($filename),
            'mtime' => filemtime($filename)
        ];

        // 读取历史状态
        $history = json_decode(file_get_contents($this->monitor_file), true);

        if (!isset($history[$filename])) {
            // 首次监控
            $history[$filename] = [
                'initial_state' => $current_state,
                'last_check' => time(),
                'changes' => []
            ];

            $result = ['status' => '首次监控已建立'];
        } else {
            // 比较状态
            $old_state = $history[$filename]['last_state'] ?? $history[$filename]['initial_state'];
            $changes = [];

            if ($current_state['ctime'] != $old_state['ctime']) {
                $changes[] = 'inode修改时间变化 (可能权限、所有者或链接数改变)';
            }
            if ($current_state['perms'] != $old_state['perms']) {
                $changes[] = '文件权限变化';
            }
            if ($current_state['owner'] != $old_state['owner']) {
                $changes[] = '文件所有者变化';
            }
            if ($current_state['size'] != $old_state['size']) {
                $changes[] = '文件大小变化';
            }
            if ($current_state['mtime'] != $old_state['mtime']) {
                $changes[] = '文件内容修改';
            }

            if (!empty($changes)) {
                // 记录变化
                $change_record = [
                    'timestamp' => time(),
                    'changes' => $changes,
                    'old_state' => $old_state,
                    'new_state' => $current_state
                ];

                $history[$filename]['changes'][] = $change_record;
                $result = ['status' => '检测到变化', 'changes' => $changes];
            } else {
                $result = ['status' => '未检测到变化'];
            }

            $history[$filename]['last_state'] = $current_state;
            $history[$filename]['last_check'] = time();
        }

        // 保存监控数据
        file_put_contents($this->monitor_file, json_encode($history, JSON_PRETTY_PRINT));

        return $result;
    }

    public function getChangeHistory($filename = null) {
        $history = json_decode(file_get_contents($this->monitor_file), true);

        if ($filename) {
            return $history[$filename] ?? null;
        }

        return $history;
    }
}

// 使用示例
$monitor = new FileChangeMonitor();

// 监控文件变化
$result = $monitor->monitorFile('important_config.php');

echo "<pre>";
print_r($result);
echo "</pre>";

// 查看历史记录
$history = $monitor->getChangeHistory('important_config.php');
if ($history) {
    echo "<h5>监控历史:</h5>";
    echo "首次监控时间: " . date('Y-m-d H:i:s', $history['initial_state']['ctime']) . "<br>";
    echo "变化次数: " . count($history['changes']);
}
?>
示例4:检测文件是否被非法修改
<?php
class FileIntegrityGuard {
    private $signature_file = 'file_signatures.dat';
    private $signatures = [];

    public function __construct() {
        if (file_exists($this->signature_file)) {
            $this->signatures = unserialize(file_get_contents($this->signature_file));
        }
    }

    public function createSignature($filename) {
        if (!file_exists($filename)) {
            return false;
        }

        clearstatcache(true, $filename);

        $signature = [
            'filename' => $filename,
            'ctime' => filectime($filename),
            'mtime' => filemtime($filename),
            'size' => filesize($filename),
            'perms' => fileperms($filename),
            'owner' => fileowner($filename),
            'content_hash' => md5_file($filename),
            'created_at' => time()
        ];

        $this->signatures[$filename] = $signature;
        $this->saveSignatures();

        return $signature;
    }

    public function verifySignature($filename) {
        if (!isset($this->signatures[$filename])) {
            return ['status' => '未找到签名记录'];
        }

        if (!file_exists($filename)) {
            return ['status' => '文件已删除'];
        }

        clearstatcache(true, $filename);

        $original = $this->signatures[$filename];
        $current = [
            'ctime' => filectime($filename),
            'mtime' => filemtime($filename),
            'size' => filesize($filename),
            'perms' => fileperms($filename),
            'owner' => fileowner($filename),
            'content_hash' => md5_file($filename)
        ];

        $issues = [];

        if ($current['ctime'] != $original['ctime']) {
            $issues[] = [
                'type' => 'inode修改',
                'detail' => '文件inode已被修改(权限、所有者或链接数改变)',
                'original' => date('Y-m-d H:i:s', $original['ctime']),
                'current' => date('Y-m-d H:i:s', $current['ctime'])
            ];
        }

        if ($current['mtime'] != $original['mtime']) {
            $issues[] = [
                'type' => '内容修改',
                'detail' => '文件内容已被修改',
                'original' => date('Y-m-d H:i:s', $original['mtime']),
                'current' => date('Y-m-d H:i:s', $current['mtime'])
            ];
        }

        if ($current['content_hash'] != $original['content_hash']) {
            $issues[] = [
                'type' => '内容哈希不匹配',
                'detail' => '文件内容已被篡改',
                'original_hash' => $original['content_hash'],
                'current_hash' => $current['content_hash']
            ];
        }

        if ($current['perms'] != $original['perms']) {
            $issues[] = [
                'type' => '权限修改',
                'detail' => '文件权限已被修改',
                'original_perms' => substr(sprintf('%o', $original['perms']), -4),
                'current_perms' => substr(sprintf('%o', $current['perms']), -4)
            ];
        }

        if (empty($issues)) {
            return [
                'status' => '完整',
                'message' => '文件未被修改'
            ];
        } else {
            return [
                'status' => '篡改检测',
                'issues' => $issues,
                'message' => '检测到 ' . count($issues) . ' 处修改'
            ];
        }
    }

    private function saveSignatures() {
        file_put_contents($this->signature_file, serialize($this->signatures));
    }
}

// 使用示例
$guard = new FileIntegrityGuard();

// 为重要文件创建签名
$signature = $guard->createSignature('config.php');
echo "已为 config.php 创建签名<br>";

// 验证文件完整性
$result = $guard->verifySignature('config.php');

echo "<pre>";
print_r($result);
echo "</pre>";
?>
示例5:文件系统审计工具
<?php
class FileSystemAuditor {
    private $audit_log = 'filesystem_audit.log';

    public function auditDirectory($directory, $recursive = true) {
        if (!is_dir($directory)) {
            return ['error' => "目录不存在: {$directory}"];
        }

        $audit_results = [
            'directory' => $directory,
            'audit_time' => time(),
            'files' => [],
            'statistics' => [
                'total_files' => 0,
                'recently_changed' => 0,
                'permission_issues' => 0,
                'ownership_issues' => 0
            ]
        ];

        $iterator = $recursive ?
            new RecursiveIteratorIterator(new RecursiveDirectoryIterator($directory)) :
            new DirectoryIterator($directory);

        foreach ($iterator as $file) {
            if ($file->isDot()) {
                continue;
            }

            if ($file->isFile()) {
                $filepath = $file->getPathname();
                clearstatcache(true, $filepath);

                $file_info = [
                    'name' => $file->getFilename(),
                    'path' => $filepath,
                    'size' => $file->getSize(),
                    'ctime' => filectime($filepath),
                    'mtime' => filemtime($filepath),
                    'atime' => fileatime($filepath),
                    'perms' => fileperms($filepath),
                    'owner' => fileowner($filepath),
                    'group' => filegroup($filepath)
                ];

                // 格式化权限
                $file_info['perms_octal'] = substr(sprintf('%o', $file_info['perms']), -4);
                $file_info['perms_human'] = $this->formatPermissions($file_info['perms']);

                // 检查安全问题
                $file_info['issues'] = $this->checkSecurityIssues($file_info);

                // 统计
                $audit_results['statistics']['total_files']++;

                if (!empty($file_info['issues'])) {
                    $audit_results['statistics']['permission_issues']++;
                }

                // 检查最近修改的文件(24小时内)
                if ((time() - $file_info['ctime']) < 86400) {
                    $audit_results['statistics']['recently_changed']++;
                }

                $audit_results['files'][] = $file_info;
            }
        }

        // 记录审计日志
        $this->logAudit($audit_results);

        return $audit_results;
    }

    private function checkSecurityIssues($file_info) {
        $issues = [];

        // 检查权限是否过宽
        $perms = $file_info['perms_octal'];
        if (in_array($perms, ['0777', '0775', '0666'])) {
            $issues[] = '权限过宽: ' . $perms;
        }

        // 检查是否有设置用户ID位
        if (($file_info['perms'] & 0x800) != 0) {
            $issues[] = '设置了setuid位';
        }

        // 检查是否有设置组ID位
        if (($file_info['perms'] & 0x400) != 0) {
            $issues[] = '设置了setgid位';
        }

        // 检查粘滞位
        if (($file_info['perms'] & 0x200) != 0) {
            $issues[] = '设置了粘滞位';
        }

        return $issues;
    }

    private function formatPermissions($perms) {
        $symbolic = '';

        // 文件类型
        $symbolic .= (($perms & 0xC000) == 0xC000) ? 's' : // socket
                    (($perms & 0xA000) == 0xA000) ? 'l' : // 符号链接
                    (($perms & 0x8000) == 0x8000) ? '-' : // 普通文件
                    (($perms & 0x6000) == 0x6000) ? 'b' : // 块设备
                    (($perms & 0x4000) == 0x4000) ? 'd' : // 目录
                    (($perms & 0x2000) == 0x2000) ? 'c' : // 字符设备
                    (($perms & 0x1000) == 0x1000) ? 'p' : // FIFO
                    'u'; // 未知

        // 所有者权限
        $symbolic .= (($perms & 0x0100) ? 'r' : '-');
        $symbolic .= (($perms & 0x0080) ? 'w' : '-');
        $symbolic .= (($perms & 0x0040) ?
                    (($perms & 0x0800) ? 's' : 'x') :
                    (($perms & 0x0800) ? 'S' : '-'));

        // 组权限
        $symbolic .= (($perms & 0x0020) ? 'r' : '-');
        $symbolic .= (($perms & 0x0010) ? 'w' : '-');
        $symbolic .= (($perms & 0x0008) ?
                    (($perms & 0x0400) ? 's' : 'x') :
                    (($perms & 0x0400) ? 'S' : '-'));

        // 其他用户权限
        $symbolic .= (($perms & 0x0004) ? 'r' : '-');
        $symbolic .= (($perms & 0x0002) ? 'w' : '-');
        $symbolic .= (($perms & 0x0001) ?
                    (($perms & 0x0200) ? 't' : 'x') :
                    (($perms & 0x0200) ? 'T' : '-'));

        return $symbolic;
    }

    private function logAudit($audit_results) {
        $log_entry = sprintf(
            "[%s] 审计目录: %s, 文件总数: %d, 安全问题: %d\n",
            date('Y-m-d H:i:s'),
            $audit_results['directory'],
            $audit_results['statistics']['total_files'],
            $audit_results['statistics']['permission_issues']
        );

        file_put_contents($this->audit_log, $log_entry, FILE_APPEND);
    }
}

// 使用示例
$auditor = new FileSystemAuditor();
$result = $auditor->auditDirectory('/var/www/html', true);

echo "<h4>文件系统审计结果:</h4>";
echo "审计目录: " . $result['directory'] . "<br>";
echo "文件总数: " . $result['statistics']['total_files'] . "<br>";
echo "最近修改的文件: " . $result['statistics']['recently_changed'] . "<br>";
echo "权限问题: " . $result['statistics']['permission_issues'] . "<br>";

// 显示前5个文件的详细信息
echo "<h5>文件示例:</h5>";
for ($i = 0; $i < min(5, count($result['files'])); $i++) {
    $file = $result['files'][$i];
    echo "文件: {$file['name']}, 权限: {$file['perms_human']}, 大小: {$file['size']} 字节<br>";
    echo "ctime: " . date('Y-m-d H:i:s', $file['ctime']) . "<br>";
    if (!empty($file['issues'])) {
        echo "问题: " . implode(', ', $file['issues']) . "<br>";
    }
    echo "<hr>";
}
?>

与类似函数的比较

函数 描述 返回内容 适用场景
filectime() 获取文件inode修改时间 Unix时间戳 检测文件权限、所有权等元数据变化
filemtime() 获取文件内容修改时间 Unix时间戳 检测文件内容变化、版本控制
fileatime() 获取文件最后访问时间 Unix时间戳 监控文件访问频率、缓存管理
stat() 获取文件所有状态信息 包含ctime、mtime、atime的数组 需要完整文件信息的场景
lstat() 获取文件或符号链接状态 数组(与stat类似) 处理符号链接的场景

不同操作系统差异

跨平台注意事项
操作系统 filectime() 含义 注意事项
Unix/Linux inode修改时间(change time) 权限、所有权、链接数等元数据变化时更新
Windows 文件创建时间(creation time) 文件首次创建的时间,通常不会改变
macOS inode修改时间 与Unix/Linux行为一致

性能优化建议

最佳实践
  • 对于频繁查询的文件时间,使用缓存机制避免重复调用
  • 在处理多个文件时,使用clearstatcache()确保获取最新信息
  • 注意跨平台差异,编写可移植代码时考虑操作系统区别
  • 使用绝对路径而不是相对路径,避免路径解析开销
  • 结合filemtime()fileatime()进行综合文件状态分析
  • 对于安全敏感的应用,考虑使用stat()一次性获取所有文件状态

安全注意事项

安全警告
  • 权限检查: 调用filectime()前确保文件存在且有读取权限
  • 时间篡改: 注意系统时间可能被篡改,影响时间戳的可靠性
  • 竞态条件: 在多进程/线程环境中,文件状态可能在检查和使用之间改变
  • 符号链接: 使用lstat()检查符号链接本身,而不是其目标
  • 敏感文件: 审计系统文件时注意不要泄露敏感信息
完整示例:文件变更监控系统
<?php
class FileChangeDetectionSystem {
    private $storage_file = 'file_changes.json';
    private $alert_thresholds = [
        'ctime_change' => true,    // 监控inode变化
        'mtime_change' => true,    // 监控内容变化
        'perms_change' => true,    // 监控权限变化
        'owner_change' => true,    // 监控所有者变化
        'size_change' => true      // 监控大小变化
    ];

    public function __construct() {
        if (!file_exists($this->storage_file)) {
            file_put_contents($this->storage_file, json_encode([]));
        }
    }

    public function monitorFiles($file_list) {
        $results = [];
        $alerts = [];

        foreach ($file_list as $filename) {
            if (!file_exists($filename)) {
                $results[$filename] = ['status' => '不存在'];
                continue;
            }

            clearstatcache(true, $filename);

            $current_state = $this->getFileState($filename);
            $previous_state = $this->getPreviousState($filename);

            if ($previous_state) {
                $changes = $this->detectChanges($previous_state, $current_state);

                if (!empty($changes)) {
                    $results[$filename] = [
                        'status' => '已修改',
                        'changes' => $changes,
                        'current_state' => $current_state
                    ];

                    // 检查是否需要警报
                    $file_alerts = $this->checkAlerts($changes);
                    if (!empty($file_alerts)) {
                        $alerts[$filename] = $file_alerts;
                    }
                } else {
                    $results[$filename] = ['status' => '未修改'];
                }
            } else {
                $results[$filename] = [
                    'status' => '首次监控',
                    'current_state' => $current_state
                ];
            }

            // 保存当前状态
            $this->saveFileState($filename, $current_state);
        }

        // 触发警报
        if (!empty($alerts)) {
            $this->triggerAlerts($alerts);
        }

        return [
            'results' => $results,
            'alerts' => $alerts,
            'monitored_count' => count($file_list),
            'changed_count' => count($alerts)
        ];
    }

    private function getFileState($filename) {
        return [
            'ctime' => filectime($filename),
            'mtime' => filemtime($filename),
            'atime' => fileatime($filename),
            'size' => filesize($filename),
            'perms' => fileperms($filename),
            'owner' => fileowner($filename),
            'group' => filegroup($filename),
            'inode' => fileinode($filename),
            'hash' => md5_file($filename),
            'checked_at' => time()
        ];
    }

    private function detectChanges($old, $new) {
        $changes = [];

        foreach ($this->alert_thresholds as $key => $enabled) {
            if (!$enabled) continue;

            switch ($key) {
                case 'ctime_change':
                    if ($old['ctime'] != $new['ctime']) {
                        $changes[] = [
                            'type' => 'inode修改',
                            'old' => date('Y-m-d H:i:s', $old['ctime']),
                            'new' => date('Y-m-d H:i:s', $new['ctime'])
                        ];
                    }
                    break;

                case 'mtime_change':
                    if ($old['mtime'] != $new['mtime']) {
                        $changes[] = [
                            'type' => '内容修改',
                            'old' => date('Y-m-d H:i:s', $old['mtime']),
                            'new' => date('Y-m-d H:i:s', $new['mtime'])
                        ];
                    }
                    break;

                case 'perms_change':
                    if ($old['perms'] != $new['perms']) {
                        $changes[] = [
                            'type' => '权限修改',
                            'old' => substr(sprintf('%o', $old['perms']), -4),
                            'new' => substr(sprintf('%o', $new['perms']), -4)
                        ];
                    }
                    break;

                case 'owner_change':
                    if ($old['owner'] != $new['owner']) {
                        $changes[] = [
                            'type' => '所有者修改',
                            'old' => $old['owner'],
                            'new' => $new['owner']
                        ];
                    }
                    break;

                case 'size_change':
                    if ($old['size'] != $new['size']) {
                        $changes[] = [
                            'type' => '大小修改',
                            'old' => $old['size'],
                            'new' => $new['size']
                        ];
                    }
                    break;
            }
        }

        return $changes;
    }

    private function checkAlerts($changes) {
        $alerts = [];

        foreach ($changes as $change) {
            // 根据变化类型生成警报
            $alerts[] = [
                'level' => $this->getAlertLevel($change['type']),
                'message' => "检测到{$change['type']}",
                'details' => $change
            ];
        }

        return $alerts;
    }

    private function getAlertLevel($change_type) {
        $critical_changes = ['inode修改', '所有者修改', '权限修改'];
        $warning_changes = ['内容修改', '大小修改'];

        if (in_array($change_type, $critical_changes)) {
            return 'CRITICAL';
        } elseif (in_array($change_type, $warning_changes)) {
            return 'WARNING';
        } else {
            return 'INFO';
        }
    }

    private function triggerAlerts($alerts) {
        $alert_message = "文件变更警报:\n";

        foreach ($alerts as $filename => $file_alerts) {
            $alert_message .= "\n文件: {$filename}\n";
            foreach ($file_alerts as $alert) {
                $alert_message .= sprintf(
                    "  [%s] %s\n",
                    $alert['level'],
                    $alert['message']
                );
            }
        }

        // 记录到日志文件
        file_put_contents(
            'security_alerts.log',
            date('[Y-m-d H:i:s] ') . $alert_message . "\n",
            FILE_APPEND
        );

        // 可以扩展:发送邮件、短信等通知
        // $this->sendEmailAlert($alert_message);
    }

    private function getPreviousState($filename) {
        $data = json_decode(file_get_contents($this->storage_file), true);
        return $data[$filename] ?? null;
    }

    private function saveFileState($filename, $state) {
        $data = json_decode(file_get_contents($this->storage_file), true);
        $data[$filename] = $state;
        file_put_contents($this->storage_file, json_encode($data, JSON_PRETTY_PRINT));
    }
}

// 使用示例
$monitor = new FileChangeDetectionSystem();

// 监控重要文件
$important_files = [
    '/etc/passwd',
    '/etc/shadow',
    '/etc/ssh/sshd_config',
    '/var/www/html/index.php',
    '/var/log/auth.log'
];

$result = $monitor->monitorFiles($important_files);

echo "

文件变更监控结果:

"; echo "监控文件数: {$result['monitored_count']}
"; echo "变更文件数: {$result['changed_count']}
"; if (!empty($result['alerts'])) { echo "
安全警报:
"; foreach ($result['alerts'] as $filename => $alerts) { echo "{$filename}:
"; foreach ($alerts as $alert) { echo "  [{$alert['level']}] {$alert['message']}
"; } } } ?>