Merge tag 'ext4_for_linus-7.2-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/tytso/ext4

Pull ext4 updates from Ted Ts'o:

 - A major rework of the fast commit mechanism to avoid lock contention
   and deadlocks. We also export snapshot statistics in
   /proc/fs/ext4/*/fc_info

 - Performance optimization for directory hash computation by processing
   input in 4-byte chunks and removing function pointers, along with new
   KUnit tests for directory hash

 - Cleanups in JBD2 to remove special slabs and use kmalloc() instead

 - Various bug fixes, including:
     - Early validation of donor superblock in EXT4_IOC_MOVE_EXT to
       avoid cross-fs deadlock
     - Fix for a kernel BUG in ext4_write_inline_data_end under
       data=journal
     - Fix for a NULL dereference in jbd2_journal_dirty_metadata when
       handle is aborted
     - Fix for an underflow in JBD2 fast commit block initialization
       check
     - Fix for LOGFLUSH shutdown ordering to ensure ordered data
       writeback
     - Miscellaneous fixes for error path return values and KUnit
       assertions

* tag 'ext4_for_linus-7.2-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/tytso/ext4:
  ext4: validate donor file superblock early in EXT4_IOC_MOVE_EXT
  ext4: fix kernel BUG in ext4_write_inline_data_end
  ext4: fix ERR_PTR(0) in ext4_mkdir()
  jbd2: remove special jbd2 slabs
  ext4: remove mention of PageWriteback
  ext4: improve str2hashbuf by processing 4-byte chunks and removing function pointers
  ext4: add Kunit coverage for directory hash computation
  ext4: fast commit: export snapshot stats in fc_info
  ext4: fast commit: add lock_updates tracepoint
  ext4: fast commit: avoid i_data_sem by dropping ext4_map_blocks() in snapshots
  ext4: fast commit: avoid self-deadlock in inode snapshotting
  ext4: fast commit: avoid waiting for FC_COMMITTING
  ext4: lockdep: handle i_data_sem subclassing for special inodes
  ext4: fast commit: snapshot inode state before writing log
  jbd2: fix integer underflow in jbd2_journal_initialize_fast_commit()
  ext4: fix fast commit wait/wake bit mapping on 64-bit
  jbd2: check for aborted handle in jbd2_journal_dirty_metadata()
  ext4: Use %pe to print PTR_ERR()
  ext4: fix LOGFLUSH shutdown ordering to allow ordered-mode data writeback
  ext4: replace KUnit tests for memcmp() with KUNIT_ASSERT_MEMEQ()
This commit is contained in:
Linus Torvalds
2026-06-18 09:42:02 -07:00
17 changed files with 1505 additions and 345 deletions

View File

@@ -15,7 +15,7 @@ ext4-y := balloc.o bitmap.o block_validity.o dir.o ext4_jbd2.o extents.o \
ext4-$(CONFIG_EXT4_FS_POSIX_ACL) += acl.o
ext4-$(CONFIG_EXT4_FS_SECURITY) += xattr_security.o
ext4-test-objs += inode-test.o mballoc-test.o \
extents-test.o
extents-test.o hash-test.o
obj-$(CONFIG_EXT4_KUNIT_TESTS) += ext4-test.o
ext4-$(CONFIG_FS_VERITY) += verity.o
ext4-$(CONFIG_FS_ENCRYPTION) += crypto.o

View File

@@ -1015,14 +1015,32 @@ do { \
* than the first
* I_DATA_SEM_QUOTA - Used for quota inodes only
* I_DATA_SEM_EA - Used for ea_inodes only
* I_DATA_SEM_JOURNAL - Used for journal inode only
*/
enum {
I_DATA_SEM_NORMAL = 0,
I_DATA_SEM_OTHER,
I_DATA_SEM_QUOTA,
I_DATA_SEM_EA
I_DATA_SEM_EA,
I_DATA_SEM_JOURNAL
};
struct ext4_fc_inode_snap;
/*
* Snapshot failure reasons for ext4_fc_lock_updates tracepoint.
* Keep these stable for tooling.
*/
enum ext4_fc_snap_err {
EXT4_FC_SNAP_ERR_NONE = 0,
EXT4_FC_SNAP_ERR_ES_MISS,
EXT4_FC_SNAP_ERR_ES_DELAYED,
EXT4_FC_SNAP_ERR_ES_OTHER,
EXT4_FC_SNAP_ERR_INODES_CAP,
EXT4_FC_SNAP_ERR_RANGES_CAP,
EXT4_FC_SNAP_ERR_NOMEM,
EXT4_FC_SNAP_ERR_INODE_LOC,
};
/*
* fourth extended file system inode data in memory
@@ -1079,6 +1097,22 @@ struct ext4_inode_info {
/* End of lblk range that needs to be committed in this fast commit */
ext4_lblk_t i_fc_lblk_len;
/*
* Commit-time fast commit snapshots.
*
* i_fc_snap is installed and freed under sbi->s_fc_lock. The fast
* commit log writing path reads the snapshot under sbi->s_fc_lock while
* serializing fast commit TLVs.
*
* The snapshot lifetime is bounded by EXT4_STATE_FC_COMMITTING and the
* corresponding cleanup / eviction paths.
*
* i_fc_snap points to per-inode snapshot data for fast commit:
* - a raw inode snapshot for EXT4_FC_TAG_INODE
* - data range records for EXT4_FC_TAG_{ADD,DEL}_RANGE
*/
struct ext4_fc_inode_snap *i_fc_snap;
spinlock_t i_raw_lock; /* protects updates to the raw inode */
/*
@@ -1516,6 +1550,36 @@ struct ext4_orphan_info {
* file blocks */
};
/*
* Ext4 fast commit snapshot statistics.
*
* These are best-effort counters intended for debugging / performance
* introspection; they are not exact under concurrent updates.
*/
struct ext4_fc_snap_stats {
atomic64_t lock_updates_ns_total;
atomic64_t lock_updates_ns_max;
atomic64_t lock_updates_samples;
atomic64_t snap_inodes;
atomic64_t snap_ranges;
atomic64_t snap_fail_es_miss;
atomic64_t snap_fail_es_delayed;
atomic64_t snap_fail_es_other;
atomic64_t snap_fail_inodes_cap;
atomic64_t snap_fail_ranges_cap;
atomic64_t snap_fail_nomem;
atomic64_t snap_fail_inode_loc;
/*
* Missing inode snapshots during log writing should never happen.
* Keep this counter to help catch unexpected regressions.
*/
atomic64_t snap_fail_no_snap;
};
/*
* fourth extended-fs super-block data in memory
*/
@@ -1790,6 +1854,7 @@ struct ext4_sb_info {
struct mutex s_fc_lock;
struct buffer_head *s_fc_bh;
struct ext4_fc_stats s_fc_stats;
struct ext4_fc_snap_stats s_fc_snap_stats;
tid_t s_fc_ineligible_tid;
#ifdef CONFIG_EXT4_DEBUG
int s_fc_debug_max_replay;
@@ -1972,6 +2037,7 @@ enum {
EXT4_STATE_FC_COMMITTING, /* Fast commit ongoing */
EXT4_STATE_FC_FLUSHING_DATA, /* Fast commit flushing data */
EXT4_STATE_ORPHAN_FILE, /* Inode orphaned in orphan file */
EXT4_STATE_FC_REQUEUE, /* Inode modified during fast commit */
};
#define EXT4_INODE_BIT_FNS(name, field, offset) \
@@ -2000,6 +2066,8 @@ EXT4_INODE_BIT_FNS(flag, flags, 0)
static inline int ext4_test_inode_state(struct inode *inode, int bit);
static inline void ext4_set_inode_state(struct inode *inode, int bit);
static inline void ext4_clear_inode_state(struct inode *inode, int bit);
static inline unsigned long *ext4_inode_state_wait_word(struct inode *inode);
static inline int ext4_inode_state_wait_bit(int bit);
#if (BITS_PER_LONG < 64)
EXT4_INODE_BIT_FNS(state, state_flags, 0)
@@ -2015,6 +2083,24 @@ static inline void ext4_clear_state_flags(struct ext4_inode_info *ei)
/* We depend on the fact that callers will set i_flags */
}
#endif
static inline unsigned long *ext4_inode_state_wait_word(struct inode *inode)
{
#if (BITS_PER_LONG < 64)
return &EXT4_I(inode)->i_state_flags;
#else
return &EXT4_I(inode)->i_flags;
#endif
}
static inline int ext4_inode_state_wait_bit(int bit)
{
#if (BITS_PER_LONG < 64)
return bit;
#else
return bit + 32;
#endif
}
#else
/* Assume that user mode programs are passing in an ext4fs superblock, not
* a kernel struct super_block. This will allow us to call the feature-test
@@ -3080,8 +3166,9 @@ extern int ext4_file_getattr(struct mnt_idmap *, const struct path *,
struct kstat *, u32, unsigned int);
extern void ext4_dirty_inode(struct inode *, int);
extern int ext4_change_inode_journal_flag(struct inode *, int);
extern int ext4_get_inode_loc(struct inode *, struct ext4_iloc *);
extern int ext4_get_fc_inode_loc(struct super_block *sb, unsigned long ino,
int ext4_get_inode_loc(struct inode *inode, struct ext4_iloc *iloc);
int ext4_get_inode_loc_noio(struct inode *inode, struct ext4_iloc *iloc);
int ext4_get_fc_inode_loc(struct super_block *sb, unsigned long ino,
struct ext4_iloc *iloc);
extern int ext4_inode_attach_jinode(struct inode *inode);
extern int ext4_can_truncate(struct inode *inode);

View File

@@ -3268,8 +3268,8 @@ static struct ext4_ext_path *ext4_split_extent_at(handle_t *handle,
*/
path = ext4_find_extent(inode, ee_block, NULL, flags | EXT4_EX_NOFAIL);
if (IS_ERR(path)) {
EXT4_ERROR_INODE(inode, "Failed split extent on %u, err %ld",
split, PTR_ERR(path));
EXT4_ERROR_INODE(inode, "Failed split extent on %u, err %pe",
split, path);
goto out_path;
}

File diff suppressed because it is too large Load Diff

567
fs/ext4/hash-test.c Normal file
View File

@@ -0,0 +1,567 @@
// SPDX-License-Identifier: GPL-2.0
/*
* KUnit tests for ext4 directory hash computation.
*/
#include <kunit/test.h>
#include <kunit/resource.h>
#include <linux/fs.h>
#include <linux/stddef.h>
#include <linux/string.h>
#include <linux/unicode.h>
#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 = 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 = sbi;
sbi->s_sb = sb;
}
#ifdef CONFIG_FS_ENCRYPTION
static const struct fscrypt_operations ext4_hash_test_cryptops = {
.inode_info_offs =
(int)offsetof(struct ext4_inode_info, i_crypt_info) -
(int)offsetof(struct ext4_inode_info, vfs_inode),
};
#endif
static void ext4_hash_init_fake_ext4_dir(struct ext4_inode_info *ei,
struct super_block *sb,
struct ext4_sb_info *sbi)
{
struct inode *dir = &ei->vfs_inode;
memset(sb, 0, sizeof(*sb));
memset(ei, 0, sizeof(*ei));
memset(sbi, 0, sizeof(*sbi));
strscpy(sb->s_id, "kunit-ext4", sizeof(sb->s_id));
sb->s_fs_info = sbi;
sbi->s_sb = sb;
dir->i_sb = sb;
dir->i_mode = S_IFDIR;
#ifdef CONFIG_FS_ENCRYPTION
fscrypt_set_ops(sb, &ext4_hash_test_cryptops);
#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[] = {
{
.name = "legacy_abc",
.hash_version = DX_HASH_LEGACY,
.input = "abc",
.len = 3,
.use_seed = false,
.expected_hash = 0x75afd992,
.expected_minor_hash = 0x00000000,
},
{
.name = "legacy_unsigned_abc",
.hash_version = DX_HASH_LEGACY_UNSIGNED,
.input = "abc",
.len = 3,
.use_seed = false,
.expected_hash = 0x75afd992,
.expected_minor_hash = 0x00000000,
},
{
.name = "half_md4_abc",
.hash_version = DX_HASH_HALF_MD4,
.input = "abc",
.len = 3,
.use_seed = false,
.expected_hash = 0xd196a868,
.expected_minor_hash = 0xc420eb28,
},
{
.name = "half_md4_unsigned_abc",
.hash_version = DX_HASH_HALF_MD4_UNSIGNED,
.input = "abc",
.len = 3,
.use_seed = false,
.expected_hash = 0xd196a868,
.expected_minor_hash = 0xc420eb28,
},
{
.name = "tea_abc",
.hash_version = DX_HASH_TEA,
.input = "abc",
.len = 3,
.use_seed = false,
.expected_hash = 0xb1435ec4,
.expected_minor_hash = 0x3f7eaa0e,
},
{
.name = "tea_unsigned_abc",
.hash_version = DX_HASH_TEA_UNSIGNED,
.input = "abc",
.len = 3,
.use_seed = false,
.expected_hash = 0xb1435ec4,
.expected_minor_hash = 0x3f7eaa0e,
},
{
.name = "empty_half_md4",
.hash_version = DX_HASH_HALF_MD4,
.input = "",
.len = 0,
.use_seed = false,
.expected_hash = 0xefcdab88,
.expected_minor_hash = 0x98badcfe,
},
{
.name = "half_md4_31bytes",
.hash_version = DX_HASH_HALF_MD4,
.input = "1234567890123456789012345678901",
.len = 31,
.use_seed = false,
.expected_hash = 0xc4db1f78,
.expected_minor_hash = 0xea23921b,
},
{
.name = "half_md4_32bytes",
.hash_version = DX_HASH_HALF_MD4,
.input = "12345678901234567890123456789012",
.len = 32,
.use_seed = false,
.expected_hash = 0xfa6cc63e,
.expected_minor_hash = 0x2f77bd1c,
},
{
.name = "half_md4_33bytes",
.hash_version = DX_HASH_HALF_MD4,
.input = "123456789012345678901234567890123",
.len = 33,
.use_seed = false,
.expected_hash = 0xdc0c2dec,
.expected_minor_hash = 0x5ca23365,
},
{
.name = "half_md4_unsigned_31bytes",
.hash_version = DX_HASH_HALF_MD4_UNSIGNED,
.input = "1234567890123456789012345678901",
.len = 31,
.use_seed = false,
.expected_hash = 0xc4db1f78,
.expected_minor_hash = 0xea23921b,
},
{
.name = "half_md4_unsigned_32bytes",
.hash_version = DX_HASH_HALF_MD4_UNSIGNED,
.input = "12345678901234567890123456789012",
.len = 32,
.use_seed = false,
.expected_hash = 0xfa6cc63e,
.expected_minor_hash = 0x2f77bd1c,
},
{
.name = "half_md4_unsigned_33bytes",
.hash_version = DX_HASH_HALF_MD4_UNSIGNED,
.input = "123456789012345678901234567890123",
.len = 33,
.use_seed = false,
.expected_hash = 0xdc0c2dec,
.expected_minor_hash = 0x5ca23365,
},
{
.name = "tea_15bytes",
.hash_version = DX_HASH_TEA,
.input = "123456789abcdef",
.len = 15,
.use_seed = false,
.expected_hash = 0xa562903a,
.expected_minor_hash = 0x6174a00f,
},
{
.name = "tea_16bytes",
.hash_version = DX_HASH_TEA,
.input = "1234567890abcdef",
.len = 16,
.use_seed = false,
.expected_hash = 0x8449f258,
.expected_minor_hash = 0x49a16d46,
},
{
.name = "tea_17bytes",
.hash_version = DX_HASH_TEA,
.input = "123456789abcdefgh",
.len = 17,
.use_seed = false,
.expected_hash = 0xf32ec10c,
.expected_minor_hash = 0x58ceae61,
},
{
.name = "half_md4_seeded",
.hash_version = DX_HASH_HALF_MD4,
.input = "same-name",
.len = 9,
.seed = { 0x11111111, 0x22222222, 0x33333333, 0x44444444 },
.use_seed = true,
.expected_hash = 0x8aebf604,
.expected_minor_hash = 0x66ce48fe,
},
{
.name = "half_md4_non_ascii_signed",
.hash_version = DX_HASH_HALF_MD4,
.input = "\x80\x81\x82\x83\x84",
.len = 5,
.use_seed = false,
.expected_hash = 0x8bab0498,
.expected_minor_hash = 0xc326632d,
},
{
.name = "half_md4_non_ascii_unsigned",
.hash_version = DX_HASH_HALF_MD4_UNSIGNED,
.input = "\x80\x81\x82\x83\x84",
.len = 5,
.use_seed = false,
.expected_hash = 0xbc48596e,
.expected_minor_hash = 0xde0fad41,
},
{
.name = "tea_non_ascii_signed",
.hash_version = DX_HASH_TEA,
.input = "\x80\x81\x82\x83\x84",
.len = 5,
.use_seed = false,
.expected_hash = 0x21e3a154,
.expected_minor_hash = 0x90112c3d,
},
{
.name = "tea_non_ascii_unsigned",
.hash_version = DX_HASH_TEA_UNSIGNED,
.input = "\x80\x81\x82\x83\x84",
.len = 5,
.use_seed = false,
.expected_hash = 0x9b648616,
.expected_minor_hash = 0x011dd507,
},
};
static void test_ext4fs_dirhash_vectors(struct kunit *test)
{
struct super_block *sb;
struct inode *dir;
int i;
sb = kunit_kzalloc(test, sizeof(*sb), GFP_KERNEL);
dir = 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 = 0; i < ARRAY_SIZE(ext4_dirhash_test_cases); i++) {
const struct ext4_dirhash_test_case *tc =
&ext4_dirhash_test_cases[i];
struct dx_hash_info hinfo;
int ret;
memset(&hinfo, 0, sizeof(hinfo));
hinfo.hash_version = tc->hash_version;
hinfo.seed = tc->use_seed ? (u32 *)tc->seed : NULL;
ret = ext4fs_dirhash(dir, tc->input, tc->len, &hinfo);
KUNIT_ASSERT_EQ_MSG(test, ret, 0, "case=%s", tc->name);
KUNIT_EXPECT_EQ_MSG(test, hinfo.hash, tc->expected_hash,
"case=%s", tc->name);
KUNIT_EXPECT_EQ_MSG(test, hinfo.minor_hash,
tc->expected_minor_hash,
"case=%s", tc->name);
}
}
static void test_ext4fs_dirhash_seed_changes_result(struct kunit *test)
{
struct super_block *sb;
struct inode *dir;
u32 seed[4] = { 0x11111111, 0x22222222, 0x33333333, 0x44444444 };
struct dx_hash_info plain = {
.hash_version = DX_HASH_HALF_MD4,
};
struct dx_hash_info seeded = {
.hash_version = DX_HASH_HALF_MD4,
.seed = seed,
};
int ret_plain, ret_seeded;
sb = kunit_kzalloc(test, sizeof(*sb), GFP_KERNEL);
dir = 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 = ext4fs_dirhash(dir, "same-name", 9, &plain);
ret_seeded = 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 != seeded.hash ||
plain.minor_hash != seeded.minor_hash);
}
static void test_ext4fs_dirhash_invalid_version_returns_einval(struct kunit *test)
{
struct super_block *sb;
struct inode *dir;
struct ext4_sb_info *sbi;
struct dx_hash_info hinfo = {
.hash = 0xdeadbeef,
.minor_hash = 0xcafebabe,
.hash_version = DX_HASH_LAST + 1,
};
int ret;
sb = kunit_kzalloc(test, sizeof(*sb), GFP_KERNEL);
dir = kunit_kzalloc(test, sizeof(*dir), GFP_KERNEL);
sbi = 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 = 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 ext4_inode_info *ei;
struct inode *dir;
struct ext4_sb_info *sbi;
struct dx_hash_info hinfo = {
.hash_version = DX_HASH_SIPHASH,
};
int ret;
sb = kunit_kzalloc(test, sizeof(*sb), GFP_KERNEL);
ei = kunit_kzalloc(test, sizeof(*ei), GFP_KERNEL);
sbi = 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);
ext4_hash_init_fake_ext4_dir(ei, sb, sbi);
dir = &ei->vfs_inode;
ret = ext4fs_dirhash(dir, "name", strlen("name"), &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[] = "\x80\xff\x81\xfe\101bc";
struct dx_hash_info legacy_signed = {
.hash_version = DX_HASH_LEGACY,
};
struct dx_hash_info legacy_unsigned = {
.hash_version = DX_HASH_LEGACY_UNSIGNED,
};
struct dx_hash_info md4_signed = {
.hash_version = DX_HASH_HALF_MD4,
};
struct dx_hash_info md4_unsigned = {
.hash_version = DX_HASH_HALF_MD4_UNSIGNED,
};
struct dx_hash_info tea_signed = {
.hash_version = DX_HASH_TEA,
};
struct dx_hash_info tea_unsigned = {
.hash_version = DX_HASH_TEA_UNSIGNED,
};
int ret;
sb = kunit_kzalloc(test, sizeof(*sb), GFP_KERNEL);
dir = 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 = ext4fs_dirhash(dir, input, sizeof(input) - 1, &legacy_signed);
KUNIT_ASSERT_EQ(test, ret, 0);
ret = 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 = ext4fs_dirhash(dir, input, sizeof(input) - 1, &md4_signed);
KUNIT_ASSERT_EQ(test, ret, 0);
ret = ext4fs_dirhash(dir, input, sizeof(input) - 1, &md4_unsigned);
KUNIT_ASSERT_EQ(test, ret, 0);
KUNIT_EXPECT_TRUE(test,
md4_signed.hash != md4_unsigned.hash ||
md4_signed.minor_hash != md4_unsigned.minor_hash);
ret = ext4fs_dirhash(dir, input, sizeof(input) - 1, &tea_signed);
KUNIT_ASSERT_EQ(test, ret, 0);
ret = ext4fs_dirhash(dir, input, sizeof(input) - 1, &tea_unsigned);
KUNIT_ASSERT_EQ(test, ret, 0);
KUNIT_EXPECT_TRUE(test,
tea_signed.hash != tea_unsigned.hash ||
tea_signed.minor_hash != tea_unsigned.minor_hash);
}
#if IS_ENABLED(CONFIG_UNICODE)
KUNIT_DEFINE_ACTION_WRAPPER(utf8_unload_action, utf8_unload,
struct unicode_map *);
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 = {
.hash_version = DX_HASH_HALF_MD4,
};
struct dx_hash_info h2 = {
.hash_version = DX_HASH_HALF_MD4,
};
int ret, ret1, ret2;
sb = kunit_kzalloc(test, sizeof(*sb), GFP_KERNEL);
ei = kunit_kzalloc(test, sizeof(*ei), GFP_KERNEL);
sbi = 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 = utf8_load(UTF8_LATEST);
if (IS_ERR(um)) {
kunit_skip(test, "utf8_load(UTF8_LATEST) failed: %pe",
um);
return;
}
ret = kunit_add_action_or_reset(test, utf8_unload_action, um);
KUNIT_ASSERT_EQ(test, ret, 0);
ext4_hash_init_fake_ext4_dir(ei, sb, sbi);
sb->s_encoding = um;
ei->vfs_inode.i_flags |= S_CASEFOLD;
KUNIT_ASSERT_TRUE(test, IS_CASEFOLDED(&ei->vfs_inode));
ret1 = ext4fs_dirhash(&ei->vfs_inode, "Alpha", 5, &h1);
ret2 = 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);
}
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[] = "\xc3\x28";
struct dx_hash_info folded_dir = {
.hash_version = DX_HASH_HALF_MD4,
};
struct dx_hash_info plain = {
.hash_version = DX_HASH_HALF_MD4,
};
int ret, ret_cf, ret_plain;
sb_cf = kunit_kzalloc(test, sizeof(*sb_cf), GFP_KERNEL);
sb_plain = kunit_kzalloc(test, sizeof(*sb_plain), GFP_KERNEL);
ei = kunit_kzalloc(test, sizeof(*ei), GFP_KERNEL);
sbi = kunit_kzalloc(test, sizeof(*sbi), GFP_KERNEL);
plain_dir = 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 = utf8_load(UTF8_LATEST);
if (IS_ERR(um)) {
kunit_skip(test, "utf8_load(UTF8_LATEST) failed: %pe",
um);
return;
}
ret = kunit_add_action_or_reset(test, utf8_unload_action, um);
KUNIT_ASSERT_EQ(test, ret, 0);
ext4_hash_init_fake_ext4_dir(ei, sb_cf, sbi);
sb_cf->s_encoding = um;
ei->vfs_inode.i_flags |= S_CASEFOLD;
KUNIT_ASSERT_TRUE(test, IS_CASEFOLDED(&ei->vfs_inode));
ext4_hash_init_fake_dir(plain_dir, sb_plain);
ret_cf = ext4fs_dirhash(&ei->vfs_inode, invalid_utf8,
sizeof(invalid_utf8) - 1, &folded_dir);
ret_plain = 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);
}
#endif
static struct kunit_case ext4_hash_test_cases[] = {
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 = {
.name = "ext4_hash",
.test_cases = ext4_hash_test_cases,
};
kunit_test_suites(&ext4_hash_test_suite);
MODULE_LICENSE("GPL");

View File

@@ -9,6 +9,7 @@
#include <linux/unicode.h>
#include <linux/compiler.h>
#include <linux/bitops.h>
#include <linux/unaligned.h>
#include "ext4.h"
#define DELTA 0x9E3779B9
@@ -141,21 +142,28 @@ static void str2hashbuf_signed(const char *msg, int len, __u32 *buf, int num)
pad = (__u32)len | ((__u32)len << 8);
pad |= pad << 16;
val = pad;
if (len > num*4)
len = num * 4;
for (i = 0; i < len; i++) {
val = ((int) scp[i]) + (val << 8);
if ((i % 4) == 3) {
*buf++ = val;
val = pad;
num--;
}
while (len >= 4) {
val = ((__u32)scp[0] << 24) + ((__u32)scp[1] << 16) + ((__u32)scp[2] << 8) + scp[3];
*buf++ = val;
scp += 4;
len -= 4;
num--;
}
val = pad;
for (i = 0; i < len; i++)
val = scp[i] + (val << 8);
if (--num >= 0)
*buf++ = val;
while (--num >= 0)
*buf++ = pad;
}
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 = (__u32)len | ((__u32)len << 8);
pad |= pad << 16;
val = pad;
if (len > num*4)
len = num * 4;
for (i = 0; i < len; i++) {
val = ((int) ucp[i]) + (val << 8);
if ((i % 4) == 3) {
*buf++ = val;
val = pad;
num--;
}
while (len >= 4) {
val = get_unaligned_be32(ucp);
*buf++ = val;
ucp += 4;
len -= 4;
num--;
}
val = pad;
for (i = 0; i < len; i++)
val = ucp[i] + (val << 8);
if (--num >= 0)
*buf++ = val;
while (--num >= 0)
*buf++ = pad;
}
/*
@@ -205,8 +220,7 @@ static int __ext4fs_dirhash(const struct inode *dir, const char *name, int len,
const char *p;
int i;
__u32 in[8], buf[4];
void (*str2hashbuf)(const char *, int, __u32 *, int) =
str2hashbuf_signed;
bool use_unsigned = false;
/* Initialize the default seed for the hash checksum functions */
buf[0] = 0x67452301;
@@ -232,12 +246,15 @@ static int __ext4fs_dirhash(const struct inode *dir, const char *name, int len,
hash = dx_hack_hash_signed(name, len);
break;
case DX_HASH_HALF_MD4_UNSIGNED:
str2hashbuf = str2hashbuf_unsigned;
use_unsigned = true;
fallthrough;
case DX_HASH_HALF_MD4:
p = 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 -= 32;
p += 32;
@@ -246,12 +263,15 @@ static int __ext4fs_dirhash(const struct inode *dir, const char *name, int len,
hash = buf[1];
break;
case DX_HASH_TEA_UNSIGNED:
str2hashbuf = str2hashbuf_unsigned;
use_unsigned = true;
fallthrough;
case DX_HASH_TEA:
p = 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 -= 16;
p += 16;
@@ -321,3 +341,7 @@ int ext4fs_dirhash(const struct inode *dir, const char *name, int len,
#endif
return __ext4fs_dirhash(dir, name, len, hinfo);
}
#if IS_ENABLED(CONFIG_EXT4_KUNIT_TESTS)
EXPORT_SYMBOL_FOR_EXT4_TEST(ext4fs_dirhash);
#endif

View File

@@ -1560,7 +1560,8 @@ static int ext4_journalled_write_end(const struct kiocb *iocb,
BUG_ON(!ext4_handle_valid(handle));
if (ext4_has_inline_data(inode))
if (ext4_has_inline_data(inode) &&
ext4_test_inode_state(inode, EXT4_STATE_MAY_INLINE_DATA))
return ext4_write_inline_data_end(inode, pos, len, copied,
folio);
@@ -5025,6 +5026,57 @@ int ext4_get_inode_loc(struct inode *inode, struct ext4_iloc *iloc)
return ret;
}
/*
* ext4_get_inode_loc_noio() is a best-effort variant of ext4_get_inode_loc().
* It looks up the inode table block in the buffer cache and returns -EAGAIN if
* the block is not present or not uptodate, without starting any I/O.
*/
int ext4_get_inode_loc_noio(struct inode *inode, struct ext4_iloc *iloc)
{
struct super_block *sb = inode->i_sb;
struct ext4_group_desc *gdp;
struct buffer_head *bh;
ext4_fsblk_t block;
int inodes_per_block, inode_offset;
unsigned long ino = inode->i_ino;
iloc->bh = NULL;
if (ino < EXT4_ROOT_INO ||
ino > le32_to_cpu(EXT4_SB(sb)->s_es->s_inodes_count))
return -EFSCORRUPTED;
iloc->block_group = (ino - 1) / EXT4_INODES_PER_GROUP(sb);
gdp = ext4_get_group_desc(sb, iloc->block_group, NULL);
if (!gdp)
return -EIO;
/* Figure out the offset within the block group inode table. */
inodes_per_block = EXT4_SB(sb)->s_inodes_per_block;
inode_offset = ((ino - 1) % EXT4_INODES_PER_GROUP(sb));
iloc->offset = (inode_offset % inodes_per_block) * EXT4_INODE_SIZE(sb);
block = ext4_inode_table(sb, gdp);
if (block <= le32_to_cpu(EXT4_SB(sb)->s_es->s_first_data_block) ||
block >= ext4_blocks_count(EXT4_SB(sb)->s_es)) {
ext4_error(sb,
"Invalid inode table block %llu in block_group %u",
block, iloc->block_group);
return -EFSCORRUPTED;
}
block += inode_offset / inodes_per_block;
bh = sb_find_get_block(sb, block);
if (!bh)
return -EAGAIN;
if (!ext4_buffer_uptodate(bh)) {
brelse(bh);
return -EAGAIN;
}
iloc->bh = bh;
return 0;
}
int ext4_get_fc_inode_loc(struct super_block *sb, unsigned long ino,
struct ext4_iloc *iloc)

View File

@@ -830,11 +830,17 @@ int ext4_force_shutdown(struct super_block *sb, u32 flags)
bdev_thaw(sb->s_bdev);
break;
case EXT4_GOING_FLAGS_LOGFLUSH:
/*
* Call ext4_force_commit() before setting EXT4_FLAGS_SHUTDOWN.
* This is because in data=ordered mode, journal commit
* triggers data writeback which fails if shutdown is already
* set, causing the journal to be aborted prematurely before
* the commit succeeds.
*/
(void) ext4_force_commit(sb);
set_bit(EXT4_FLAGS_SHUTDOWN, &sbi->s_ext4_flags);
if (sbi->s_journal && !is_journal_aborted(sbi->s_journal)) {
(void) ext4_force_commit(sb);
if (sbi->s_journal && !is_journal_aborted(sbi->s_journal))
jbd2_journal_abort(sbi->s_journal, -ESHUTDOWN);
}
break;
case EXT4_GOING_FLAGS_NOLOGFLUSH:
set_bit(EXT4_FLAGS_SHUTDOWN, &sbi->s_ext4_flags);
@@ -1650,6 +1656,9 @@ static long __ext4_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
if (!(fd_file(donor)->f_mode & FMODE_WRITE))
return -EBADF;
if (file_inode(filp)->i_sb != file_inode(fd_file(donor))->i_sb)
return -EXDEV;
err = mnt_want_write_file(filp);
if (err)
return err;

View File

@@ -727,8 +727,7 @@ do_test_generate_buddy(struct kunit *test, struct super_block *sb, void *bitmap,
ext4_mb_generate_buddy_test(sb, ext4_buddy, bitmap, TEST_GOAL_GROUP,
ext4_grp);
KUNIT_ASSERT_EQ(test, memcmp(mbt_buddy, ext4_buddy, sb->s_blocksize),
0);
KUNIT_ASSERT_MEMEQ(test, mbt_buddy, ext4_buddy, sb->s_blocksize);
mbt_validate_group_info(test, mbt_grp, ext4_grp);
}
@@ -789,8 +788,7 @@ test_mb_mark_used_range(struct kunit *test, struct ext4_buddy *e4b,
grp->bb_counters[i] = 0;
ext4_mb_generate_buddy_test(sb, buddy, bitmap, 0, grp);
KUNIT_ASSERT_EQ(test, memcmp(buddy, e4b->bd_buddy, sb->s_blocksize),
0);
KUNIT_ASSERT_MEMEQ(test, buddy, e4b->bd_buddy, sb->s_blocksize);
mbt_validate_group_info(test, grp, e4b->bd_info);
}
@@ -854,8 +852,7 @@ test_mb_free_blocks_range(struct kunit *test, struct ext4_buddy *e4b,
grp->bb_counters[i] = 0;
ext4_mb_generate_buddy_test(sb, buddy, bitmap, 0, grp);
KUNIT_ASSERT_EQ(test, memcmp(buddy, e4b->bd_buddy, sb->s_blocksize),
0);
KUNIT_ASSERT_MEMEQ(test, buddy, e4b->bd_buddy, sb->s_blocksize);
mbt_validate_group_info(test, grp, e4b->bd_info);
}

View File

@@ -145,9 +145,9 @@ static struct buffer_head *__ext4_read_dirblock(struct inode *inode,
if (IS_ERR(bh)) {
__ext4_warning(inode->i_sb, func, line,
"inode #%llu: lblock %lu: comm %s: "
"error %ld reading directory block",
"error %pe reading directory block",
inode->i_ino, (unsigned long)block,
current->comm, PTR_ERR(bh));
current->comm, bh);
return bh;
}
@@ -3054,7 +3054,7 @@ static struct dentry *ext4_mkdir(struct mnt_idmap *idmap, struct inode *dir,
out_retry:
if (err == -ENOSPC && ext4_should_retry_alloc(dir->i_sb, &retries))
goto retry;
return ERR_PTR(err);
return err ? ERR_PTR(err) : NULL;
}
/*

View File

@@ -168,7 +168,7 @@ static void ext4_release_io_end(ext4_io_end_t *io_end)
* written. On IO failure, check if journal abort is needed. Note that
* we are protected from truncate touching same part of extent tree by the
* fact that truncate code waits for all DIO to finish (thus exclusion from
* direct IO is achieved) and also waits for PageWriteback bits. Thus we
* direct IO is achieved) and also waits for writeback to complete. Thus we
* cannot get to ext4_ext_truncate() before all IOs overlapping that range are
* completed (happens from ext4_free_ioend()).
*/

View File

@@ -1431,6 +1431,9 @@ static struct inode *ext4_alloc_inode(struct super_block *sb)
ext4_fc_init_inode(&ei->vfs_inode);
spin_lock_init(&ei->i_fc_lock);
mmb_init(&ei->i_metadata_bhs, &ei->vfs_inode.i_data);
#ifdef CONFIG_LOCKDEP
lockdep_set_subclass(&ei->i_data_sem, I_DATA_SEM_NORMAL);
#endif
return &ei->vfs_inode;
}
@@ -4541,6 +4544,7 @@ static void ext4_fast_commit_init(struct super_block *sb)
sbi->s_fc_ineligible_tid = 0;
mutex_init(&sbi->s_fc_lock);
memset(&sbi->s_fc_stats, 0, sizeof(sbi->s_fc_stats));
memset(&sbi->s_fc_snap_stats, 0, sizeof(sbi->s_fc_snap_stats));
sbi->s_fc_replay_state.fc_regions = NULL;
sbi->s_fc_replay_state.fc_regions_size = 0;
sbi->s_fc_replay_state.fc_regions_used = 0;
@@ -5910,6 +5914,11 @@ static struct inode *ext4_get_journal_inode(struct super_block *sb,
return ERR_PTR(-EFSCORRUPTED);
}
#ifdef CONFIG_LOCKDEP
lockdep_set_subclass(&EXT4_I(journal_inode)->i_data_sem,
I_DATA_SEM_JOURNAL);
#endif
ext4_debug("Journal inode found at %p: %lld bytes\n",
journal_inode, journal_inode->i_size);
return journal_inode;
@@ -5977,8 +5986,8 @@ static struct file *ext4_get_journal_blkdev(struct super_block *sb,
sb, &fs_holder_ops);
if (IS_ERR(bdev_file)) {
ext4_msg(sb, KERN_ERR,
"failed to open journal device unknown-block(%u,%u) %ld",
MAJOR(j_dev), MINOR(j_dev), PTR_ERR(bdev_file));
"failed to open journal device unknown-block(%u,%u) %pe",
MAJOR(j_dev), MINOR(j_dev), bdev_file);
return bdev_file;
}

View File

@@ -512,10 +512,8 @@ void jbd2_journal_commit_transaction(journal_t *journal)
* leave undo-committed data.
*/
if (jh->b_committed_data) {
struct buffer_head *bh = jh2bh(jh);
spin_lock(&jh->b_state_lock);
jbd2_free(jh->b_committed_data, bh->b_size);
kfree(jh->b_committed_data);
jh->b_committed_data = NULL;
spin_unlock(&jh->b_state_lock);
}
@@ -976,7 +974,7 @@ void jbd2_journal_commit_transaction(journal_t *journal)
* its triggers if they exist, so we can clear that too.
*/
if (jh->b_committed_data) {
jbd2_free(jh->b_committed_data, bh->b_size);
kfree(jh->b_committed_data);
jh->b_committed_data = NULL;
if (jh->b_frozen_data) {
jh->b_committed_data = jh->b_frozen_data;
@@ -984,7 +982,7 @@ void jbd2_journal_commit_transaction(journal_t *journal)
jh->b_frozen_triggers = NULL;
}
} else if (jh->b_frozen_data) {
jbd2_free(jh->b_frozen_data, bh->b_size);
kfree(jh->b_frozen_data);
jh->b_frozen_data = NULL;
jh->b_frozen_triggers = NULL;
}

View File

@@ -95,8 +95,6 @@ EXPORT_SYMBOL(jbd2_journal_release_jbd_inode);
EXPORT_SYMBOL(jbd2_journal_begin_ordered_truncate);
EXPORT_SYMBOL(jbd2_inode_cache);
static int jbd2_journal_create_slab(size_t slab_size);
#ifdef CONFIG_JBD2_DEBUG
void __jbd2_debug(int level, const char *file, const char *func,
unsigned int line, const char *fmt, ...)
@@ -385,10 +383,10 @@ int jbd2_journal_write_metadata_buffer(transaction_t *transaction,
goto escape_done;
spin_unlock(&jh_in->b_state_lock);
tmp = jbd2_alloc(bh_in->b_size, GFP_NOFS | __GFP_NOFAIL);
tmp = kmalloc(bh_in->b_size, GFP_NOFS | __GFP_NOFAIL);
spin_lock(&jh_in->b_state_lock);
if (jh_in->b_frozen_data) {
jbd2_free(tmp, bh_in->b_size);
kfree(tmp);
goto copy_done;
}
@@ -2062,14 +2060,6 @@ EXPORT_SYMBOL(jbd2_journal_update_sb_errno);
int jbd2_journal_load(journal_t *journal)
{
int err;
journal_superblock_t *sb = journal->j_superblock;
/*
* Create a slab for this blocksize
*/
err = jbd2_journal_create_slab(be32_to_cpu(sb->s_blocksize));
if (err)
return err;
/* Let the recovery code check whether it needs to recover any
* data from the journal. */
@@ -2261,6 +2251,8 @@ jbd2_journal_initialize_fast_commit(journal_t *journal)
unsigned long long num_fc_blks;
num_fc_blks = jbd2_journal_get_num_fc_blks(sb);
if (num_fc_blks > journal->j_last)
return -EFSCORRUPTED;
if (journal->j_last - num_fc_blks < JBD2_MIN_JOURNAL_BLOCKS)
return -ENOSPC;
@@ -2697,105 +2689,6 @@ size_t journal_tag_bytes(journal_t *journal)
return sz - sizeof(__u32);
}
/*
* JBD memory management
*
* These functions are used to allocate block-sized chunks of memory
* used for making copies of buffer_head data. Very often it will be
* page-sized chunks of data, but sometimes it will be in
* sub-page-size chunks. (For example, 16k pages on Power systems
* with a 4k block file system.) For blocks smaller than a page, we
* use a SLAB allocator. There are slab caches for each block size,
* which are allocated at mount time, if necessary, and we only free
* (all of) the slab caches when/if the jbd2 module is unloaded. For
* this reason we don't need to a mutex to protect access to
* jbd2_slab[] allocating or releasing memory; only in
* jbd2_journal_create_slab().
*/
#define JBD2_MAX_SLABS 8
static struct kmem_cache *jbd2_slab[JBD2_MAX_SLABS];
static const char *jbd2_slab_names[JBD2_MAX_SLABS] = {
"jbd2_1k", "jbd2_2k", "jbd2_4k", "jbd2_8k",
"jbd2_16k", "jbd2_32k", "jbd2_64k", "jbd2_128k"
};
static void jbd2_journal_destroy_slabs(void)
{
int i;
for (i = 0; i < JBD2_MAX_SLABS; i++) {
kmem_cache_destroy(jbd2_slab[i]);
jbd2_slab[i] = NULL;
}
}
static int jbd2_journal_create_slab(size_t size)
{
static DEFINE_MUTEX(jbd2_slab_create_mutex);
int i = order_base_2(size) - 10;
size_t slab_size;
if (size == PAGE_SIZE)
return 0;
if (i >= JBD2_MAX_SLABS)
return -EINVAL;
if (unlikely(i < 0))
i = 0;
mutex_lock(&jbd2_slab_create_mutex);
if (jbd2_slab[i]) {
mutex_unlock(&jbd2_slab_create_mutex);
return 0; /* Already created */
}
slab_size = 1 << (i+10);
jbd2_slab[i] = kmem_cache_create(jbd2_slab_names[i], slab_size,
slab_size, 0, NULL);
mutex_unlock(&jbd2_slab_create_mutex);
if (!jbd2_slab[i]) {
printk(KERN_EMERG "JBD2: no memory for jbd2_slab cache\n");
return -ENOMEM;
}
return 0;
}
static struct kmem_cache *get_slab(size_t size)
{
int i = order_base_2(size) - 10;
BUG_ON(i >= JBD2_MAX_SLABS);
if (unlikely(i < 0))
i = 0;
BUG_ON(jbd2_slab[i] == NULL);
return jbd2_slab[i];
}
void *jbd2_alloc(size_t size, gfp_t flags)
{
void *ptr;
BUG_ON(size & (size-1)); /* Must be a power of 2 */
if (size < PAGE_SIZE)
ptr = kmem_cache_alloc(get_slab(size), flags);
else
ptr = kmalloc(size, flags);
/* Check alignment; SLUB has gotten this wrong in the past,
* and this can lead to user data corruption! */
BUG_ON(((unsigned long) ptr) & (size-1));
return ptr;
}
void jbd2_free(void *ptr, size_t size)
{
kfree(ptr);
};
/*
* Journal_head storage management
*/
@@ -2969,15 +2862,15 @@ static void __journal_remove_journal_head(struct buffer_head *bh)
clear_buffer_jbd(bh);
}
static void journal_release_journal_head(struct journal_head *jh, size_t b_size)
static void journal_release_journal_head(struct journal_head *jh)
{
if (jh->b_frozen_data) {
printk(KERN_WARNING "%s: freeing b_frozen_data\n", __func__);
jbd2_free(jh->b_frozen_data, b_size);
kfree(jh->b_frozen_data);
}
if (jh->b_committed_data) {
printk(KERN_WARNING "%s: freeing b_committed_data\n", __func__);
jbd2_free(jh->b_committed_data, b_size);
kfree(jh->b_committed_data);
}
journal_free_journal_head(jh);
}
@@ -2996,7 +2889,7 @@ void jbd2_journal_put_journal_head(struct journal_head *jh)
if (!jh->b_jcount) {
__journal_remove_journal_head(bh);
jbd_unlock_bh_journal_head(bh);
journal_release_journal_head(jh, bh->b_size);
journal_release_journal_head(jh);
__brelse(bh);
} else {
jbd_unlock_bh_journal_head(bh);
@@ -3138,7 +3031,6 @@ static void jbd2_journal_destroy_caches(void)
jbd2_journal_destroy_handle_cache();
jbd2_journal_destroy_inode_cache();
jbd2_journal_destroy_transaction_cache();
jbd2_journal_destroy_slabs();
}
static int __init journal_init(void)

View File

@@ -1131,7 +1131,7 @@ do_get_write_access(handle_t *handle, struct journal_head *jh,
if (!frozen_buffer) {
JBUFFER_TRACE(jh, "allocate memory for buffer");
spin_unlock(&jh->b_state_lock);
frozen_buffer = jbd2_alloc(jh2bh(jh)->b_size,
frozen_buffer = kmalloc(jh2bh(jh)->b_size,
GFP_NOFS | __GFP_NOFAIL);
goto repeat;
}
@@ -1159,7 +1159,7 @@ do_get_write_access(handle_t *handle, struct journal_head *jh,
out:
if (unlikely(frozen_buffer)) /* It's usually NULL */
jbd2_free(frozen_buffer, bh->b_size);
kfree(frozen_buffer);
JBUFFER_TRACE(jh, "exit");
return error;
@@ -1424,7 +1424,7 @@ int jbd2_journal_get_undo_access(handle_t *handle, struct buffer_head *bh)
repeat:
if (!jh->b_committed_data)
committed_data = jbd2_alloc(jh2bh(jh)->b_size,
committed_data = kmalloc(jh2bh(jh)->b_size,
GFP_NOFS|__GFP_NOFAIL);
spin_lock(&jh->b_state_lock);
@@ -1445,7 +1445,7 @@ int jbd2_journal_get_undo_access(handle_t *handle, struct buffer_head *bh)
out:
jbd2_journal_put_journal_head(jh);
if (unlikely(committed_data))
jbd2_free(committed_data, bh->b_size);
kfree(committed_data);
return err;
}
@@ -1516,14 +1516,19 @@ void jbd2_buffer_abort_trigger(struct journal_head *jh,
*/
int jbd2_journal_dirty_metadata(handle_t *handle, struct buffer_head *bh)
{
transaction_t *transaction = handle->h_transaction;
journal_t *journal = transaction->t_journal;
transaction_t *transaction;
journal_t *journal;
struct journal_head *jh;
int ret = 0;
if (is_handle_aborted(handle))
return -EROFS;
if (!buffer_jbd(bh))
return -EUCLEAN;
transaction = handle->h_transaction;
journal = transaction->t_journal;
/*
* We don't grab jh reference here since the buffer must be part
* of the running transaction.

View File

@@ -63,9 +63,6 @@ void __jbd2_debug(int level, const char *file, const char *func,
#define jbd2_debug(n, fmt, a...) no_printk(fmt, ##a)
#endif
extern void *jbd2_alloc(size_t size, gfp_t flags);
extern void jbd2_free(void *ptr, size_t size);
#define JBD2_MIN_JOURNAL_BLOCKS 1024
#define JBD2_DEFAULT_FAST_COMMIT_BLOCKS 256

View File

@@ -107,6 +107,26 @@ TRACE_DEFINE_ENUM(EXT4_FC_REASON_VERITY);
TRACE_DEFINE_ENUM(EXT4_FC_REASON_MOVE_EXT);
TRACE_DEFINE_ENUM(EXT4_FC_REASON_MAX);
#undef EM
#undef EMe
#define EM(a) TRACE_DEFINE_ENUM(EXT4_FC_SNAP_ERR_##a);
#define EMe(a) TRACE_DEFINE_ENUM(EXT4_FC_SNAP_ERR_##a);
#define TRACE_SNAP_ERR \
EM(NONE) \
EM(ES_MISS) \
EM(ES_DELAYED) \
EM(ES_OTHER) \
EM(INODES_CAP) \
EM(RANGES_CAP) \
EM(NOMEM) \
EMe(INODE_LOC)
TRACE_SNAP_ERR
#undef EM
#undef EMe
#define show_fc_reason(reason) \
__print_symbolic(reason, \
{ EXT4_FC_REASON_XATTR, "XATTR"}, \
@@ -2818,6 +2838,47 @@ TRACE_EVENT(ext4_fc_commit_stop,
__entry->num_fc_ineligible, __entry->nblks_agg, __entry->tid)
);
#define EM(a) { EXT4_FC_SNAP_ERR_##a, #a },
#define EMe(a) { EXT4_FC_SNAP_ERR_##a, #a }
TRACE_EVENT(ext4_fc_lock_updates,
TP_PROTO(struct super_block *sb, tid_t commit_tid, u64 locked_ns,
unsigned int nr_inodes, unsigned int nr_ranges, int err,
int snap_err),
TP_ARGS(sb, commit_tid, locked_ns, nr_inodes, nr_ranges, err, snap_err),
TP_STRUCT__entry(/* entry */
__field(dev_t, dev)
__field(tid_t, tid)
__field(u64, locked_ns)
__field(unsigned int, nr_inodes)
__field(unsigned int, nr_ranges)
__field(int, err)
__field(int, snap_err)
),
TP_fast_assign(/* assign */
__entry->dev = sb->s_dev;
__entry->tid = commit_tid;
__entry->locked_ns = locked_ns;
__entry->nr_inodes = nr_inodes;
__entry->nr_ranges = nr_ranges;
__entry->err = err;
__entry->snap_err = snap_err;
),
TP_printk("dev %d,%d tid %u locked_ns %llu nr_inodes %u nr_ranges %u err %d snap_err %s",
MAJOR(__entry->dev), MINOR(__entry->dev), __entry->tid,
__entry->locked_ns, __entry->nr_inodes, __entry->nr_ranges,
__entry->err, __print_symbolic(__entry->snap_err,
TRACE_SNAP_ERR))
);
#undef EM
#undef EMe
#undef TRACE_SNAP_ERR
#define FC_REASON_NAME_STAT(reason) \
show_fc_reason(reason), \
__entry->fc_ineligible_rc[reason]