2

Just encountered a problem: when rebooting a Linux system, timestamps of all files in the mounted VFAT filesystem are shown in the incorrect timezone. It seems like a device starts thinking that its local time is in UTC, so it displays all timestamps with the shift.

Steps to reproduce:

  • Create some small FAT-formatted image:

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

    mkfs.vfat small.img

  • Mount this image locally:

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

  • Set the timezone to some non-UTC one;

  • Create a file in the mounted filesystem (ie. touch mnt/newfile)

  • Observe the file modification/change timestamps: they are correct, concerning the currently set one:

    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
    
  • Unmount the filesystem, to check if anything has been changed with the remount: 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: -
    
  • Reboot the system;

  • Mount the image once more, have a look at the created file's timestamps:

    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: -
    

It can be clearly observable, that the time is shifted forward by 1 hour (12:10 to 13:19), while the timezone is shown the same - +0100. Looks like mount now thinks that the file timestamps were recorded in UTC, so it tries to display them with the "correct" shift.

To check the validity of the previous statement, let's remount the same filesystem with the tz=UTC option explicitly:

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: -

Even though the system's timezone is CET indeed:

date

Mon Mar 22 12:26:42 CET 2021

P.S. https://stackoverflow.com/questions/10068855/how-do-i-get-the-correct-modified-datetime-of-a-fat32-file-regardless-of-timezo is not an answer to this question, since I can't get from it, why is this change seen right after reboot of the machine and not after a remount? If vfat stores timestamps in local time, why does mount after rebooting assumes that the timestamps are in UTC rather than local time?

2
  • 1
    FAT doesn't record the access time, only the access date. See the data resolution section on Wikipedia. I am sure there are more quirks that make it hard to map FAT timing to POSIX/UNIX timing. – berndbausch Mar 23 at 1:42
  • @berndbausch thank you for the quick response, the question was not specifically about the access time, but about the very fact that the times (access/modify/change) are different after a reboot. Leaving the access time behind then, what's wrong with the other two, if you have an idea? – vmlinuz Mar 23 at 9:31
2

Seems like the problem resides in the Linux kernel itself, as the timezone may (and usually do) differ between the kernel and the userspace. time.c file in the kernel/time in the Linux kernel source tree holds (and exports) the struct timezone sys_tz, which is then used in fs/fat/misc.c in the FAT time <-> UNIX time conventions. The tz_minuteswest field of this struct is used to show the difference between the current timezone and the UTC, and it is taken into consideration if the tz=UTC option is not passed to the mount.vfat command. However, the aforementioned field is by default set to 0, As explained here,

Under Linux, there are some peculiar "warp clock" semantics associated with the settimeofday() system call if on the very first call (after booting) that has a non-NULL tz argument, the tv argument is NULL and the tz_minuteswest field is nonzero. (The tz_dsttime field should be zero for this case.) In such a case it is assumed that the CMOS clock is on local time, and that it has to be incremented by this amount to get UTC system time. No doubt it is a bad idea to use this feature.

So the only way to have the kernel (and its drivers) always see the correct timezone, is to call the settimeofday() with tz argument, where tz.tz_minuteswest is the needed time offset "to the west" in hours, relative to the UTC (ie. -60 for CET, etc.), and tz_dsttime set to 0, after each system boot. This can be achieved by setting (by any means) the system timezone to the current timezone after changing it to some another one (ie.UTC), since the command line tools s.a. timedatectl usually do not perform the actual timezone change, if the desired timezone equals to the current one. The following code was created to prove this concept:

#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;
}

This code execution was as follows:

$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

This problem won't go anytime soon, even though the usage of the timezone structure is considered obsolete. So for my problem, I'll consider the usage of the time_offset=minutes option of the mount.vfat.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.