[PATCH v9 00/11] Data in direntry (dirdata) feature

Artem Blagodarenko <[email protected]> Thu, 30 Jul 2026 20:54:17 -0400
Newsgroups org.kernel.vger.linux-ext4
Message-ID <[email protected]>
EXT4 currently stores an 8-byte hash in the directory entry immediately
after the file name to support simultaneous fscrypt and casefold
functionality.

The dirdata feature provides a general mechanism to store multiple
metadata records in each directory entry after the NUL filename
terminator. The unused high 4 bits of 'file_type' indicate which
records are present; each record starts with a 1-byte length field,
providing forward compatibility.

The first user of this feature is LUFID (Locally Unique File ID), an
identifier that must survive rename and be accessible from directory
readdir without an inode lookup.

e2fsprogs support is provided in a separate patch series.

Thanks to Sashiko AI review for identifying several correctness and
safety issues in earlier versions of this series.

Changes in v9:

Patch 05 (dirdata format definitions): remove unused ext4_dirdata_next()
helper. It was added alongside ext4_dirent_get_data_len() but never
called; the loop in ext4_dirent_get_data_len() advances the pointer
directly. Dead code that reads a disk field without READ_ONCE() is a
latent TOCTOU hazard.

Patch 08 (dirdata feature): fix ext4_readdir() to use ext4_dirdata_get()
instead of the fixed-offset EXT4_DIRENT_HASH() macro when reading the
casefold hash on a dirdata-enabled filesystem. The fixed-offset macro
reads LUFID bytes as hash data on entries that carry a LUFID extension
before CFHASH.

Patch 08 (dirdata feature): fix get_dx_countlimit() to decode rec_len
via ext4_rec_len_from_disk() before comparing it against the blocksize.
The v8 code compared the encoded on-disk value directly, which can
differ from the actual byte length on filesystems with large block sizes.

Patch 09 (LUFID rename support): revise the size-mismatch handling in
ext4_setent(). The v8 approach — leaving LUFID bytes and flags in place
— preserved the CFHASH offset but kept the entry advertising a stale
LUFID from the old inode, and also left the CFHASH unreachable when
there was no subsequent SET_LUFID call. The v9 approach introduces
ext4_setent_compact_exts(), which slides the CFHASH bytes into the slot
vacated by LUFID and then clears the LUFID flag. If extension headers
are malformed, CFHASH is cleared instead of risking a corrupt read.
Apply the same fix to the no-src-fid clearing path, which had the same
CFHASH-stranding bug when renaming a non-LUFID file over a LUFID+CFHASH
entry.

Patch 10 (dirdata set/get helpers): fix ext4_match() to pass buf_size
(the inline directory buffer size) to ext4_dirdata_get() instead of
sizeof(struct ext4_dir_entry_2). The old value was too small for any
entry with a non-empty name, causing ext4_dirent_get_data_len() to
reject every extension as out-of-bounds and always fall through to the
legacy fixed-offset hash path, silently missing LUFID entries.

Patch 11 (SET_LUFID ioctl): fix dx_get_dx_info() bounds check for the
'..' entry. Replace the absolute rlen > blocksize check with a
position-relative check, (de_buf - base) + rlen > blocksize. The
absolute check failed to catch a '..' entry whose rec_len would extend
past the end of the block given its actual position within the block.

Patch 11 (SET_LUFID ioctl): add IS_IMMUTABLE / IS_APPEND checks on the
target inode in ext4_ioctl_set_lufid(). The v8 code relied on
inode_permission() alone, which does not cover those VFS flags.


Artem Blagodarenko (11):
  ext4: validate count against limit in ext4_dx_csum_verify/_set
  ext4: replace ext4_dir_entry with ext4_dir_entry_2
  ext4: add ext4_dir_entry_is_tail()
  ext4: refactor dx_root to support variable dirent sizes
  ext4: add dirdata format definitions and access helpers
  ext4: add ext4_dir_entry_len()
  ext4: rename ext4_dir_rec_len() and clarify dirdata usage
  ext4: dirdata feature
  ext4: add dirdata LUFID support for directory entry rename
  ext4: add dirdata set/get helpers
  ext4: Add EXT4_IOC_SET_LUFID ioctl for setting LUFID on directory
    entries

 fs/ext4/dir.c             |   49 +-
 fs/ext4/ext4.h            |  230 ++++++-
 fs/ext4/fast_commit.c     |    1 +
 fs/ext4/fast_commit.h     |    1 +
 fs/ext4/inline.c          |   67 ++-
 fs/ext4/ioctl.c           |   85 +++
 fs/ext4/namei.c           | 1187 +++++++++++++++++++++++++++++++------
 fs/ext4/sysfs.c           |    2 +
 include/uapi/linux/ext4.h |   14 +
 9 files changed, 1392 insertions(+), 244 deletions(-)

-- 
2.43.7