VFAT、Linux:重新启动后显示无效的文件时间戳

VFAT、Linux:重新启动后显示无效的文件时间戳

刚刚遇到一个问题:重新启动 Linux 系统时,已挂载的 VFAT 文件系统中的所有文件的时间戳显示在错误的时区中。设备似乎开始认为其本地时间采用 UTC 格式,因此它会显示带有偏移的所有时间戳。

重现步骤:

  • 创建一些小的 FAT 格式的映像:

    dd if=/dev/zero of=small.img bs=1M seek=1 count=0

    mkfs.vfat small.img

  • 在本地挂载此镜像:

    mount -t vfat -o umask=0022,gid=1001,uid=1001 small.img mnt

  • 将时区设置为某个非 UTC 时区;

  • 在已安装的文件系统中创建一个文件(即touch mnt/newfile

  • 观察文件修改/更改时间戳:对于当前设置的时间戳,它们是正确的:

    stat mnt/newfile

    File: mnt/newfile
    Size: 0             Blocks: 0          IO Block: 16384  regular empty file
    Device: 700h/1792d  Inode: 40          Links: 1
    Access: (0755/-rwxr-xr-x)  Uid: (    0/    root)   Gid: (    0/    root)
    Access: 2021-03-22 12:19:56.000000000 +0100
    Modify: 2021-03-22 12:19:56.000000000 +0100
    Change: 2021-03-22 12:19:56.000000000 +0100
    Birth: -
    

    timedatectl

    Local time: Mon 2021-03-22 12:19:07 CET
    Universal time: Mon 2021-03-22 11:19:07 UTC
    RTC time: Mon 2021-03-22 11:19:07
    Time zone: Europe/Vienna (CET, +0100)
    System clock synchronized: yes
    NTP service: active
    RTC in local TZ: no
    
  • 卸载文件系统,检查重新挂载是否发生了任何更改: umount mnt; mount -t vfat -o umask=0022,gid=1001,uid=1001 small.img mnt; stat mnt/newfile

    File: mnt/newfile
    Size: 0          Blocks: 0          IO Block: 16384  regular empty file
    Device: 700h/1792d   Inode: 64          Links: 1
    Access: (0755/-rwxr-xr-x)  Uid: (    0/    root)   Gid: (    0/    root)
    Access: 2021-03-22 00:00:00.000000000 +0100
    Modify: 2021-03-22 12:19:56.000000000 +0100
    Change: 2021-03-22 12:19:56.000000000 +0100
    Birth: -
    
  • 重新启动系统;

  • 再次挂载镜像,看看创建的文件的时间戳:

    File: mnt/newfile
    Size: 0             Blocks: 0          IO Block: 16384  regular empty file
    Device: 700h/1792d  Inode: 26          Links: 1
    Access: (0755/-rwxr-xr-x)  Uid: (    0/    root)   Gid: (    0/    root)
    Access: 2021-03-22 01:00:00.000000000 +0100
    Modify: 2021-03-22 13:19:56.000000000 +0100
    Change: 2021-03-22 13:19:56.000000000 +0100
    Birth: -
    

可以清楚地观察到,时间向前移动了 1 小时(12:10 到 13:19),而时区显示相同 - +0100。看起来mount现在认为文件时间戳是用 UTC 记录的,因此它尝试使用“正确”的偏移来显示它们。

要检查上一条语句的有效性,让我们tz=UTC显式地使用该选项重新挂载相同的文件系统:

mount -t vfat -o umask=0022,gid=1001,uid=1001,tz=UTC small.img mnt; stat mnt/newfile

File: mnt/newfile
Size: 0          Blocks: 0          IO Block: 16384  regular empty file
Device: 700h/1792d   Inode: 50          Links: 1
Access: (0755/-rwxr-xr-x)  Uid: (    0/    root)   Gid: (    0/    root)
Access: 2021-03-22 01:00:00.000000000 +0100
Modify: 2021-03-22 13:19:56.000000000 +0100
Change: 2021-03-22 13:19:56.000000000 +0100
Birth: -

即使系统的时区确实是 CET:

date

Mon Mar 22 12:26:42 CET 2021

聚苯乙烯https://stackoverflow.com/questions/10068855/how-do-i-get-the- Correct-modified-datetime-of-a-fat32-file-regardless-of-timezo不是这个问题的答案,因为我无法从中得到答案,为什么在机器重新启动后而不是重新安装后会立即看到此更改?如果 vfat 以本地时间存储时间戳,为什么重新启动后挂载会假定时间戳为 UTC 而不是本地时间?

答案1

看来问题出在Linux内核本身,内核和用户空间之间的时区可能(通常)不同。Linux 内核源代码树time.c中的 文件保存(并导出) ,然后在 FAT 时间 <-> UNIX 时间约定中使用。该结构体的字段用于显示当前时区与 UTC 之间的差异,如果选项为kernel/timestruct timezone sys_tzfs/fat/misc.ctz_minuteswesttz=UTC不是传递给mount.vfat命令。然而,上述字段默认设置为 0,如所解释的这里,

在 Linux 下,如果在具有非 NULL tz 参数的第一次调用(启动后)时,tv 参数为 NULL 并且 tz_minuteswest 字段非零,则有一些与 settimeofday() 系统调用相关的特殊“扭曲时钟”语义。 (对于这种情况,tz_dsttime 字段应为零。)在这种情况下,假定 CMOS 时钟为本地时间,并且必须将其增加此量才能获得 UTC 系统时间。毫无疑问,使用此功能是一个坏主意。

因此,让内核(及其驱动程序)始终看到正确时区的唯一方法是调用settimeofday()withtz参数,其中tz.tz_minuteswest所需的时间偏移量为“向西”,以小时为单位,相对于 UTC(即 -60 CET 等),并tz_dsttime在每次系统启动后设置为 0。这可以通过将系统时区设置(以任何方式)来实现当前的时区将其更改为另一个(即UTC)后timedatectl,因为如果所需的时区等于当前时区,则命令行工具 sa通常不会执行实际的时区更改。创建以下代码来证明这个概念:

#include <sys/time.h>
#include <stdio.h>

int main()
{
    struct timeval tv;
    struct timezone tz;
    int ret = gettimeofday(&tv, &tz);
    printf("%d, %dr\n", tz.tz_minuteswest, tz.tz_dsttime);
    return ret;
}

该代码执行如下:

$gcc testtz.cpp -o testtz
$./testtz
minuteswest: 0, dsttime: 0r
$timedatectl set-timezone Europe/Vienna
$./testtz
minuteswest: 0, dsttime: 0r
$timedatectl set-timezone UTC
$timedatectl set-timezone Europe/Vienna
$./testtz
minuteswest: -60, dsttime: 0r

timezone尽管该结构的使用被认为已经过时,但这个问题不会很快消失。所以对于我的问题,我会考虑使用time_offset=分钟选项mount.vfat

相关内容