]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
fat: add KUnit tests for timestamp conversion helpers
authoravivdaum <aviv.daum@gmail.com>
Sun, 15 Mar 2026 22:24:04 +0000 (00:24 +0200)
committerChristian Brauner <brauner@kernel.org>
Tue, 24 Mar 2026 22:37:16 +0000 (23:37 +0100)
Extend fat_test with coverage for FAT timestamp edge cases that are not
currently exercised.

Add tests for fat_time_unix2fat() clamping at the UTC boundaries and
when time_offset pushes an otherwise valid timestamp outside the FAT
date range. Also cover the NULL time_cs path used by existing callers,
and verify fat_truncate_atime() truncation across timezone offsets.

Link: https://patch.msgid.link/20260315222404.9678-1-aviv.daum@gmail.com
Acked-by: OGAWA Hirofumi <hirofumi@mail.parknet.co.jp>
Signed-off-by: avivdaum <aviv.daum@gmail.com>
Signed-off-by: Christian Brauner <brauner@kernel.org>
fs/fat/fat_test.c
fs/fat/misc.c

index 1f00626590675849db82cecfa31103e990f5390e..5c97a7fcc41d0ee74f68c37b14243a68344757e7 100644 (file)
@@ -29,6 +29,22 @@ struct fat_timestamp_testcase {
        int time_offset;
 };
 
+struct fat_unix2fat_clamp_testcase {
+       const char *name;
+       struct timespec64 ts;
+       __le16 time;
+       __le16 date;
+       u8 cs;
+       int time_offset;
+};
+
+struct fat_truncate_atime_testcase {
+       const char *name;
+       struct timespec64 ts;
+       struct timespec64 expected;
+       int time_offset;
+};
+
 static struct fat_timestamp_testcase time_test_cases[] = {
        {
                .name = "Earliest possible UTC (1980-01-01 00:00:00)",
@@ -120,13 +136,92 @@ static struct fat_timestamp_testcase time_test_cases[] = {
        },
 };
 
+static struct fat_unix2fat_clamp_testcase unix2fat_clamp_test_cases[] = {
+       {
+               .name = "Clamp to earliest FAT date for 1979-12-31 23:59:59 UTC",
+               .ts = {.tv_sec = 315532799LL, .tv_nsec = 0L},
+               .time = cpu_to_le16(0),
+               .date = cpu_to_le16(33),
+               .cs = 0,
+               .time_offset = 0,
+       },
+       {
+               .name = "Clamp after time_offset=-60 pushes 1980-01-01 00:30 UTC below 1980",
+               .ts = {.tv_sec = 315534600LL, .tv_nsec = 0L},
+               .time = cpu_to_le16(0),
+               .date = cpu_to_le16(33),
+               .cs = 0,
+               .time_offset = -60,
+       },
+       {
+               .name = "Clamp to latest FAT date for 2108-01-01 00:00:00 UTC",
+               .ts = {.tv_sec = 4354819200LL, .tv_nsec = 0L},
+               .time = cpu_to_le16(49021),
+               .date = cpu_to_le16(65439),
+               .cs = 199,
+               .time_offset = 0,
+       },
+       {
+               .name = "Clamp after time_offset=60 pushes 2107-12-31 23:30 UTC beyond 2107",
+               .ts = {.tv_sec = 4354817400LL, .tv_nsec = 0L},
+               .time = cpu_to_le16(49021),
+               .date = cpu_to_le16(65439),
+               .cs = 199,
+               .time_offset = 60,
+       },
+};
+
+static struct fat_truncate_atime_testcase truncate_atime_test_cases[] = {
+       {
+               .name = "UTC atime truncates to 2004-02-29 00:00:00",
+               .ts = {.tv_sec = 1078058096LL, .tv_nsec = 789000000L},
+               .expected = {.tv_sec = 1078012800LL, .tv_nsec = 0L},
+               .time_offset = 0,
+       },
+       {
+               .name = "time_offset=-60 truncates 2004-02-29 00:30 UTC to previous local midnight",
+               .ts = {.tv_sec = 1078014645LL, .tv_nsec = 123000000L},
+               .expected = {.tv_sec = 1077930000LL, .tv_nsec = 0L},
+               .time_offset = -60,
+       },
+       {
+               .name = "time_offset=60 truncates 2004-02-29 23:30 UTC to next local midnight",
+               .ts = {.tv_sec = 1078097445LL, .tv_nsec = 123000000L},
+               .expected = {.tv_sec = 1078095600LL, .tv_nsec = 0L},
+               .time_offset = 60,
+       },
+};
+
 static void time_testcase_desc(struct fat_timestamp_testcase *t,
                               char *desc)
 {
        strscpy(desc, t->name, KUNIT_PARAM_DESC_SIZE);
 }
 
+static void unix2fat_clamp_testcase_desc(struct fat_unix2fat_clamp_testcase *t,
+                                        char *desc)
+{
+       strscpy(desc, t->name, KUNIT_PARAM_DESC_SIZE);
+}
+
+static void truncate_atime_testcase_desc(struct fat_truncate_atime_testcase *t,
+                                        char *desc)
+{
+       strscpy(desc, t->name, KUNIT_PARAM_DESC_SIZE);
+}
+
 KUNIT_ARRAY_PARAM(fat_time, time_test_cases, time_testcase_desc);
+KUNIT_ARRAY_PARAM(fat_unix2fat_clamp, unix2fat_clamp_test_cases,
+                 unix2fat_clamp_testcase_desc);
+KUNIT_ARRAY_PARAM(fat_truncate_atime, truncate_atime_test_cases,
+                 truncate_atime_testcase_desc);
+
+static void fat_test_set_time_offset(struct msdos_sb_info *sbi, int time_offset)
+{
+       *sbi = (struct msdos_sb_info){};
+       sbi->options.tz_set = 1;
+       sbi->options.time_offset = time_offset;
+}
 
 static void fat_time_fat2unix_test(struct kunit *test)
 {
@@ -135,8 +230,7 @@ static void fat_time_fat2unix_test(struct kunit *test)
        struct fat_timestamp_testcase *testcase =
                (struct fat_timestamp_testcase *)test->param_value;
 
-       fake_sb.options.tz_set = 1;
-       fake_sb.options.time_offset = testcase->time_offset;
+       fat_test_set_time_offset(&fake_sb, testcase->time_offset);
 
        fat_time_fat2unix(&fake_sb, &ts,
                          testcase->time,
@@ -160,18 +254,17 @@ static void fat_time_unix2fat_test(struct kunit *test)
        struct fat_timestamp_testcase *testcase =
                (struct fat_timestamp_testcase *)test->param_value;
 
-       fake_sb.options.tz_set = 1;
-       fake_sb.options.time_offset = testcase->time_offset;
+       fat_test_set_time_offset(&fake_sb, testcase->time_offset);
 
        fat_time_unix2fat(&fake_sb, &testcase->ts,
                          &time, &date, &cs);
        KUNIT_EXPECT_EQ_MSG(test,
-                           le16_to_cpu(testcase->time),
-                           le16_to_cpu(time),
+                           testcase->time,
+                           time,
                            "Time mismatch\n");
        KUNIT_EXPECT_EQ_MSG(test,
-                           le16_to_cpu(testcase->date),
-                           le16_to_cpu(date),
+                           testcase->date,
+                           date,
                            "Date mismatch\n");
        KUNIT_EXPECT_EQ_MSG(test,
                            testcase->cs,
@@ -179,10 +272,82 @@ static void fat_time_unix2fat_test(struct kunit *test)
                            "Centisecond mismatch\n");
 }
 
+static void fat_time_unix2fat_clamp_test(struct kunit *test)
+{
+       static struct msdos_sb_info fake_sb;
+       __le16 date, time;
+       u8 cs;
+       struct fat_unix2fat_clamp_testcase *testcase =
+               (struct fat_unix2fat_clamp_testcase *)test->param_value;
+
+       fat_test_set_time_offset(&fake_sb, testcase->time_offset);
+
+       fat_time_unix2fat(&fake_sb, &testcase->ts, &time, &date, &cs);
+       KUNIT_EXPECT_EQ_MSG(test,
+                           testcase->time,
+                           time,
+                           "Clamped time mismatch\n");
+       KUNIT_EXPECT_EQ_MSG(test,
+                           testcase->date,
+                           date,
+                           "Clamped date mismatch\n");
+       KUNIT_EXPECT_EQ_MSG(test,
+                           testcase->cs,
+                           cs,
+                           "Clamped centisecond mismatch\n");
+}
+
+static void fat_time_unix2fat_no_csec_test(struct kunit *test)
+{
+       static struct msdos_sb_info fake_sb;
+       struct timespec64 ts = {
+               .tv_sec = 946684799LL,
+               .tv_nsec = 0L,
+       };
+       __le16 date, time;
+
+       fat_test_set_time_offset(&fake_sb, 0);
+
+       fat_time_unix2fat(&fake_sb, &ts, &time, &date, NULL);
+       KUNIT_EXPECT_EQ_MSG(test,
+                           49021,
+                           le16_to_cpu(time),
+                           "Time mismatch without centiseconds\n");
+       KUNIT_EXPECT_EQ_MSG(test,
+                           10143,
+                           le16_to_cpu(date),
+                           "Date mismatch without centiseconds\n");
+}
+
+static void fat_truncate_atime_test(struct kunit *test)
+{
+       static struct msdos_sb_info fake_sb;
+       struct timespec64 actual;
+       struct fat_truncate_atime_testcase *testcase =
+               (struct fat_truncate_atime_testcase *)test->param_value;
+
+       fat_test_set_time_offset(&fake_sb, testcase->time_offset);
+
+       actual = fat_truncate_atime(&fake_sb, &testcase->ts);
+       KUNIT_EXPECT_EQ_MSG(test,
+                           testcase->expected.tv_sec,
+                           actual.tv_sec,
+                           "Atime truncation seconds mismatch\n");
+       KUNIT_EXPECT_EQ_MSG(test,
+                           testcase->expected.tv_nsec,
+                           actual.tv_nsec,
+                           "Atime truncation nanoseconds mismatch\n");
+}
+
 static struct kunit_case fat_test_cases[] = {
        KUNIT_CASE(fat_checksum_test),
        KUNIT_CASE_PARAM(fat_time_fat2unix_test, fat_time_gen_params),
        KUNIT_CASE_PARAM(fat_time_unix2fat_test, fat_time_gen_params),
+       KUNIT_CASE_PARAM(fat_time_unix2fat_clamp_test,
+                        fat_unix2fat_clamp_gen_params),
+       KUNIT_CASE(fat_time_unix2fat_no_csec_test),
+       KUNIT_CASE_PARAM(fat_truncate_atime_test,
+                        fat_truncate_atime_gen_params),
        {},
 };
 
index b154a516276452d91d11432c7c74b7f30541fbdf..3027ef53af214c6cfc228b4e60723c5628f88c5a 100644 (file)
@@ -297,6 +297,8 @@ struct timespec64 fat_truncate_atime(const struct msdos_sb_info *sbi,
 
        return (struct timespec64){ seconds, 0 };
 }
+/* Export fat_truncate_atime() for the fat_test KUnit tests. */
+EXPORT_SYMBOL_GPL(fat_truncate_atime);
 
 /*
  * Update the in-inode atime and/or mtime after truncating the timestamp to the