[PATCH] fat: add KUnit tests for timestamp conversion helpers

aviv.daum@gmail.com posted 1 patch 3 weeks, 1 day ago
There is a newer version of this series
fs/fat/fat_test.c | 173 ++++++++++++++++++++++++++++++++++++++++++++--
1 file changed, 169 insertions(+), 4 deletions(-)
[PATCH] fat: add KUnit tests for timestamp conversion helpers
Posted by aviv.daum@gmail.com 3 weeks, 1 day ago
From: avivdaum <aviv.daum@gmail.com>

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.

Signed-off-by: avivdaum <aviv.daum@gmail.com>
---
 fs/fat/fat_test.c | 173 ++++++++++++++++++++++++++++++++++++++++++++--
 1 file changed, 169 insertions(+), 4 deletions(-)

diff --git a/fs/fat/fat_test.c b/fs/fat/fat_test.c
index 1f0062659..dba96c150 100644
--- a/fs/fat/fat_test.c
+++ b/fs/fat/fat_test.c
@@ -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,8 +254,7 @@ 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);
@@ -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,
+			    le16_to_cpu(testcase->time),
+			    le16_to_cpu(time),
+			    "Clamped time mismatch\n");
+	KUNIT_EXPECT_EQ_MSG(test,
+			    le16_to_cpu(testcase->date),
+			    le16_to_cpu(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),
 	{},
 };
 
-- 
2.34.1
Re: [PATCH] fat: add KUnit tests for timestamp conversion helpers
Posted by OGAWA Hirofumi 3 weeks, 1 day ago
aviv.daum@gmail.com writes:

> +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,
> +			    le16_to_cpu(testcase->time),
> +			    le16_to_cpu(time),
> +			    "Clamped time mismatch\n");
> +	KUNIT_EXPECT_EQ_MSG(test,
> +			    le16_to_cpu(testcase->date),
> +			    le16_to_cpu(date),
> +			    "Clamped date mismatch\n");

It is unnecessary to convert to native endian?

> +	KUNIT_EXPECT_EQ_MSG(test,
> +			    testcase->cs,
> +			    cs,
> +			    "Clamped centisecond mismatch\n");
> +}

-- 
OGAWA Hirofumi <hirofumi@mail.parknet.co.jp>
[PATCH v2] fat: add KUnit tests for timestamp conversion helpers
Posted by aviv.daum@gmail.com 3 weeks, 1 day ago
From: avivdaum <aviv.daum@gmail.com>

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.

Signed-off-by: avivdaum <aviv.daum@gmail.com>

v2:
- Compare __le16 values directly in unix2fat tests

---
 fs/fat/fat_test.c | 181 ++++++++++++++++++++++++++++++++++++++++++++--
 1 file changed, 173 insertions(+), 8 deletions(-)

diff --git a/fs/fat/fat_test.c b/fs/fat/fat_test.c
index 1f0062659..5c97a7fcc 100644
--- a/fs/fat/fat_test.c
+++ b/fs/fat/fat_test.c
@@ -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),
 	{},
 };
 
-- 
2.34.1
Re: [PATCH v2] fat: add KUnit tests for timestamp conversion helpers
Posted by Christian Brauner 1 week, 6 days ago
On Mon, 16 Mar 2026 00:24:04 +0200, aviv.daum@gmail.com wrote:
> 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.
> 
> [...]

Applied to the vfs-7.1.fat branch of the vfs/vfs.git tree.
Patches in the vfs-7.1.fat branch should appear in linux-next soon.

Please report any outstanding bugs that were missed during review in a
new review to the original patch series allowing us to drop it.

It's encouraged to provide Acked-bys and Reviewed-bys even though the
patch has now been applied. If possible patch trailers will be updated.

Note that commit hashes shown below are subject to change due to rebase,
trailer updates or similar. If in doubt, please check the listed branch.

tree:   https://git.kernel.org/pub/scm/linux/kernel/git/vfs/vfs.git
branch: vfs-7.1.fat

[1/1] fat: add KUnit tests for timestamp conversion helpers
      https://git.kernel.org/vfs/vfs/c/24d061105f04
Re: [PATCH v2] fat: add KUnit tests for timestamp conversion helpers
Posted by OGAWA Hirofumi 3 weeks, 1 day ago
aviv.daum@gmail.com writes:

> From: avivdaum <aviv.daum@gmail.com>
>
> 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.
>
> Signed-off-by: avivdaum <aviv.daum@gmail.com>

Looks good. Thanks.

Acked-by: OGAWA Hirofumi <hirofumi@mail.parknet.co.jp>

> v2:
> - Compare __le16 values directly in unix2fat tests
>
> ---
>  fs/fat/fat_test.c | 181 ++++++++++++++++++++++++++++++++++++++++++++--
>  1 file changed, 173 insertions(+), 8 deletions(-)
>
> diff --git a/fs/fat/fat_test.c b/fs/fat/fat_test.c
> index 1f0062659..5c97a7fcc 100644
> --- a/fs/fat/fat_test.c
> +++ b/fs/fat/fat_test.c
> @@ -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),
>  	{},
>  };

-- 
OGAWA Hirofumi <hirofumi@mail.parknet.co.jp>
Re: [PATCH v2] fat: add KUnit tests for timestamp conversion helpers
Posted by OGAWA Hirofumi 1 week, 6 days ago
Hi, Christian

Ping? IIRC, you requested me to send FAT patches (fs/*) to you.

Thanks.

OGAWA Hirofumi <hirofumi@mail.parknet.co.jp> writes:

> aviv.daum@gmail.com writes:
>
>> From: avivdaum <aviv.daum@gmail.com>
>>
>> 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.
>>
>> Signed-off-by: avivdaum <aviv.daum@gmail.com>
>
> Looks good. Thanks.
>
> Acked-by: OGAWA Hirofumi <hirofumi@mail.parknet.co.jp>
>
>> v2:
>> - Compare __le16 values directly in unix2fat tests
>>
>> ---
>>  fs/fat/fat_test.c | 181 ++++++++++++++++++++++++++++++++++++++++++++--
>>  1 file changed, 173 insertions(+), 8 deletions(-)
>>
>> diff --git a/fs/fat/fat_test.c b/fs/fat/fat_test.c
>> index 1f0062659..5c97a7fcc 100644
>> --- a/fs/fat/fat_test.c
>> +++ b/fs/fat/fat_test.c
>> @@ -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),
>>  	{},
>>  };

-- 
OGAWA Hirofumi <hirofumi@mail.parknet.co.jp>
Re: [PATCH v2] fat: add KUnit tests for timestamp conversion helpers
Posted by Christian Brauner 1 week, 6 days ago
On Tue, Mar 24, 2026 at 04:19:50PM +0900, OGAWA Hirofumi wrote:
> Hi, Christian
> 
> Ping? IIRC, you requested me to send FAT patches (fs/*) to you.

Thanks, sorry I missed this. Can you please update the MAINTAINERS entry to:

diff --git a/MAINTAINERS b/MAINTAINERS
index 61bf550fd37c..cb81b80a0f88 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -27671,6 +27671,7 @@ F:      drivers/mtd/nand/raw/vf610_nfc.c

 VFAT/FAT/MSDOS FILESYSTEM
 M:     OGAWA Hirofumi <hirofumi@mail.parknet.co.jp>
+L:     linux-fsdevel@vger.kernel.org
 S:     Maintained
 F:     Documentation/filesystems/vfat.rst
 F:     fs/fat/

then this gets auto-handled on-list. :)

Thanks!
Re: [PATCH v2] fat: add KUnit tests for timestamp conversion helpers
Posted by OGAWA Hirofumi 1 week, 6 days ago
Christian Brauner <brauner@kernel.org> writes:

> On Tue, Mar 24, 2026 at 04:19:50PM +0900, OGAWA Hirofumi wrote:
>> Hi, Christian
>> 
>> Ping? IIRC, you requested me to send FAT patches (fs/*) to you.
>
> Thanks, sorry I missed this. Can you please update the MAINTAINERS entry to:
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 61bf550fd37c..cb81b80a0f88 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -27671,6 +27671,7 @@ F:      drivers/mtd/nand/raw/vf610_nfc.c
>
>  VFAT/FAT/MSDOS FILESYSTEM
>  M:     OGAWA Hirofumi <hirofumi@mail.parknet.co.jp>
> +L:     linux-fsdevel@vger.kernel.org
>  S:     Maintained
>  F:     Documentation/filesystems/vfat.rst
>  F:     fs/fat/
>
> then this gets auto-handled on-list. :)

I should send this patch from me? Or could you apply the your patch
yourself?

Then I'm, of course, Acked-by: OGAWA Hirofumi <hirofumi@mail.parknet.co.jp>

Thanks.
-- 
OGAWA Hirofumi <hirofumi@mail.parknet.co.jp>