fatfs: fix incorrect mtime returned for files created during DST

mktime function uses tm_isdst member as an indicator whether the time
stamp is expected to be in daylight saving time (1) or not (0).
FAT filesystem uses local time as mtime, so no information about DST
is available from the filesystem.

According to mktime documentation, tm_isdst can be set to -1, in which
case the C library will try to determine if DST was or wasn't in
effect at that time, and will set UTC time accordingly.

Note that the conversion from UTC to local time and then back to UTC
(time_t -> localtime_r -> FAT timestamp -> mktime -> time_t) does not
always recover the same UTC time. In particular, the local time in the
hour before DST comes into effect can be interpreted as "before DST"
or "after DST", which would correspond to different UTC values. In
this case which option the C library chooses is undefined.

Closes https://github.com/espressif/esp-idf/issues/9039
Originally reported in https://github.com/espressif/arduino-esp32/issues/6786
This commit is contained in:
Ivan Grokhotkov 2022-05-31 13:34:29 +02:00 committed by BOT
parent dc4c9c087f
commit d7db6c3148
4 changed files with 52 additions and 8 deletions

View File

@ -317,13 +317,14 @@ void test_fatfs_truncate_file(const char* filename)
void test_fatfs_stat(const char* filename, const char* root_dir)
{
struct tm tm;
tm.tm_year = 2017 - 1900;
tm.tm_mon = 11;
tm.tm_mday = 8;
tm.tm_hour = 19;
tm.tm_min = 51;
tm.tm_sec = 10;
struct tm tm = {
.tm_year = 2017 - 1900,
.tm_mon = 11,
.tm_mday = 8,
.tm_hour = 19,
.tm_min = 51,
.tm_sec = 10
};
time_t t = mktime(&tm);
printf("Setting time: %s", asctime(&tm));
struct timeval now = { .tv_sec = t };
@ -348,6 +349,34 @@ void test_fatfs_stat(const char* filename, const char* root_dir)
TEST_ASSERT_FALSE(st.st_mode & S_IFREG);
}
void test_fatfs_mtime_dst(const char* filename, const char* root_dir)
{
struct timeval tv = { 1653638041, 0 };
settimeofday(&tv, NULL);
setenv("TZ", "MST7MDT,M3.2.0,M11.1.0", 1);
tzset();
struct tm tm;
time_t sys_time = tv.tv_sec;
localtime_r(&sys_time, &tm);
printf("Setting time: %s", asctime(&tm));
test_fatfs_create_file_with_text(filename, "foo\n");
struct stat st;
TEST_ASSERT_EQUAL(0, stat(filename, &st));
time_t mtime = st.st_mtime;
struct tm mtm;
localtime_r(&mtime, &mtm);
printf("File time: %s", asctime(&mtm));
TEST_ASSERT(llabs(mtime - sys_time) < 2); // fatfs library stores time with 2 second precision
unsetenv("TZ");
tzset();
}
void test_fatfs_utime(const char* filename, const char* root_dir)
{
struct stat achieved_stat;

View File

@ -53,6 +53,8 @@ void test_fatfs_truncate_file(const char* path);
void test_fatfs_stat(const char* filename, const char* root_dir);
void test_fatfs_mtime_dst(const char* filename, const char* root_dir);
void test_fatfs_utime(const char* filename, const char* root_dir);
void test_fatfs_unlink(const char* filename);

View File

@ -125,6 +125,13 @@ TEST_CASE("(WL) stat returns correct values", "[fatfs][wear_levelling]")
test_teardown();
}
TEST_CASE("(WL) stat returns correct mtime if DST is enabled", "[fatfs][wear_levelling]")
{
test_setup();
test_fatfs_mtime_dst("/spiflash/statdst.txt", "/spiflash");
test_teardown();
}
TEST_CASE("(WL) utime sets modification time", "[fatfs][wear_levelling]")
{
test_setup();

View File

@ -611,7 +611,13 @@ static int vfs_fat_stat(void* ctx, const char * path, struct stat * st)
.tm_year = fdate.year + 80,
.tm_sec = ftime.sec * 2,
.tm_min = ftime.min,
.tm_hour = ftime.hour
.tm_hour = ftime.hour,
/* FAT doesn't keep track if the time was DST or not, ask the C library
* to try to figure this out. Note that this may yield incorrect result
* in the hour before the DST comes in effect, when the local time can't
* be converted to UTC uniquely.
*/
.tm_isdst = -1
};
st->st_mtime = mktime(&tm);
st->st_atime = 0;