From nobody Mon Jun 15 19:27:02 2026 Received: from mail-pj1-f48.google.com (mail-pj1-f48.google.com [209.85.216.48]) (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 CDAE935A398 for ; Mon, 13 Apr 2026 06:51:36 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.216.48 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1776063098; cv=none; b=DzsEvmRC2HMdoNbnhgnUR3t+dZ+bB2vT1rbWv1dKPmeY6FXWNFFUopLCFohly8rc7UQQfWB1yrtHQozCeQBl8ynRdjVOgrzbvwFahDm67zymKaRb3iLzDSzvzW4502johwPdPAW5X3/N9zg6200PDrlQ4fFS2DKI3DiFHS0AN9g= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1776063098; c=relaxed/simple; bh=HP+Ej0HV+2ncsP88mh6+XiDWyyHr/4TFTjdUK5p+TrM=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version; b=qRuAyl/tTzyZG8qmdVrwlOvut1zolud1TVqD/QUTWQMRpiwR5LlxDGh18UjtH05s/DzQBkzQj9BwYQqTZDddEEeJaRsXBbMMPa9fIdS+c7lLwx/B+Ste0PKVRvXbY2nKyM/Qjs/gs7Jf6QfQPRPrC65uzx2xgWPwNw2VoZW1zMc= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=gms.tku.edu.tw; spf=pass smtp.mailfrom=gms.tku.edu.tw; dkim=pass (2048-bit key) header.d=gms-tku-edu-tw.20251104.gappssmtp.com header.i=@gms-tku-edu-tw.20251104.gappssmtp.com header.b=OehaQSmK; arc=none smtp.client-ip=209.85.216.48 Authentication-Results: smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=gms.tku.edu.tw Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gms.tku.edu.tw Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gms-tku-edu-tw.20251104.gappssmtp.com header.i=@gms-tku-edu-tw.20251104.gappssmtp.com header.b="OehaQSmK" Received: by mail-pj1-f48.google.com with SMTP id 98e67ed59e1d1-35d9f68d011so2547033a91.2 for ; Sun, 12 Apr 2026 23:51:36 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gms-tku-edu-tw.20251104.gappssmtp.com; s=20251104; t=1776063096; x=1776667896; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=IaJZBskiv7Rb1V4NrQfCtdYsCLSrKzxcTk4n0XYetus=; b=OehaQSmK7u1tw8ghncdcqjLfXE0IZXelENcclRAGjufTKln+qZ0xM4y/7uJRgmDgkp umrEhRRNnlKE7n4j4/cD2d2jD4f0Smc7TQKJpyY3bgMP33NlDUfZBrHNquyGw9MB2pRk VITbniSiAZCPb9wgWjTmYvbhIBda1g+Z88+tAGz7rb9/JpPt5PBe0j1zNYuVRU7TJCJO ntx7Kilz5ph2q74kJVAB4gSpvhuz9aUcO2eZnd+TQqcajVikeMkgTpk+6YzOLYZcI1Td 0dveEbbDV5Vvgp/5+yPGs12Nx+Ey57agwQSAodf4QW+uHIPt78QTrmdr3P8QQ+WXuJIO ZbSw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1776063096; x=1776667896; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to; bh=IaJZBskiv7Rb1V4NrQfCtdYsCLSrKzxcTk4n0XYetus=; b=JQ4p7FT1WuFkWPdEMvGNmZmcWMApq1b3wj/z53u5XuAtyjBKTtLoa0tI9SL5Jb4J+i kJmCue4BHih1Bua4nmzU+/8qvcvUas3vbvE/lSilYC2L7uqUZfPvJnVe4tLlgL6AaSF6 zY3PUBrW/y9bVCI09KNcXJUrmaKlKfWpFB8FXroctuR2iFEQfksVqIfGk0T1CaKn0Otx j/Ff33qT7sikh31p1DfGBwfIsqVfleQ6D/+8zagMtCnGuBWQOENdqj9apq8LlSDS1UI8 iZ6pBnPCwElR/9rZ4ZpjtxK314U8oBwsnoiC6p7nXqB46KsqfTjcWexnXLnUq4g4BLY7 5ZEQ== X-Forwarded-Encrypted: i=1; AFNElJ8EkFrUiymmGhiv51+JCp3g71WCAFjVGOqpMlFZhzma4+OswV6lZ1PC1mNnM0rkIIkawd4D05c/dLl4Dk8=@vger.kernel.org X-Gm-Message-State: AOJu0Yy1Vykkk6RPbxV1pvBxvPOazE2PPJFbylIX8mWWsxCVOwzNgY2B 8fczeoZMVStEDRO1xfeq2jUz/I+bLp1twYInOdPGKask2jQLDCtcU8EiJ2QzPsquPy4= X-Gm-Gg: AeBDiettl2bJ31873PRfOtRVQRcLzeWFzBtDnNIUMvYcnIn4oMBUpg18cOk2/TK1qsm 3fDJ1xatHYvjcjr7jjwftC2ScXfjrDt2t90s3t5uY5SvuK61Wc7kdEIZEs78tuR4YiiQzi0hWwv udHJQszpM+CGXUmeNGW0toHP2bUFlUpnyEz0bmvL2CTGbhUkQgoTS9HcS1FpgWBejCeEStCRHHO YQ6NWT7ZaRXpXzl/sn0MZHk3TY/cxZ2kTCeK4pYAFhLbYzyxS/dQshwZaJ08DZPCTPvdP/IiPXA gv6TxhfRdRLFMJx0d1zL1LWkaOPCAyQISRCpREFqGSxb3zRMsv076MtCmnEWrUkPGUOvKsxZFjm OTeankbBQd7RRvWz7lSynZw58ao+ebWpG5eRtNlKRg1wskJDbIYv8rxDCO7cAczWvUTuboER/aI xtYwsqhrY8nNVVmYjymLaGglaNNG2SL1n2FwYbNdSkJkhPdsk= X-Received: by 2002:a17:903:40cf:b0:2b0:673a:7c90 with SMTP id d9443c01a7336-2b2d5a1c860mr124346885ad.28.1776063096180; Sun, 12 Apr 2026 23:51:36 -0700 (PDT) Received: from wu-Pro-E500-G6-WS720T.. ([2001:288:7001:2703:d062:a770:8a34:87a2]) by smtp.gmail.com with ESMTPSA id d9443c01a7336-2b2d4dde346sm100107855ad.29.2026.04.12.23.51.33 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sun, 12 Apr 2026 23:51:35 -0700 (PDT) From: Guan-Chun Wu <409411716@gms.tku.edu.tw> To: Theodore Ts'o , Andreas Dilger , Baokun Li , Jan Kara , Ojaswin Mujoo , Ritesh Harjani , Zhang Yi Cc: linux-ext4@vger.kernel.org, linux-kernel@vger.kernel.org, edward062254@gmail.com, visitorckw@gmail.com, david.laight.linux@gmail.com, Guan-Chun Wu <409411716@gms.tku.edu.tw> Subject: [PATCH v3 1/2] ext4: add Kunit coverage for directory hash computation Date: Mon, 13 Apr 2026 14:51:13 +0800 Message-Id: <20260413065114.730231-2-409411716@gms.tku.edu.tw> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20260413065114.730231-1-409411716@gms.tku.edu.tw> References: <20260413065114.730231-1-409411716@gms.tku.edu.tw> 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" Introduce Kunit tests for fs/ext4/hash.c to verify ext4fs_dirhash() across the legacy, half-MD4, and TEA hash variants. The tests cover empty, seeded hashing, and non-ASCII name handling. They also verify error paths, including invalid hash versions and SipHash without a configured key, and check that the signed and unsigned hash variants differ on non-ASCII input as expected. When CONFIG_UNICODE is enabled, the tests further verify casefolded-name hashing and the fallback behavior for invalid input. Co-developed-by: Chen Hao Yu Signed-off-by: Chen Hao Yu Signed-off-by: Guan-Chun Wu <409411716@gms.tku.edu.tw> --- fs/ext4/Makefile | 2 +- fs/ext4/hash-test.c | 546 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 547 insertions(+), 1 deletion(-) create mode 100644 fs/ext4/hash-test.c diff --git a/fs/ext4/Makefile b/fs/ext4/Makefile index 3baee4e7c..3f9fc0eb8 100644 --- a/fs/ext4/Makefile +++ b/fs/ext4/Makefile @@ -15,7 +15,7 @@ ext4-y :=3D balloc.o bitmap.o block_validity.o dir.o ext4= _jbd2.o extents.o \ ext4-$(CONFIG_EXT4_FS_POSIX_ACL) +=3D acl.o ext4-$(CONFIG_EXT4_FS_SECURITY) +=3D xattr_security.o ext4-test-objs +=3D inode-test.o mballoc-test.o \ - extents-test.o + extents-test.o hash-test.o obj-$(CONFIG_EXT4_KUNIT_TESTS) +=3D ext4-test.o ext4-$(CONFIG_FS_VERITY) +=3D verity.o ext4-$(CONFIG_FS_ENCRYPTION) +=3D crypto.o diff --git a/fs/ext4/hash-test.c b/fs/ext4/hash-test.c new file mode 100644 index 000000000..a151b5684 --- /dev/null +++ b/fs/ext4/hash-test.c @@ -0,0 +1,546 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * KUnit tests for ext4 directory hash computation. + */ + +#include +#include +#include +#include +#include "ext4.h" + +static void ext4_hash_init_fake_dir(struct inode *dir, struct super_block = *sb) +{ + memset(sb, 0, sizeof(*sb)); + memset(dir, 0, sizeof(*dir)); + dir->i_sb =3D sb; + strscpy(sb->s_id, "kunit-ext4", sizeof(sb->s_id)); +} + +static void ext4_hash_init_fake_dir_with_sbi(struct inode *dir, + struct super_block *sb, + struct ext4_sb_info *sbi) +{ + ext4_hash_init_fake_dir(dir, sb); + memset(sbi, 0, sizeof(*sbi)); + sb->s_fs_info =3D sbi; + sbi->s_sb =3D sb; +} + +#if IS_ENABLED(CONFIG_UNICODE) +static void ext4_hash_init_fake_ext4_dir(struct ext4_inode_info *ei, + struct super_block *sb, + struct ext4_sb_info *sbi) +{ + memset(sb, 0, sizeof(*sb)); + memset(ei, 0, sizeof(*ei)); + memset(sbi, 0, sizeof(*sbi)); + + inode_init_once(&ei->vfs_inode); + ei->vfs_inode.i_sb =3D sb; + ei->vfs_inode.i_mode =3D S_IFDIR; + + strscpy(sb->s_id, "kunit-ext4", sizeof(sb->s_id)); + sb->s_fs_info =3D sbi; + sbi->s_sb =3D sb; +} +#endif + +struct ext4_dirhash_test_case { + const char *name; + u32 hash_version; + const char *input; + int len; + u32 seed[4]; + bool use_seed; + u32 expected_hash; + u32 expected_minor_hash; +}; + +static const struct ext4_dirhash_test_case ext4_dirhash_test_cases[] =3D { + { + .name =3D "legacy_abc", + .hash_version =3D DX_HASH_LEGACY, + .input =3D "abc", + .len =3D 3, + .use_seed =3D false, + .expected_hash =3D 0x75afd992, + .expected_minor_hash =3D 0x00000000, + }, + { + .name =3D "legacy_unsigned_abc", + .hash_version =3D DX_HASH_LEGACY_UNSIGNED, + .input =3D "abc", + .len =3D 3, + .use_seed =3D false, + .expected_hash =3D 0x75afd992, + .expected_minor_hash =3D 0x00000000, + }, + { + .name =3D "half_md4_abc", + .hash_version =3D DX_HASH_HALF_MD4, + .input =3D "abc", + .len =3D 3, + .use_seed =3D false, + .expected_hash =3D 0xd196a868, + .expected_minor_hash =3D 0xc420eb28, + }, + { + .name =3D "half_md4_unsigned_abc", + .hash_version =3D DX_HASH_HALF_MD4_UNSIGNED, + .input =3D "abc", + .len =3D 3, + .use_seed =3D false, + .expected_hash =3D 0xd196a868, + .expected_minor_hash =3D 0xc420eb28, + }, + { + .name =3D "tea_abc", + .hash_version =3D DX_HASH_TEA, + .input =3D "abc", + .len =3D 3, + .use_seed =3D false, + .expected_hash =3D 0xb1435ec4, + .expected_minor_hash =3D 0x3f7eaa0e, + }, + { + .name =3D "tea_unsigned_abc", + .hash_version =3D DX_HASH_TEA_UNSIGNED, + .input =3D "abc", + .len =3D 3, + .use_seed =3D false, + .expected_hash =3D 0xb1435ec4, + .expected_minor_hash =3D 0x3f7eaa0e, + }, + { + .name =3D "empty_half_md4", + .hash_version =3D DX_HASH_HALF_MD4, + .input =3D "", + .len =3D 0, + .use_seed =3D false, + .expected_hash =3D 0xefcdab88, + .expected_minor_hash =3D 0x98badcfe, + }, + { + .name =3D "half_md4_31bytes", + .hash_version =3D DX_HASH_HALF_MD4, + .input =3D "1234567890123456789012345678901", + .len =3D 31, + .use_seed =3D false, + .expected_hash =3D 0xc4db1f78, + .expected_minor_hash =3D 0xea23921b, + }, + { + .name =3D "half_md4_32bytes", + .hash_version =3D DX_HASH_HALF_MD4, + .input =3D "12345678901234567890123456789012", + .len =3D 32, + .use_seed =3D false, + .expected_hash =3D 0xfa6cc63e, + .expected_minor_hash =3D 0x2f77bd1c, + }, + { + .name =3D "half_md4_33bytes", + .hash_version =3D DX_HASH_HALF_MD4, + .input =3D "123456789012345678901234567890123", + .len =3D 33, + .use_seed =3D false, + .expected_hash =3D 0xdc0c2dec, + .expected_minor_hash =3D 0x5ca23365, + }, + { + .name =3D "half_md4_unsigned_31bytes", + .hash_version =3D DX_HASH_HALF_MD4_UNSIGNED, + .input =3D "1234567890123456789012345678901", + .len =3D 31, + .use_seed =3D false, + .expected_hash =3D 0xc4db1f78, + .expected_minor_hash =3D 0xea23921b, + }, + { + .name =3D "half_md4_unsigned_32bytes", + .hash_version =3D DX_HASH_HALF_MD4_UNSIGNED, + .input =3D "12345678901234567890123456789012", + .len =3D 32, + .use_seed =3D false, + .expected_hash =3D 0xfa6cc63e, + .expected_minor_hash =3D 0x2f77bd1c, + }, + { + .name =3D "half_md4_unsigned_33bytes", + .hash_version =3D DX_HASH_HALF_MD4_UNSIGNED, + .input =3D "123456789012345678901234567890123", + .len =3D 33, + .use_seed =3D false, + .expected_hash =3D 0xdc0c2dec, + .expected_minor_hash =3D 0x5ca23365, + }, + { + .name =3D "tea_15bytes", + .hash_version =3D DX_HASH_TEA, + .input =3D "123456789abcdef", + .len =3D 15, + .use_seed =3D false, + .expected_hash =3D 0xa562903a, + .expected_minor_hash =3D 0x6174a00f, + }, + { + .name =3D "tea_16bytes", + .hash_version =3D DX_HASH_TEA, + .input =3D "1234567890abcdef", + .len =3D 16, + .use_seed =3D false, + .expected_hash =3D 0x8449f258, + .expected_minor_hash =3D 0x49a16d46, + }, + { + .name =3D "tea_17bytes", + .hash_version =3D DX_HASH_TEA, + .input =3D "123456789abcdefgh", + .len =3D 17, + .use_seed =3D false, + .expected_hash =3D 0xf32ec10c, + .expected_minor_hash =3D 0x58ceae61, + }, + { + .name =3D "half_md4_seeded", + .hash_version =3D DX_HASH_HALF_MD4, + .input =3D "same-name", + .len =3D 9, + .seed =3D { 0x11111111, 0x22222222, 0x33333333, 0x44444444 }, + .use_seed =3D true, + .expected_hash =3D 0x8aebf604, + .expected_minor_hash =3D 0x66ce48fe, + }, + { + .name =3D "half_md4_non_ascii_signed", + .hash_version =3D DX_HASH_HALF_MD4, + .input =3D "\x80\x81\x82\x83\x84", + .len =3D 5, + .use_seed =3D false, + .expected_hash =3D 0x8bab0498, + .expected_minor_hash =3D 0xc326632d, + }, + { + .name =3D "half_md4_non_ascii_unsigned", + .hash_version =3D DX_HASH_HALF_MD4_UNSIGNED, + .input =3D "\x80\x81\x82\x83\x84", + .len =3D 5, + .use_seed =3D false, + .expected_hash =3D 0xbc48596e, + .expected_minor_hash =3D 0xde0fad41, + }, + { + .name =3D "tea_non_ascii_signed", + .hash_version =3D DX_HASH_TEA, + .input =3D "\x80\x81\x82\x83\x84", + .len =3D 5, + .use_seed =3D false, + .expected_hash =3D 0x21e3a154, + .expected_minor_hash =3D 0x90112c3d, + }, + { + .name =3D "tea_non_ascii_unsigned", + .hash_version =3D DX_HASH_TEA_UNSIGNED, + .input =3D "\x80\x81\x82\x83\x84", + .len =3D 5, + .use_seed =3D false, + .expected_hash =3D 0x9b648616, + .expected_minor_hash =3D 0x011dd507, + }, +}; + +static void test_ext4fs_dirhash_vectors(struct kunit *test) +{ + struct super_block *sb; + struct inode *dir; + int i; + + sb =3D kunit_kzalloc(test, sizeof(*sb), GFP_KERNEL); + dir =3D kunit_kzalloc(test, sizeof(*dir), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, sb); + KUNIT_ASSERT_NOT_NULL(test, dir); + + ext4_hash_init_fake_dir(dir, sb); + + for (i =3D 0; i < ARRAY_SIZE(ext4_dirhash_test_cases); i++) { + const struct ext4_dirhash_test_case *tc =3D + &ext4_dirhash_test_cases[i]; + struct dx_hash_info hinfo; + int ret; + + memset(&hinfo, 0, sizeof(hinfo)); + hinfo.hash_version =3D tc->hash_version; + hinfo.seed =3D tc->use_seed ? (u32 *)tc->seed : NULL; + + ret =3D ext4fs_dirhash(dir, tc->input, tc->len, &hinfo); + + KUNIT_ASSERT_EQ_MSG(test, ret, 0, "case=3D%s", tc->name); + KUNIT_EXPECT_EQ_MSG(test, hinfo.hash, tc->expected_hash, + "case=3D%s", tc->name); + KUNIT_EXPECT_EQ_MSG(test, hinfo.minor_hash, + tc->expected_minor_hash, + "case=3D%s", tc->name); + } +} + +static void test_ext4fs_dirhash_seed_changes_result(struct kunit *test) +{ + struct super_block *sb; + struct inode *dir; + u32 seed[4] =3D { 0x11111111, 0x22222222, 0x33333333, 0x44444444 }; + struct dx_hash_info plain =3D { + .hash_version =3D DX_HASH_HALF_MD4, + }; + struct dx_hash_info seeded =3D { + .hash_version =3D DX_HASH_HALF_MD4, + .seed =3D seed, + }; + int ret_plain, ret_seeded; + + sb =3D kunit_kzalloc(test, sizeof(*sb), GFP_KERNEL); + dir =3D kunit_kzalloc(test, sizeof(*dir), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, sb); + KUNIT_ASSERT_NOT_NULL(test, dir); + + ext4_hash_init_fake_dir(dir, sb); + + ret_plain =3D ext4fs_dirhash(dir, "same-name", 9, &plain); + ret_seeded =3D ext4fs_dirhash(dir, "same-name", 9, &seeded); + + KUNIT_ASSERT_EQ(test, ret_plain, 0); + KUNIT_ASSERT_EQ(test, ret_seeded, 0); + + KUNIT_EXPECT_TRUE(test, + plain.hash !=3D seeded.hash || + plain.minor_hash !=3D seeded.minor_hash); +} + +static void test_ext4fs_dirhash_invalid_version_returns_einval(struct kuni= t *test) +{ + struct super_block *sb; + struct inode *dir; + struct ext4_sb_info *sbi; + struct dx_hash_info hinfo =3D { + .hash =3D 0xdeadbeef, + .minor_hash =3D 0xcafebabe, + .hash_version =3D DX_HASH_LAST + 1, + }; + int ret; + + sb =3D kunit_kzalloc(test, sizeof(*sb), GFP_KERNEL); + dir =3D kunit_kzalloc(test, sizeof(*dir), GFP_KERNEL); + sbi =3D kunit_kzalloc(test, sizeof(*sbi), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, sb); + KUNIT_ASSERT_NOT_NULL(test, dir); + KUNIT_ASSERT_NOT_NULL(test, sbi); + + ext4_hash_init_fake_dir_with_sbi(dir, sb, sbi); + + ret =3D ext4fs_dirhash(dir, "abc", 3, &hinfo); + + KUNIT_EXPECT_EQ(test, ret, -EINVAL); + KUNIT_EXPECT_EQ(test, hinfo.hash, 0); + KUNIT_EXPECT_EQ(test, hinfo.minor_hash, 0); +} + +static void test_ext4fs_dirhash_siphash_without_key_returns_einval(struct = kunit *test) +{ + struct super_block *sb; + struct inode *dir; + struct ext4_sb_info *sbi; + struct dx_hash_info hinfo =3D { + .hash_version =3D DX_HASH_SIPHASH, + }; + int ret; + + sb =3D kunit_kzalloc(test, sizeof(*sb), GFP_KERNEL); + dir =3D kunit_kzalloc(test, sizeof(*dir), GFP_KERNEL); + sbi =3D kunit_kzalloc(test, sizeof(*sbi), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, sb); + KUNIT_ASSERT_NOT_NULL(test, dir); + KUNIT_ASSERT_NOT_NULL(test, sbi); + + ext4_hash_init_fake_dir_with_sbi(dir, sb, sbi); + + ret =3D ext4fs_dirhash(dir, "abc", 3, &hinfo); + + KUNIT_EXPECT_EQ(test, ret, -EINVAL); +} + +static void test_ext4fs_dirhash_signed_unsigned_differ_on_nonascii(struct = kunit *test) +{ + struct super_block *sb; + struct inode *dir; + static const char input[] =3D "\x80\xff\x81\xfe\101bc"; + struct dx_hash_info legacy_signed =3D { + .hash_version =3D DX_HASH_LEGACY, + }; + struct dx_hash_info legacy_unsigned =3D { + .hash_version =3D DX_HASH_LEGACY_UNSIGNED, + }; + struct dx_hash_info md4_signed =3D { + .hash_version =3D DX_HASH_HALF_MD4, + }; + struct dx_hash_info md4_unsigned =3D { + .hash_version =3D DX_HASH_HALF_MD4_UNSIGNED, + }; + struct dx_hash_info tea_signed =3D { + .hash_version =3D DX_HASH_TEA, + }; + struct dx_hash_info tea_unsigned =3D { + .hash_version =3D DX_HASH_TEA_UNSIGNED, + }; + int ret; + + sb =3D kunit_kzalloc(test, sizeof(*sb), GFP_KERNEL); + dir =3D kunit_kzalloc(test, sizeof(*dir), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, sb); + KUNIT_ASSERT_NOT_NULL(test, dir); + + ext4_hash_init_fake_dir(dir, sb); + + ret =3D ext4fs_dirhash(dir, input, sizeof(input) - 1, &legacy_signed); + KUNIT_ASSERT_EQ(test, ret, 0); + ret =3D ext4fs_dirhash(dir, input, sizeof(input) - 1, &legacy_unsigned); + KUNIT_ASSERT_EQ(test, ret, 0); + KUNIT_EXPECT_NE(test, legacy_signed.hash, legacy_unsigned.hash); + + ret =3D ext4fs_dirhash(dir, input, sizeof(input) - 1, &md4_signed); + KUNIT_ASSERT_EQ(test, ret, 0); + ret =3D ext4fs_dirhash(dir, input, sizeof(input) - 1, &md4_unsigned); + KUNIT_ASSERT_EQ(test, ret, 0); + KUNIT_EXPECT_TRUE(test, + md4_signed.hash !=3D md4_unsigned.hash || + md4_signed.minor_hash !=3D md4_unsigned.minor_hash); + + ret =3D ext4fs_dirhash(dir, input, sizeof(input) - 1, &tea_signed); + KUNIT_ASSERT_EQ(test, ret, 0); + ret =3D ext4fs_dirhash(dir, input, sizeof(input) - 1, &tea_unsigned); + KUNIT_ASSERT_EQ(test, ret, 0); + KUNIT_EXPECT_TRUE(test, + tea_signed.hash !=3D tea_unsigned.hash || + tea_signed.minor_hash !=3D tea_unsigned.minor_hash); +} + +#if IS_ENABLED(CONFIG_UNICODE) +static void test_ext4fs_dirhash_casefolded_names_hash_consistently(struct = kunit *test) +{ + struct super_block *sb; + struct ext4_inode_info *ei; + struct ext4_sb_info *sbi; + struct unicode_map *um; + struct dx_hash_info h1 =3D { + .hash_version =3D DX_HASH_HALF_MD4, + }; + struct dx_hash_info h2 =3D { + .hash_version =3D DX_HASH_HALF_MD4, + }; + int ret1, ret2; + + sb =3D kunit_kzalloc(test, sizeof(*sb), GFP_KERNEL); + ei =3D kunit_kzalloc(test, sizeof(*ei), GFP_KERNEL); + sbi =3D kunit_kzalloc(test, sizeof(*sbi), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, sb); + KUNIT_ASSERT_NOT_NULL(test, ei); + KUNIT_ASSERT_NOT_NULL(test, sbi); + + um =3D utf8_load(UTF8_LATEST); + if (!um) { + kunit_skip(test, "utf8_load(UTF8_LATEST) failed"); + return; + } + + ext4_hash_init_fake_ext4_dir(ei, sb, sbi); + sb->s_encoding =3D um; + ei->vfs_inode.i_flags |=3D S_CASEFOLD; + + KUNIT_ASSERT_TRUE(test, IS_CASEFOLDED(&ei->vfs_inode)); + + ret1 =3D ext4fs_dirhash(&ei->vfs_inode, "Alpha", 5, &h1); + ret2 =3D ext4fs_dirhash(&ei->vfs_inode, "aLPHa", 5, &h2); + + KUNIT_ASSERT_EQ(test, ret1, 0); + KUNIT_ASSERT_EQ(test, ret2, 0); + KUNIT_EXPECT_EQ(test, h1.hash, h2.hash); + KUNIT_EXPECT_EQ(test, h1.minor_hash, h2.minor_hash); + + utf8_unload(um); +} + +static void test_ext4fs_dirhash_casefold_fallback(struct kunit *test) +{ + struct super_block *sb_cf, *sb_plain; + struct ext4_inode_info *ei; + struct ext4_sb_info *sbi; + struct inode *plain_dir; + struct unicode_map *um; + static const char invalid_utf8[] =3D "\xc3\x28"; + struct dx_hash_info folded_dir =3D { + .hash_version =3D DX_HASH_HALF_MD4, + }; + struct dx_hash_info plain =3D { + .hash_version =3D DX_HASH_HALF_MD4, + }; + int ret_cf, ret_plain; + + sb_cf =3D kunit_kzalloc(test, sizeof(*sb_cf), GFP_KERNEL); + sb_plain =3D kunit_kzalloc(test, sizeof(*sb_plain), GFP_KERNEL); + ei =3D kunit_kzalloc(test, sizeof(*ei), GFP_KERNEL); + sbi =3D kunit_kzalloc(test, sizeof(*sbi), GFP_KERNEL); + plain_dir =3D kunit_kzalloc(test, sizeof(*plain_dir), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, sb_cf); + KUNIT_ASSERT_NOT_NULL(test, sb_plain); + KUNIT_ASSERT_NOT_NULL(test, ei); + KUNIT_ASSERT_NOT_NULL(test, sbi); + KUNIT_ASSERT_NOT_NULL(test, plain_dir); + + um =3D utf8_load(UTF8_LATEST); + if (!um) { + kunit_skip(test, "utf8_load(UTF8_LATEST) failed"); + return; + } + + ext4_hash_init_fake_ext4_dir(ei, sb_cf, sbi); + sb_cf->s_encoding =3D um; + ei->vfs_inode.i_flags |=3D S_CASEFOLD; + + KUNIT_ASSERT_TRUE(test, IS_CASEFOLDED(&ei->vfs_inode)); + + ext4_hash_init_fake_dir(plain_dir, sb_plain); + + ret_cf =3D ext4fs_dirhash(&ei->vfs_inode, invalid_utf8, + sizeof(invalid_utf8) - 1, &folded_dir); + ret_plain =3D ext4fs_dirhash(plain_dir, invalid_utf8, + sizeof(invalid_utf8) - 1, &plain); + + KUNIT_ASSERT_EQ(test, ret_cf, 0); + KUNIT_ASSERT_EQ(test, ret_plain, 0); + KUNIT_EXPECT_EQ(test, folded_dir.hash, plain.hash); + KUNIT_EXPECT_EQ(test, folded_dir.minor_hash, plain.minor_hash); + + utf8_unload(um); +} +#endif + +static struct kunit_case ext4_hash_test_cases[] =3D { + KUNIT_CASE(test_ext4fs_dirhash_vectors), + KUNIT_CASE(test_ext4fs_dirhash_seed_changes_result), + KUNIT_CASE(test_ext4fs_dirhash_invalid_version_returns_einval), + KUNIT_CASE(test_ext4fs_dirhash_siphash_without_key_returns_einval), + KUNIT_CASE(test_ext4fs_dirhash_signed_unsigned_differ_on_nonascii), +#if IS_ENABLED(CONFIG_UNICODE) + KUNIT_CASE(test_ext4fs_dirhash_casefolded_names_hash_consistently), + KUNIT_CASE(test_ext4fs_dirhash_casefold_fallback), +#endif + {} +}; + +static struct kunit_suite ext4_hash_test_suite =3D { + .name =3D "ext4_hash", + .test_cases =3D ext4_hash_test_cases, +}; + +kunit_test_suites(&ext4_hash_test_suite); + +MODULE_LICENSE("GPL"); --=20 2.34.1 From nobody Mon Jun 15 19:27:02 2026 Received: from mail-pl1-f179.google.com (mail-pl1-f179.google.com [209.85.214.179]) (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 E7BF2392C47 for ; Mon, 13 Apr 2026 06:51:41 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.214.179 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1776063103; cv=none; b=Gc2s/UOkobTmJNdi/n3BjcBUaWKNgTi63CLtWus2YoDXJz17KKwhroHy0WJiNgsJYkadlCQhQDcvKJ7W0WKi9+TcpsvoRnl+n+zLMVDk3j2uo5RV8I5DSF85/2NNhOMgrZ1frH5A5y7vbjoeuS8EEEHavb8t62VRnVMYthWO9qA= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1776063103; c=relaxed/simple; bh=q9+P125CZ549OI7L3wYHYt7w3xleV/ATVK4sTB5jhk8=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version; b=S2HRfUP0HOL3kPbS7B/2GaBNH2H2HcjAzPrEzSWS8hA0rvNRlNFE51mpoRHE9re3wJPtgO+xpMVDsJJP7jIZMOzI8Quhx+53Km9Tz2konkhCbDAbvJRFl1vpNsENp93PFtprQdsnWH6b+qUKIJdo0EHwLDR2z+KkwAC+orOcNac= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=gms.tku.edu.tw; spf=pass smtp.mailfrom=gms.tku.edu.tw; dkim=pass (2048-bit key) header.d=gms-tku-edu-tw.20251104.gappssmtp.com header.i=@gms-tku-edu-tw.20251104.gappssmtp.com header.b=kIVZo7hZ; arc=none smtp.client-ip=209.85.214.179 Authentication-Results: smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=gms.tku.edu.tw Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gms.tku.edu.tw Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gms-tku-edu-tw.20251104.gappssmtp.com header.i=@gms-tku-edu-tw.20251104.gappssmtp.com header.b="kIVZo7hZ" Received: by mail-pl1-f179.google.com with SMTP id d9443c01a7336-2ab232cc803so19604205ad.3 for ; Sun, 12 Apr 2026 23:51:41 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gms-tku-edu-tw.20251104.gappssmtp.com; s=20251104; t=1776063101; x=1776667901; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=D4TXFklFQDmQmwxZ0Ud/cdt/UKuliJ1cGjB8R+Go2+M=; b=kIVZo7hZtsxgQ7iDHlkvyYqtoezBnE411i0cky1Hk72TbJnW6Riy+Q8Xy5TYjluytp ztV3xITlC/hKBGNCihdyFFviAHWD6XqnCYO67AY4wlRDXqQhCdh2jdFkBA79aHEp8Gw8 PUrcOdn25/LVsQ5oYi7vfZpQNBhOv+u3v2AOBvBtK+h18kQppTx4OelZQITSJBtdl9rn xHcz76k4cVfk39WEUCbRQZAFTv+6PhE3ytFEWsvTRhsYxO5oRQOwngxmzoanr2AVNShs XHoUs+GyN/NwDwG8AhOCuMrtwfCtlCWvsj7jAAXbmGhOXjO27aNbxlAwETL+3x7cqUWS 7jWQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1776063101; x=1776667901; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to; bh=D4TXFklFQDmQmwxZ0Ud/cdt/UKuliJ1cGjB8R+Go2+M=; b=lIEHydY2UhRUItQZZNItJiKwH3qSNqSOSdSwHGQGhZlaMQXioMMINrAOgOgUOC07F+ PS8ELfZ2gUy0Q5OSGnpJS+OD+a1XvkLIGoZkooFTfs/z6ymH6osmryYT38T/rNDcTkyK RrBWB8t4yzhsrtUjbDpZPsWFjNBOp+UaoUa6CDdBet1jLrQ4TJx+FBn974X9C8sobvNE 1jIFopOYpy8a6u0n5N2afAyDxuruPDRTxmgCXD1y9R4eYDL7th8h1wTprxrVOYFKqll1 WbhQ08E8bVeoaY1tpNqnHB7xuojv131ti5Xd3bCOHivWnmP5mIYFyTI5XPX4zi2gU3XS HSPw== X-Forwarded-Encrypted: i=1; AFNElJ9RyBRBzppySdtQhQaspAYbTZILQl/Y4CqaJ9cxok03qbzY7jiN3JJubX+TfXgb7kqW5v3oM5FfEnTBtVc=@vger.kernel.org X-Gm-Message-State: AOJu0Yyova5JfGFhkYvsHB9LlBs8HEU38Amvsji4clpmpq4Ty0s+W90S eCWwjazs9kuOlmMi2ygYJ+nQid6Z5kx1NGLizYBYoKiMYk0+uDIip4Un3n4ONSoF4cQ= X-Gm-Gg: AeBDiesCOr5XlYJTrTDPnjzEccpt4f2Bsjtk4E66P/ib95BNfvlDUoY3QQrHPgllmFR waLD41JPbz2yyL7RAOAIEgQbKHOGHQ4vGUBFhpU+fKOABireoLV++7gcVnl5qF8SogFOpcdo0fM 8zzIq4/ZDTEuObcTfzrNuSuS5ETNA82T3L/IEU9JF0k8fajglcMBTW/WEWCDu8oNMrLlhz743zV QHHr5XXtbSn4hEE6p+76XXmfgCHDct8v35KGWWHeOGdg8mjm3cGdwGPSGHrrVnNl3yNy3/nAy8m gUfHSWUUBPwfGKao+H9K8MW/KxtvCbcqneI6LBi9chUn8eTzWZSHV27u0xa0vx4GMK8jZ8ePUmD T1Vt+ZDTyrl9dzXlnVyoTTWrX03nLpRlpoGL5WgiTu3oobm15PCyIEWNxIGFIja25hWb9BF4lJH ewhqXgVPhgRtY2hTXUNmYAh8VoBKVbugQJp2HtYztphe7gBRs= X-Received: by 2002:a17:903:3886:b0:2b0:6ce3:8f7 with SMTP id d9443c01a7336-2b2d5a95db7mr111280365ad.43.1776063101390; Sun, 12 Apr 2026 23:51:41 -0700 (PDT) Received: from wu-Pro-E500-G6-WS720T.. ([2001:288:7001:2703:d062:a770:8a34:87a2]) by smtp.gmail.com with ESMTPSA id d9443c01a7336-2b2d4dde346sm100107855ad.29.2026.04.12.23.51.39 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sun, 12 Apr 2026 23:51:41 -0700 (PDT) From: Guan-Chun Wu <409411716@gms.tku.edu.tw> To: Theodore Ts'o , Andreas Dilger , Baokun Li , Jan Kara , Ojaswin Mujoo , Ritesh Harjani , Zhang Yi Cc: linux-ext4@vger.kernel.org, linux-kernel@vger.kernel.org, edward062254@gmail.com, visitorckw@gmail.com, david.laight.linux@gmail.com, Guan-Chun Wu <409411716@gms.tku.edu.tw> Subject: [PATCH v3 2/2] ext4: improve str2hashbuf by processing 4-byte chunks and removing function pointers Date: Mon, 13 Apr 2026 14:51:14 +0800 Message-Id: <20260413065114.730231-3-409411716@gms.tku.edu.tw> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20260413065114.730231-1-409411716@gms.tku.edu.tw> References: <20260413065114.730231-1-409411716@gms.tku.edu.tw> 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" The original byte-by-byte implementation with modulo checks is less efficient. Refactor str2hashbuf_unsigned() and str2hashbuf_signed() to process input in explicit 4-byte chunks instead of using a modulus-based loop to emit words byte by byte. Additionally, the use of function pointers for selecting the appropriate str2hashbuf implementation has been removed. Instead, the functions are directly invoked based on the hash type, eliminating the overhead of dynamic function calls. Performance test (x86_64, Intel Core i7-10700 @ 2.90GHz, average over 10000 runs, using kernel module for testing): len | orig_s | new_s | orig_u | new_u ----+--------+-------+--------+------- 1 | 70 | 71 | 63 | 63 8 | 68 | 64 | 64 | 62 32 | 75 | 70 | 75 | 63 64 | 96 | 71 | 100 | 68 255 | 192 | 108 | 187 | 84 This change improves performance, especially for larger input sizes. Signed-off-by: Guan-Chun Wu <409411716@gms.tku.edu.tw> --- fs/ext4/hash.c | 64 +++++++++++++++++++++++++++++++++----------------- 1 file changed, 42 insertions(+), 22 deletions(-) diff --git a/fs/ext4/hash.c b/fs/ext4/hash.c index 48483cd01..c3fb2df44 100644 --- a/fs/ext4/hash.c +++ b/fs/ext4/hash.c @@ -9,6 +9,7 @@ #include #include #include +#include #include "ext4.h" =20 #define DELTA 0x9E3779B9 @@ -141,21 +142,28 @@ static void str2hashbuf_signed(const char *msg, int l= en, __u32 *buf, int num) pad =3D (__u32)len | ((__u32)len << 8); pad |=3D pad << 16; =20 - val =3D pad; if (len > num*4) len =3D num * 4; - for (i =3D 0; i < len; i++) { - val =3D ((int) scp[i]) + (val << 8); - if ((i % 4) =3D=3D 3) { - *buf++ =3D val; - val =3D pad; - num--; - } + + while (len >=3D 4) { + val =3D (scp[0] << 24) + (scp[1] << 16) + (scp[2] << 8) + scp[3]; + *buf++ =3D val; + scp +=3D 4; + len -=3D 4; + num--; } + + val =3D pad; + + for (i =3D 0; i < len; i++) + val =3D scp[i] + (val << 8); + if (--num >=3D 0) *buf++ =3D val; + while (--num >=3D 0) *buf++ =3D pad; + } =20 static void str2hashbuf_unsigned(const char *msg, int len, __u32 *buf, int= num) @@ -167,21 +175,28 @@ static void str2hashbuf_unsigned(const char *msg, int= len, __u32 *buf, int num) pad =3D (__u32)len | ((__u32)len << 8); pad |=3D pad << 16; =20 - val =3D pad; if (len > num*4) len =3D num * 4; - for (i =3D 0; i < len; i++) { - val =3D ((int) ucp[i]) + (val << 8); - if ((i % 4) =3D=3D 3) { - *buf++ =3D val; - val =3D pad; - num--; - } + + while (len >=3D 4) { + val =3D get_unaligned_be32(ucp); + *buf++ =3D val; + ucp +=3D 4; + len -=3D 4; + num--; } + + val =3D pad; + + for (i =3D 0; i < len; i++) + val =3D ucp[i] + (val << 8); + if (--num >=3D 0) *buf++ =3D val; + while (--num >=3D 0) *buf++ =3D pad; + } =20 /* @@ -205,8 +220,7 @@ static int __ext4fs_dirhash(const struct inode *dir, co= nst char *name, int len, const char *p; int i; __u32 in[8], buf[4]; - void (*str2hashbuf)(const char *, int, __u32 *, int) =3D - str2hashbuf_signed; + bool use_unsigned =3D false; =20 /* Initialize the default seed for the hash checksum functions */ buf[0] =3D 0x67452301; @@ -232,12 +246,15 @@ static int __ext4fs_dirhash(const struct inode *dir, = const char *name, int len, hash =3D dx_hack_hash_signed(name, len); break; case DX_HASH_HALF_MD4_UNSIGNED: - str2hashbuf =3D str2hashbuf_unsigned; + use_unsigned =3D true; fallthrough; case DX_HASH_HALF_MD4: p =3D name; while (len > 0) { - (*str2hashbuf)(p, len, in, 8); + if (use_unsigned) + str2hashbuf_unsigned(p, len, in, 8); + else + str2hashbuf_signed(p, len, in, 8); half_md4_transform(buf, in); len -=3D 32; p +=3D 32; @@ -246,12 +263,15 @@ static int __ext4fs_dirhash(const struct inode *dir, = const char *name, int len, hash =3D buf[1]; break; case DX_HASH_TEA_UNSIGNED: - str2hashbuf =3D str2hashbuf_unsigned; + use_unsigned =3D true; fallthrough; case DX_HASH_TEA: p =3D name; while (len > 0) { - (*str2hashbuf)(p, len, in, 4); + if (use_unsigned) + str2hashbuf_unsigned(p, len, in, 4); + else + str2hashbuf_signed(p, len, in, 4); TEA_transform(buf, in); len -=3D 16; p +=3D 16; --=20 2.34.1