[RFC PATCH v4 11/11] ext4/069: test LUFID in inline_data directory entries
Artem Blagodarenko <[email protected]> Mon, 27 Jul 2026 17:13:51 -0400
| Newsgroups | org.kernel.vger.linux-ext4 |
|---|---|
| Message-ID | <[email protected]> |
From: Lustre Development <[email protected]> Verify that a LUFID set via EXT4_IOC_SET_LUFID on an entry in an inline_data directory is correctly stored on disk. The test formats a filesystem with -O dirdata,inline_data, creates a directory with two entries (small enough to remain inline), sets a LUFID on one entry, and confirms the fid= field is present in the raw directory data via debugfs. The require guard probes the combined dirdata+inline_data mount so that a kernel that accepts each feature individually but rejects the combination emits [not run] rather than FAIL. Signed-off-by: Artem Blagodarenko <[email protected]> --- tests/ext4/069 | 164 +++++++++++++++++++++++++++++++++++++++++++++ tests/ext4/069.out | 7 ++ 2 files changed, 171 insertions(+) diff --git a/tests/ext4/069 b/tests/ext4/069 new file mode 100755 index 00000000..b8b3e459 --- /dev/null +++ b/tests/ext4/069 @@ -0,0 +1,164 @@ +#! /bin/bash +# SPDX-License-Identifier: GPL-2.0 +# Copyright (c) 2026 The Lustre Collective. All Rights Reserved. +# Author: Artem Blagodarenko <[email protected]> +# +# FS QA Test ext4/069 +# +# Regression test for inline directory + dirdata LUFID loss. +# +# ext4_add_dirent_to_inline() was passing NULL instead of dfid to +# ext4_insert_dentry_data(), silently dropping the LUFID for every entry +# created while the directory fits in the inode's inline-data area. +# +# The test creates a directory small enough to stay inline, sets a LUFID +# on one of its entries, and verifies the LUFID is present on disk via +# debugfs. Without the fix, debugfs reports no fid= field and the test +# fails. + +. ./common/preamble +_begin_fstest auto quick + +. ./common/filter +. ./common/ext4 + +_exclude_fs ext2 +_exclude_fs ext3 + +_require_scratch_nocheck +_require_command "$SET_LUFID_PROG" + +_require_scratch_dirdata() +{ + if test ! -f /sys/fs/ext4/features/dirdata ; then + _notrun "dirdata feature not supported by kernel" + fi + if ! $MKFS_EXT4_PROG -O dirdata -n $SCRATCH_DEV &>>$seqres.full ; then + _notrun "mkfs.ext4 does not support dirdata feature" + fi + if ! _scratch_mkfs -O dirdata,inline_data &>>$seqres.full ; then + _notrun "failed to create filesystem with dirdata+inline_data" + fi + if ! _try_scratch_mount &>>$seqres.full ; then + _notrun "kernel cannot mount filesystem with dirdata+inline_data" + fi + _scratch_unmount +} + +_require_scratch_ext4_feature "inline_data" +_require_scratch_dirdata + +# ---- helpers ---------------------------------------------------------------- + +_u32_to_le_hex() +{ + local v=$1 + local h + h=$(printf '%08x' "$((v & 0xffffffff))") + printf '%s%s%s%s' "${h:6:2}" "${h:4:2}" "${h:2:2}" "${h:0:2}" +} + +_build_default_expected_fid() +{ + local path=$1 + local inode version ino_lo ver_lo ver_hi seq_hex oid_hex ver_hex + + inode=$(stat -c '%i' "$path") || return 1 + version=$(debugfs -R "stat <${inode}>" $SCRATCH_DEV 2>/dev/null | \ + sed -n 's/.*Generation:[[:space:]]*\([0-9xa-fA-F]\+\).*/\1/p' | head -1) + [ -z "$version" ] && return 1 + + ino_lo=$((inode & 0xffffffff)) + ver_lo=$((version & 0xffffffff)) + ver_hi=$(((version >> 32) & 0xffffffff)) + + # ext4 inode numbers are 32-bit so f_seq fits in 8 hex chars; avoid a + # 16-char $((16#...)) that would overflow for values >= 0x8000000000. + seq_hex="$(_u32_to_le_hex "$ino_lo")" + oid_hex="$(_u32_to_le_hex "$ver_lo")" + ver_hex="$(_u32_to_le_hex "$ver_hi")" + + printf '[0x%x:0x%x:0x%x]' "$((16#$seq_hex))" "$((16#$oid_hex))" \ + "$((16#$ver_hex))" +} + +# Confirm the directory inode carries the Inline_Data flag. +_check_inline() +{ + local dir=$1 + local inode flags + + inode=$(stat -c '%i' "$dir") + flags=$(debugfs -R "stat <${inode}>" $SCRATCH_DEV 2>/dev/null | \ + sed -n 's/^.*Flags: \([0-9a-fx]*\).*$/\1/p') + # EXT4_INLINE_DATA_FL = 0x10000000 + if (( (16#${flags#0x}) & 0x10000000 )); then + return 0 + fi + return 1 +} + +# ---- test ------------------------------------------------------------------- + +echo "== Create dirdata + inline_data filesystem ==" +_scratch_mkfs -O dirdata,inline_data &>>$seqres.full +_scratch_mount + +TESTDIR=$SCRATCH_MNT/inline_dir +mkdir "$TESTDIR" + +# Two entries keep the directory comfortably within the inline-data area +# (~160 bytes available in a 256-byte inode) so ext4 does not convert it +# to an on-disk block before we set the LUFID. +echo "content" > "$TESTDIR/file_a" +echo "content" > "$TESTDIR/file_b" + +sync + +echo "== Verify directory is inline ==" +if ! _check_inline "$TESTDIR"; then + echo "SKIP: directory was not kept inline (inode too small or inline_data inactive)" + _scratch_unmount + status=0 + exit +fi +echo " inline_dir: inline data confirmed" + +echo "== Set LUFID on file_a (inline directory entry) ==" +$SET_LUFID_PROG "$SCRATCH_MNT/inline_dir" file_a >>$seqres.full +if [ $? -ne 0 ]; then + echo "FAIL: set_lufid returned error" + _scratch_unmount + _check_scratch_fs + status=1 + exit +fi + +expected=$(_build_default_expected_fid "$TESTDIR/file_a") +if [ -z "$expected" ]; then + echo "FAIL: could not compute expected LUFID" + _scratch_unmount + _check_scratch_fs + status=1 + exit +fi + +sync + +echo "== Verify LUFID stored in inline directory entry ==" +# _dump_dir_structure reads from the raw block device via debugfs and +# reports the fid= field written by ext4_dirdata_set(). Before the fix, +# ext4_add_dirent_to_inline() passed NULL instead of dfid, so no fid= +# field was written and this check would fail. +if ! _dump_dir_structure "$TESTDIR" file_a "$expected"; then + _scratch_unmount + _check_scratch_fs + status=1 + exit +fi + +_scratch_unmount +_check_scratch_fs + +status=0 +exit diff --git a/tests/ext4/069.out b/tests/ext4/069.out new file mode 100644 index 00000000..2c8c4d9f --- /dev/null +++ b/tests/ext4/069.out @@ -0,0 +1,7 @@ +QA output created by 069 +== Create dirdata + inline_data filesystem == +== Verify directory is inline == + inline_dir: inline data confirmed +== Set LUFID on file_a (inline directory entry) == +== Verify LUFID stored in inline directory entry == + Directory structure of inline_dir: OK (dirdata verified) -- 2.43.7