From nobody Tue Apr 7 06:23:04 2026 Received: from mail-wm1-f53.google.com (mail-wm1-f53.google.com [209.85.128.53]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 813E035AC1F for ; Sun, 15 Mar 2026 14:43:34 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.128.53 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1773585816; cv=none; b=lvPCtWDuedDcakS3aoA54jUhEJGfdi7upaGR2pCS1i9J8bY9WkH/P+TKh5SXsFw6mvA7LU4N9DzQKJ656X+duZH9CLifrtlg4f4X0RrQaF0W0RwufOuVg5ApPZFVU5RiBDXjEKuGo7JC8Z6TxnF5N5pIFeM2p24xAXOtWrgXqZE= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1773585816; c=relaxed/simple; bh=qsCPNAmC7FHzoFV6y5B5ImLA6gYLPRz5y/ty/PgeNeQ=; h=From:To:Cc:Subject:Date:Message-Id:MIME-Version; b=DXyyzXzL/rAbSUMsnKzVg/ukX3YBUP/RfkWJ1AELxKWbFmH54jXAqkOhT0iz/Dh3Q2M9WRRD96bmmH8FAKhZNXQnTn1eysGrntXvjVVxjme5wU1JQfJpdVJfeSMKyYzZyzSqp2iXwvhJcOW/GyPd6IOAiJKL6B1EguD+HQMDLCg= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=dhyxBfsi; arc=none smtp.client-ip=209.85.128.53 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="dhyxBfsi" Received: by mail-wm1-f53.google.com with SMTP id 5b1f17b1804b1-4855dbfc129so14309695e9.0 for ; Sun, 15 Mar 2026 07:43:34 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1773585813; x=1774190613; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:message-id:date:subject:cc :to:from:from:to:cc:subject:date:message-id:reply-to; bh=OEiEU5yP9MnM1lUyyDEdXpudCpD/NnfCd86plKVy0wE=; b=dhyxBfsipJYmkacjY1EAzkXUoj6AXRjiqdsP10aKMAWKKuQYax7Zn9AvHVKRga5OaS 9rGeSl2zzhg9aApv8Uw7KTIS7lBOjxWaeCGrkre78+npD3D3Hp38gc6J5GlEH5ACZNeR d8yXAm+U3tOrc+wS50BnJ8cmKhFgASC+hW4mWqyh6AlUy8nd3ZKOrZkEuY54QJLBQ55D zwPN8S204NeWciH+TjTAeyMKWKPYpwp8tp4wl+JqCfn2gfNf0K0ARp31NQe99YRWy+V0 wPYw+3y5tjesKldOPri9rih6adsEKnIuc0WB4XC9t4e2P2TGxcq+cILflTiT4kIDFutu Fxug== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1773585813; x=1774190613; h=content-transfer-encoding:mime-version:message-id:date:subject:cc :to:from:x-gm-gg:x-gm-message-state:from:to:cc:subject:date :message-id:reply-to; bh=OEiEU5yP9MnM1lUyyDEdXpudCpD/NnfCd86plKVy0wE=; b=Ai+EZ5M8ljHMjz/JpJmNwvUqP/uvg9pafpbROeMyqGKhB0IYg/H1q8GwWXnWx9pWa/ h2i+GR8N0Ca9bKhdCMgBk/EBJWNN1Qn/ysjIe5gmnUywxufHM70y8I6ZOpjxwWKuPrkA 8EJ3znmlY6A+a7cfXxVUO2fzBrVjMeiZAeHm9AfGzKi1HNL5Lq8acm3Mm8ckt2QmCsrZ AzuWMDwsKW12H57pgXSr5speC8XogC/aFOxrooEN3ePW21kCySvKZJFJw/NrWTgN2aRL IA5W9FNZM+ojGpMslXjmln9gBGGGR2iK3UIfAFWrHQ4m6B/FARzLZlbofMn+ZkVSvOso avig== X-Gm-Message-State: AOJu0YwPyDyJddACIEF/dhKySF1eugeUUbbp2OhWstddrpGTpxo3x9ZK Rs2GkH6Rop99XxxwO/zvZAM0yDdFn5Bp34F2UTyT9GDpgO2OBwi0F7cn X-Gm-Gg: ATEYQzyk5odJ3AJt0NHqXkWR9Bm8AApnOT23MdXB0fBRL5mOuow4fWvCi4oroJRjPzH x7EJfKwm4KFr3t2r1sTv74vLmsZXG+QRTFPkkXIIeF5izdL3kftdjpZK1k/aOzotEr9tS3jgxzR EGohF4zPROKUhirTqSkBqD2AqVtb/zOxgaS3nNb/wvo8OOVLnq6mEeF9DOmxIxw8O8PIqEk0T3S 8BwxDpWqozDSbJdIHJZ4jamBRqntzSbXc6yGz05/ZRet4W2QPrfz4GZFdGbVjGnsLykduBKaxuv c4nBbcPPW75VBls8x6CGKE7GQrrcOK0H79uWHqWpLzTBVBy4e/QpBidl++S4UWWcc3zvFliCWkm 3RFyS7Q6/zetumSpXjyTGNCKRDsgXK3w+TyF/1grsGdOO5Nn6hQ/kdGb0cfHHg0wWZHyHMXD+4I gBGAZjghl7WHyPDZMKabLNHrYZ8BFy4nGtEC4= X-Received: by 2002:a05:600c:1d2a:b0:483:a27e:6706 with SMTP id 5b1f17b1804b1-485566d4e69mr192525865e9.9.1773585812568; Sun, 15 Mar 2026 07:43:32 -0700 (PDT) Received: from localhost.localdomain ([147.235.207.31]) by smtp.gmail.com with ESMTPSA id 5b1f17b1804b1-48569672c60sm9067165e9.0.2026.03.15.07.43.31 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sun, 15 Mar 2026 07:43:31 -0700 (PDT) From: aviv.daum@gmail.com To: OGAWA Hirofumi Cc: linux-kernel@vger.kernel.org, avivdaum Subject: [PATCH] fat: add KUnit tests for timestamp conversion helpers Date: Sun, 15 Mar 2026 16:42:12 +0200 Message-Id: <20260315144212.24325-1-aviv.daum@gmail.com> X-Mailer: git-send-email 2.34.1 Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" From: avivdaum 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 --- 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; }; =20 +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[] =3D { { .name =3D "Earliest possible UTC (1980-01-01 00:00:00)", @@ -120,13 +136,92 @@ static struct fat_timestamp_testcase time_test_cases[= ] =3D { }, }; =20 +static struct fat_unix2fat_clamp_testcase unix2fat_clamp_test_cases[] =3D { + { + .name =3D "Clamp to earliest FAT date for 1979-12-31 23:59:59 UTC", + .ts =3D {.tv_sec =3D 315532799LL, .tv_nsec =3D 0L}, + .time =3D cpu_to_le16(0), + .date =3D cpu_to_le16(33), + .cs =3D 0, + .time_offset =3D 0, + }, + { + .name =3D "Clamp after time_offset=3D-60 pushes 1980-01-01 00:30 UTC bel= ow 1980", + .ts =3D {.tv_sec =3D 315534600LL, .tv_nsec =3D 0L}, + .time =3D cpu_to_le16(0), + .date =3D cpu_to_le16(33), + .cs =3D 0, + .time_offset =3D -60, + }, + { + .name =3D "Clamp to latest FAT date for 2108-01-01 00:00:00 UTC", + .ts =3D {.tv_sec =3D 4354819200LL, .tv_nsec =3D 0L}, + .time =3D cpu_to_le16(49021), + .date =3D cpu_to_le16(65439), + .cs =3D 199, + .time_offset =3D 0, + }, + { + .name =3D "Clamp after time_offset=3D60 pushes 2107-12-31 23:30 UTC beyo= nd 2107", + .ts =3D {.tv_sec =3D 4354817400LL, .tv_nsec =3D 0L}, + .time =3D cpu_to_le16(49021), + .date =3D cpu_to_le16(65439), + .cs =3D 199, + .time_offset =3D 60, + }, +}; + +static struct fat_truncate_atime_testcase truncate_atime_test_cases[] =3D { + { + .name =3D "UTC atime truncates to 2004-02-29 00:00:00", + .ts =3D {.tv_sec =3D 1078058096LL, .tv_nsec =3D 789000000L}, + .expected =3D {.tv_sec =3D 1078012800LL, .tv_nsec =3D 0L}, + .time_offset =3D 0, + }, + { + .name =3D "time_offset=3D-60 truncates 2004-02-29 00:30 UTC to previous = local midnight", + .ts =3D {.tv_sec =3D 1078014645LL, .tv_nsec =3D 123000000L}, + .expected =3D {.tv_sec =3D 1077930000LL, .tv_nsec =3D 0L}, + .time_offset =3D -60, + }, + { + .name =3D "time_offset=3D60 truncates 2004-02-29 23:30 UTC to next local= midnight", + .ts =3D {.tv_sec =3D 1078097445LL, .tv_nsec =3D 123000000L}, + .expected =3D {.tv_sec =3D 1078095600LL, .tv_nsec =3D 0L}, + .time_offset =3D 60, + }, +}; + static void time_testcase_desc(struct fat_timestamp_testcase *t, char *desc) { strscpy(desc, t->name, KUNIT_PARAM_DESC_SIZE); } =20 +static void unix2fat_clamp_testcase_desc(struct fat_unix2fat_clamp_testcas= e *t, + char *desc) +{ + strscpy(desc, t->name, KUNIT_PARAM_DESC_SIZE); +} + +static void truncate_atime_testcase_desc(struct fat_truncate_atime_testcas= e *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_o= ffset) +{ + *sbi =3D (struct msdos_sb_info){}; + sbi->options.tz_set =3D 1; + sbi->options.time_offset =3D time_offset; +} =20 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 =3D (struct fat_timestamp_testcase *)test->param_value; =20 - fake_sb.options.tz_set =3D 1; - fake_sb.options.time_offset =3D testcase->time_offset; + fat_test_set_time_offset(&fake_sb, testcase->time_offset); =20 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 =3D (struct fat_timestamp_testcase *)test->param_value; =20 - fake_sb.options.tz_set =3D 1; - fake_sb.options.time_offset =3D testcase->time_offset; + fat_test_set_time_offset(&fake_sb, testcase->time_offset); =20 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"); } =20 +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 =3D + (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 =3D { + .tv_sec =3D 946684799LL, + .tv_nsec =3D 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 =3D + (struct fat_truncate_atime_testcase *)test->param_value; + + fat_test_set_time_offset(&fake_sb, testcase->time_offset); + + actual =3D 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[] =3D { 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), {}, }; =20 --=20 2.34.1