[PATCH v12 06/11] ext4: add ext4_dir_entry_len()
Artem Blagodarenko <[email protected]> Sat, 1 Aug 2026 14:12:20 -0400
| Newsgroups | org.kernel.vger.linux-ext4 |
|---|---|
| Message-ID | <[email protected]> |
From: Artem Blagodarenko <[email protected]> Introduce ext4_dir_entry_len() helper to compute the required rec_len for a directory entry, taking into account dirdata and casefold+fscrypt hash space. Update dx_root_limit() to use ext4_dir_entry_len() instead of open-coded ext4_dir_rec_len() for '.' and '..' entries. Signed-off-by: Artem Blagodarenko <[email protected]> Reviewed-by: Andreas Dilger <[email protected]> --- fs/ext4/ext4.h | 44 ++++++++++++++++++++++++++++++++++++++++---- fs/ext4/namei.c | 20 ++++++++++++-------- 2 files changed, 52 insertions(+), 12 deletions(-) diff --git a/fs/ext4/ext4.h b/fs/ext4/ext4.h index edeb63407c3f..68eaefa93085 100644 --- a/fs/ext4/ext4.h +++ b/fs/ext4/ext4.h @@ -2620,7 +2620,15 @@ static inline unsigned int ext4_dir_rec_len(__u8 name_len, { int rec_len = (name_len + 8 + EXT4_DIR_ROUND); - if (dir && ext4_hash_in_dirent(dir)) + /* + * Without dirdata, the casefold+fscrypt hash lives at a fixed position + * after the filename and must be reserved explicitly. With dirdata the + * hash is stored as a CFHASH extension and is already counted in the + * name_len argument (via ext4_dirent_get_data_len), so adding it again + * would double-count. + */ + if (dir && ext4_hash_in_dirent(dir) && + !ext4_has_feature_dirdata(dir->i_sb)) rec_len += sizeof(struct ext4_dir_entry_hash); return (rec_len & ~EXT4_DIR_ROUND); } @@ -3044,12 +3052,14 @@ static const unsigned char ext4_filetype_table[] = { DT_UNKNOWN, DT_REG, DT_DIR, DT_CHR, DT_BLK, DT_FIFO, DT_SOCK, DT_LNK }; -static inline unsigned char get_dtype(struct super_block *sb, int filetype) +static inline unsigned char get_dtype(struct super_block *sb, int filetype) { - if (!ext4_has_feature_filetype(sb) || filetype >= EXT4_FT_MAX) + unsigned char fl_index = filetype & EXT4_FT_MASK; + + if (!ext4_has_feature_filetype(sb) || fl_index >= EXT4_FT_MAX) return DT_UNKNOWN; - return ext4_filetype_table[filetype]; + return ext4_filetype_table[fl_index]; } extern int ext4_check_all_de(struct inode *dir, struct buffer_head *bh, void *buf, int buf_size); @@ -4135,6 +4145,32 @@ static inline int ext4_dirent_get_data_len(struct ext4_dir_entry_2 *de, return dlen; } +/* + * ext4_dir_entry_len() - Compute the required rec_len for a directory entry. + * @de: directory entry (used to read name_len and any dirdata length) + * @blocksize: size of the buffer @de lives in (the real directory block + * size, or the smaller inline-data buffer size for inline + * directories) -- used only to decode @de->rec_len's "0/65535 + * means rest of buffer" sentinel correctly. + * @dir: directory inode (may be NULL for '.' and '..' entries, which + * never carry the casefold+fscrypt hash regardless of the + * directory's feature flags) + * + * Returns the minimum record length needed to hold @de, rounded up to the + * directory alignment and including room for the casefold+fscrypt hash if + * the directory requires it. + */ +static inline unsigned int ext4_dir_entry_len(struct ext4_dir_entry_2 *de, + unsigned int blocksize, + const struct inode *dir) +{ + unsigned int rec_len = ext4_rec_len_from_disk(de->rec_len, blocksize); + unsigned int dirdata = ext4_dirent_get_data_len(de, rec_len); + + return ext4_dir_rec_len(de->name_len + dirdata, dir); +} + + extern const struct iomap_ops ext4_iomap_ops; extern const struct iomap_ops ext4_iomap_report_ops; diff --git a/fs/ext4/namei.c b/fs/ext4/namei.c index 3eefd10133d7..f98e05f9dcf9 100644 --- a/fs/ext4/namei.c +++ b/fs/ext4/namei.c @@ -596,16 +596,17 @@ static inline void dx_set_limit(struct dx_entry *entries, unsigned value) ((struct dx_countlimit *) entries)->limit = cpu_to_le16(value); } -static inline unsigned dx_root_limit(struct inode *dir, unsigned infosize) +static inline unsigned dx_root_limit(struct inode *dir, + struct ext4_dir_entry_2 *dot_de) { - unsigned int entry_space = dir->i_sb->s_blocksize - - ext4_dir_rec_len(1, NULL) - - ext4_dir_rec_len(2, NULL) - infosize; struct dx_root_info *info; + unsigned int entry_space; info = dx_get_dx_info(dir, dot_de); if (IS_ERR(info)) return 0; + entry_space = dir->i_sb->s_blocksize - ((char *)info - (char *)dot_de) - + info->info_length; if (ext4_has_feature_metadata_csum(dir->i_sb)) entry_space -= sizeof(struct dx_tail); @@ -890,10 +891,13 @@ dx_probe(struct ext4_filename *fname, struct inode *dir, entries = (struct dx_entry *)(((char *)info) + info->info_length); - if (dx_get_limit(entries) != dx_root_limit(dir, info->info_length)) { + if (dx_get_limit(entries) != + dx_root_limit(dir, (struct ext4_dir_entry_2 *)frame->bh->b_data)) { ext4_warning_inode(dir, "dx entry: limit %u != root limit %u", dx_get_limit(entries), - dx_root_limit(dir, info->info_length)); + dx_root_limit(dir, + (struct ext4_dir_entry_2 *)frame->bh->b_data + )); goto fail; } @@ -2339,10 +2343,10 @@ static int make_indexed_dir(handle_t *handle, struct ext4_filename *fname, dx_info->hash_version = EXT4_SB(dir->i_sb)->s_def_hash_version; - entries = (void *)dx_info + sizeof(*dx_info); + entries = (void *)dx_info + dx_info->info_length; dx_set_block(entries, 1); dx_set_count(entries, 1); - dx_set_limit(entries, dx_root_limit(dir, sizeof(*dx_info))); + dx_set_limit(entries, dx_root_limit(dir, dot_de)); /* Initialize as for dx_probe */ fname->hinfo.hash_version = dx_info->hash_version; -- 2.43.7