AI Trajectory


Trajectory: (Expand/Collapse All)
Seq Timestamp Type Name Duration
0/0 2026/07/14 19:42 flow patch-triage
2m Args:
null

Results:
{
  "EnableConfigs": [
    "EXT4_FS"
  ],
  "FocusSymbols": [
    "ext4_ioctl_set_lufid",
    "ext4_dirdata_set_lufid",
    "ext4_dirdata_get",
    "ext4_dirdata_set",
    "ext4_lufid_snapshot",
    "ext4_insert_dentry_data",
    "ext4_init_new_dir_data",
    "dx_get_dx_info",
    "ext4_check_dx_root",
    "ext4_setent",
    "__ext4_add_entry"
  ],
  "Reasoning": "The patch introduces a new ext4 feature `dirdata` (EXT4_FEATURE_INCOMPAT_DIRDATA) and a new ioctl `EXT4_IOC_SET_LUFID` to attach LUFID data to directory entries. It modifies core directory entry parsing, insertion, splitting, and renaming logic to handle variable-length data appended to directory entries. This is a significant functional change to the ext4 filesystem that should be fuzzed.",
  "WorthFuzzing": true
}

1/1 2026/07/14 19:42 action read-patch-diff
0m Args:
null

Results:
{
  "PatchDiff": "commit b44158818cdbbf8e2281ab287f17d971a76bbfdd\nAuthor: syz-cluster \u003ctriage@syzkaller.com\u003e\nDate:   Tue Jul 14 19:42:05 2026 +0000\n\n    syz-cluster: applied patch under review\n\ndiff --git a/fs/ext4/dir.c b/fs/ext4/dir.c\nindex 17edd678fa87b8..50c48c50a29d88 100644\n--- a/fs/ext4/dir.c\n+++ b/fs/ext4/dir.c\n@@ -89,16 +89,15 @@ int __ext4_check_dir_entry(const char *function, unsigned int line,\n \tbool fake = is_fake_dir_entry(de);\n \tbool has_csum = ext4_has_feature_metadata_csum(dir-\u003ei_sb);\n \n-\tif (unlikely(rlen \u003c ext4_dir_rec_len(1, fake ? NULL : dir)))\n+\tif (unlikely(rlen \u003c ext4_dirent_rec_len(1, fake ? NULL : dir)))\n \t\terror_msg = \"rec_len is smaller than minimal\";\n \telse if (unlikely(rlen % 4 != 0))\n \t\terror_msg = \"rec_len % 4 != 0\";\n-\telse if (unlikely(rlen \u003c ext4_dir_rec_len(de-\u003ename_len,\n-\t\t\t\t\t\t\tfake ? NULL : dir)))\n-\t\terror_msg = \"rec_len is too small for name_len\";\n \telse if (unlikely(next_offset \u003e size))\n \t\terror_msg = \"directory entry overrun\";\n-\telse if (unlikely(next_offset \u003e size - ext4_dir_rec_len(1,\n+\telse if (unlikely(rlen \u003c ext4_dir_entry_len(de, size, fake ? NULL : dir)))\n+\t\terror_msg = \"rec_len is too small for name_len\";\n+\telse if (unlikely(next_offset \u003e size - ext4_dirent_rec_len(1,\n \t\t\t\t\t\t  has_csum ? NULL : dir) \u0026\u0026\n \t\t\t  next_offset != size))\n \t\terror_msg = \"directory entry too close to block end\";\n@@ -245,7 +244,7 @@ static int ext4_readdir(struct file *file, struct dir_context *ctx)\n \t\t\t\t * failure will be detected in the\n \t\t\t\t * dirent test below. */\n \t\t\t\tif (ext4_rec_len_from_disk(de-\u003erec_len,\n-\t\t\t\t\tsb-\u003es_blocksize) \u003c ext4_dir_rec_len(1,\n+\t\t\t\t\tsb-\u003es_blocksize) \u003c ext4_dirent_rec_len(1,\n \t\t\t\t\t\t\t\t\tinode))\n \t\t\t\t\tbreak;\n \t\t\t\ti += ext4_rec_len_from_disk(de-\u003erec_len,\n@@ -468,27 +467,43 @@ void ext4_htree_free_dir_info(struct dir_private_info *p)\n  * The decrypted filename is passed in via ent_name.  parameter.\n  */\n int ext4_htree_store_dirent(struct file *dir_file, __u32 hash,\n-\t\t\t     __u32 minor_hash,\n+\t\t\t     __u32 minor_hash, int buf_size,\n \t\t\t    struct ext4_dir_entry_2 *dirent,\n \t\t\t    struct fscrypt_str *ent_name)\n {\n \tstruct rb_node **p, *parent = NULL;\n \tstruct fname *fname, *new_fn;\n \tstruct dir_private_info *info;\n+\tint extra_data = 0;\n \n \tinfo = dir_file-\u003eprivate_data;\n \tp = \u0026info-\u003eroot.rb_node;\n \n \t/* Create and allocate the fname structure */\n-\tnew_fn = kzalloc_flex(*new_fn, name, ent_name-\u003elen + 1);\n+\tif (dirent-\u003efile_type \u0026 ~EXT4_FT_MASK) {\n+\t\tunsigned int rec_len =\n+\t\t\text4_rec_len_from_disk(dirent-\u003erec_len, buf_size);\n+\t\textra_data = ext4_dirent_get_data_len(dirent, rec_len);\n+\t}\n+\n+\t/* exta_data contains +1 byte for NULL */\n+\tnew_fn = kzalloc_flex(*new_fn, name, ent_name-\u003elen +\n+\t\t\t      (extra_data ? extra_data : 1));\n \tif (!new_fn)\n \t\treturn -ENOMEM;\n \tnew_fn-\u003ehash = hash;\n \tnew_fn-\u003eminor_hash = minor_hash;\n \tnew_fn-\u003einode = le32_to_cpu(dirent-\u003einode);\n-\tnew_fn-\u003ename_len = ent_name-\u003elen;\n \tnew_fn-\u003efile_type = dirent-\u003efile_type;\n+\tnew_fn-\u003ename_len = ent_name-\u003elen;\n \tmemcpy(new_fn-\u003ename, ent_name-\u003ename, ent_name-\u003elen);\n+\tif (extra_data)\n+\t\t/* Dirdata bytes lie past the __counted_by(name_len) bound but\n+\t\t * within the kzalloc_flex allocation; use unsafe_memcpy to\n+\t\t * avoid a FORTIFY_SOURCE false positive. */\n+\t\tunsafe_memcpy(new_fn-\u003ename + ent_name-\u003elen,\n+\t\t\t      dirent-\u003ename + dirent-\u003ename_len, extra_data,\n+\t\t\t      /* within kzalloc_flex allocation */);\n \n \twhile (*p) {\n \t\tparent = *p;\ndiff --git a/fs/ext4/ext4.h b/fs/ext4/ext4.h\nindex b37c136ea3ab33..15d885cf3bd025 100644\n--- a/fs/ext4/ext4.h\n+++ b/fs/ext4/ext4.h\n@@ -1227,6 +1227,7 @@ struct ext4_inode_info {\n #ifdef CONFIG_FS_ENCRYPTION\n \tstruct fscrypt_inode_info *i_crypt_info;\n #endif\n+\tvoid *i_dirdata;\n };\n \n /*\n@@ -2334,6 +2335,7 @@ EXT4_FEATURE_INCOMPAT_FUNCS(casefold,\t\tCASEFOLD)\n \t\t\t\t\t EXT4_FEATURE_INCOMPAT_FLEX_BG| \\\n \t\t\t\t\t EXT4_FEATURE_INCOMPAT_EA_INODE| \\\n \t\t\t\t\t EXT4_FEATURE_INCOMPAT_MMP | \\\n+\t\t\t\t\t EXT4_FEATURE_INCOMPAT_DIRDATA | \\\n \t\t\t\t\t EXT4_FEATURE_INCOMPAT_INLINE_DATA | \\\n \t\t\t\t\t EXT4_FEATURE_INCOMPAT_ENCRYPT | \\\n \t\t\t\t\t EXT4_FEATURE_INCOMPAT_CASEFOLD | \\\n@@ -2472,10 +2474,8 @@ static inline int ext4_emergency_state(struct super_block *sb)\n  * Structure of a directory entry\n  */\n #define EXT4_NAME_LEN 255\n-/*\n- * Base length of the ext4 directory entry excluding the name length\n- */\n-#define EXT4_BASE_DIR_LEN (sizeof(struct ext4_dir_entry_2) - EXT4_NAME_LEN)\n+/* offsetof avoids sizeof()+padding giving 9 instead of the correct on-disk 8 */\n+#define EXT4_BASE_DIR_LEN offsetof(struct ext4_dir_entry_2, name)\n \n struct ext4_dir_entry {\n \t__le32\tinode;\t\t\t/* Inode number */\n@@ -2556,6 +2556,61 @@ struct ext4_dir_entry_tail {\n #define EXT4_FT_SYMLINK\t\t7\n \n #define EXT4_FT_MAX\t\t8\n+#define EXT4_FT_MASK\t\t0xf\n+\n+#if EXT4_FT_MAX \u003e EXT4_FT_MASK\n+#error \"conflicting EXT4_FT_MAX and EXT4_FT_MASK\"\n+#endif\n+\n+/*\n+ * d_type has 4 unused bits, so it can hold four types of data. These different\n+ * types of data (e.g. fscypt hash, high 32 bits of 64-bit inode number) can be\n+ * stored, in flag order, after file-name in ext4 dirent.\n+ *\n+ * These flags are added to d_type if ext4 dirent has extra data after\n+ * filename. This data length is variable and length is stored in first byte\n+ * of data. Data starts after filename NUL byte.\n+ */\n+#define EXT4_DIRENT_LUFID\t\t0x10\n+#define EXT4_DIRENT_INO64\t\t0x20\n+#define EXT4_DIRENT_CFHASH\t\t0x40\n+\n+struct ext4_fid {\n+\tchar    fid[16];     /* 128-bit unique file identifier */\n+};\n+\n+struct ext4_dirent_data_header {\n+\t/* length of this header + the whole data blob */\n+\t__u8\tddh_length;\n+} __packed;\n+\n+struct ext4_dirent_fid {\n+\tstruct ext4_dirent_data_header df_header;\n+\tstruct ext4_fid                df_fid[];\n+};\n+\n+#define EXT4_LUFID_MAGIC    0xAD200907UL\n+struct ext4_dentry_param {\n+\t__u32\t\t\tedp_magic;\t/* EXT4_LUFID_MAGIC */\n+\tstruct ext4_dirent_fid\tedp_dfid;\n+};\n+\n+struct ext4_dirent_hash {\n+\tstruct ext4_dirent_data_header\tdh_header;\n+\tstruct ext4_dir_entry_hash\tdh_hash;\n+} __packed;\n+\n+static inline\n+struct ext4_dirent_fid *ext4_dentry_get_fid(struct super_block *sb,\n+\t\t\t\t\t    struct ext4_dentry_param *p)\n+{\n+\tif (!ext4_has_feature_dirdata(sb))\n+\t\treturn NULL;\n+\tif (p \u0026\u0026 p-\u003eedp_magic == EXT4_LUFID_MAGIC)\n+\t\treturn \u0026p-\u003eedp_dfid;\n+\n+\treturn NULL;\n+}\n \n #define EXT4_FT_DIR_CSUM\t0xDE\n \n@@ -2573,13 +2628,26 @@ struct ext4_dir_entry_tail {\n  * casefolded and encrypted need to store the hash as well, so we add room for\n  * ext4_extended_dir_entry_2. For all entries related to '.' or '..' you should\n  * pass NULL for dir, as those entries do not use the extra fields.\n+ *\n+ * For directories with the dirdata feature, extra data may follow the filename.\n+ * Use ext4_dir_entry_len() to compute the length of a directory entry\n+ * including any dirdata, or ext4_dirent_rec_len() directly when the total\n+ * name_len (including dirdata length) is already known.\n  */\n-static inline unsigned int ext4_dir_rec_len(__u8 name_len,\n+static inline unsigned int ext4_dirent_rec_len(unsigned int name_len,\n \t\t\t\t\t\tconst struct inode *dir)\n {\n-\tint rec_len = (name_len + 8 + EXT4_DIR_ROUND);\n+\tunsigned int rec_len = (name_len + 8 + EXT4_DIR_ROUND);\n \n-\tif (dir \u0026\u0026 ext4_hash_in_dirent(dir))\n+\t/*\n+\t * Without dirdata, the casefold+fscrypt hash lives at a fixed position\n+\t * after the filename and must be reserved explicitly.  With dirdata the\n+\t * hash is stored as a CFHASH extension and is already counted in the\n+\t * name_len argument (via ext4_dirent_get_data_len), so adding it again\n+\t * would double-count.\n+\t */\n+\tif (dir \u0026\u0026 ext4_hash_in_dirent(dir) \u0026\u0026\n+\t    !ext4_has_feature_dirdata(dir-\u003ei_sb))\n \t\trec_len += sizeof(struct ext4_dir_entry_hash);\n \treturn (rec_len \u0026 ~EXT4_DIR_ROUND);\n }\n@@ -2667,6 +2735,8 @@ struct ext4_filename {\n #if IS_ENABLED(CONFIG_UNICODE)\n \tstruct qstr cf_name;\n #endif\n+\t/* LUFID/dirdata payload to embed in the new directory entry, or NULL */\n+\tstruct ext4_dirent_fid *dfid;\n };\n \n #define fname_name(p) ((p)-\u003edisk_name.name)\n@@ -2978,18 +3048,27 @@ extern int __ext4_check_dir_entry(const char *, unsigned int, struct inode *,\n \tunlikely(__ext4_check_dir_entry(__func__, __LINE__, (dir), (filp), \\\n \t\t\t\t(de), (bh), (buf), (size), (offset)))\n extern int ext4_htree_store_dirent(struct file *dir_file, __u32 hash,\n-\t\t\t\t__u32 minor_hash,\n+\t\t\t\t__u32 minor_hash, int buf_size,\n \t\t\t\tstruct ext4_dir_entry_2 *dirent,\n \t\t\t\tstruct fscrypt_str *ent_name);\n extern void ext4_htree_free_dir_info(struct dir_private_info *p);\n extern int ext4_find_dest_de(struct inode *dir, struct buffer_head *bh,\n \t\t\t     void *buf, int buf_size,\n \t\t\t     struct ext4_filename *fname,\n-\t\t\t     struct ext4_dir_entry_2 **dest_de);\n-void ext4_insert_dentry(struct inode *dir, struct inode *inode,\n-\t\t\tstruct ext4_dir_entry_2 *de,\n-\t\t\tint buf_size,\n-\t\t\tstruct ext4_filename *fname);\n+\t\t\t     struct ext4_dir_entry_2 **dest_de,\n+\t\t\t     int dlen);\n+void ext4_insert_dentry_data(struct inode *dir, struct inode *inode,\n+\t\t\t     struct ext4_dir_entry_2 *de,\n+\t\t\t     int buf_size,\n+\t\t\t     struct ext4_filename *fname,\n+\t\t\t     void *data);\n+static inline void ext4_insert_dentry(struct inode *dir, struct inode *inode,\n+\t\t\t\t      struct ext4_dir_entry_2 *de,\n+\t\t\t\t      int buf_size,\n+\t\t\t\t      struct ext4_filename *fname)\n+{\n+\text4_insert_dentry_data(dir, inode, de, buf_size, fname, NULL);\n+}\n static inline void ext4_update_dx_flag(struct inode *inode)\n {\n \tif (!ext4_has_feature_dir_index(inode-\u003ei_sb) \u0026\u0026\n@@ -3003,12 +3082,14 @@ static const unsigned char ext4_filetype_table[] = {\n \tDT_UNKNOWN, DT_REG, DT_DIR, DT_CHR, DT_BLK, DT_FIFO, DT_SOCK, DT_LNK\n };\n \n-static inline  unsigned char get_dtype(struct super_block *sb, int filetype)\n+static inline unsigned char get_dtype(struct super_block *sb, int filetype)\n {\n-\tif (!ext4_has_feature_filetype(sb) || filetype \u003e= EXT4_FT_MAX)\n+\tunsigned char fl_index = filetype \u0026 EXT4_FT_MASK;\n+\n+\tif (!ext4_has_feature_filetype(sb) || fl_index \u003e= EXT4_FT_MAX)\n \t\treturn DT_UNKNOWN;\n \n-\treturn ext4_filetype_table[filetype];\n+\treturn ext4_filetype_table[fl_index];\n }\n extern int ext4_check_all_de(struct inode *dir, struct buffer_head *bh,\n \t\t\t     void *buf, int buf_size);\n@@ -3231,10 +3312,18 @@ extern int ext4_ext_migrate(struct inode *);\n extern int ext4_ind_migrate(struct inode *inode);\n \n /* namei.c */\n-extern int ext4_init_new_dir(handle_t *handle, struct inode *dir,\n-\t\t\t     struct inode *inode);\n+extern int ext4_init_new_dir_data(handle_t *handle, struct inode *dir,\n+\t\t\t\t  struct inode *inode,\n+\t\t\t\t  const void *data1, const void *data2);\n+static inline int ext4_init_new_dir(handle_t *handle, struct inode *dir,\n+\t\t\t\t    struct inode *inode)\n+{\n+\treturn ext4_init_new_dir_data(handle, dir, inode, NULL, NULL);\n+}\n extern int ext4_dirblock_csum_verify(struct inode *inode,\n \t\t\t\t     struct buffer_head *bh);\n+extern int ext4_dirdata_set_lufid(struct inode *dir, const char *filename,\n+\t\t\t   int namelen, struct ext4_dentry_param *edp);\n extern int ext4_htree_fill_tree(struct file *dir_file, __u32 start_hash,\n \t\t\t\t__u32 start_minor_hash, __u32 *next_hash);\n extern int ext4_search_dir(struct buffer_head *bh,\n@@ -3807,6 +3896,10 @@ extern int __ext4_unlink(struct inode *dir, const struct qstr *d_name,\n \t\t\t struct inode *inode, struct dentry *dentry);\n extern int __ext4_link(struct inode *dir, struct inode *inode,\n \t\t       const struct qstr *d_name, struct dentry *dentry);\n+extern unsigned char ext4_dirdata_get(struct ext4_dir_entry_2 *de,\n+\t\t\t\t      struct inode *dir, int buf_size,\n+\t\t\t\t      struct ext4_dirent_fid  *lufid,\n+\t\t\t\t      struct dx_hash_info *hinfo);\n \n #define S_SHIFT 12\n static const unsigned char ext4_type_by_mode[(S_IFMT \u003e\u003e S_SHIFT) + 1] = {\n@@ -4004,6 +4097,109 @@ static inline void ext4_clear_io_unwritten_flag(ext4_io_end_t *io_end)\n \t\tio_end-\u003eflag \u0026= ~EXT4_IO_END_UNWRITTEN;\n }\n \n+/*\n+ * Advance to the next dirdata record header starting from @ddh.\n+ */\n+#define ext4_dirdata_next(ddh) \\\n+\t((struct ext4_dirent_data_header *)((char *)(ddh) + (ddh)-\u003eddh_length))\n+\n+/*\n+ * ext4_dir_entry_is_tail() - Check if a directory entry is a tail entry.\n+ * @de: directory entry to check\n+ *\n+ * Returns true if @de is a directory block tail entry (checksum record).\n+ */\n+static inline bool ext4_dir_entry_is_tail(struct ext4_dir_entry_2 *de)\n+{\n+\tstruct ext4_dir_entry_tail *t = (struct ext4_dir_entry_tail *)de;\n+\n+\treturn !t-\u003edet_reserved_zero1 \u0026\u0026\n+\t       le16_to_cpu(t-\u003edet_rec_len) == sizeof(*t) \u0026\u0026\n+\t       !t-\u003edet_reserved_zero2 \u0026\u0026\n+\t       t-\u003edet_reserved_ft == EXT4_FT_DIR_CSUM;\n+}\n+\n+/*\n+ * ext4_dirent_get_data_len() - Compute the total dirdata length for an entry.\n+ * @de: directory entry\n+ * @rec_len: the record length of the directory entry (decoded)\n+ *\n+ * Computes the length of optional data stored after the filename (and its\n+ * implicit NUL terminator).  Each extension is indicated by a bit in the\n+ * high 4 bits of de-\u003efile_type; the first byte of each extension is its\n+ * length (including that length byte itself).\n+ *\n+ * Returns 0 for tail entries and for entries with no dirdata.\n+ */\n+static inline int ext4_dirent_get_data_len(struct ext4_dir_entry_2 *de,\n+\t\t\t\t\t   unsigned int rec_len)\n+{\n+\t__u8 extra_data_flags;\n+\tstruct ext4_dirent_data_header *ddh;\n+\tint dlen = 0;\n+\tunsigned int offset;\n+\n+\tif (ext4_dir_entry_is_tail(de))\n+\t\treturn 0;\n+\n+\textra_data_flags = (de-\u003efile_type \u0026 ~EXT4_FT_MASK) \u003e\u003e 4;\n+\t/* offset from start of entry to after filename + NUL */\n+\toffset = EXT4_BASE_DIR_LEN + de-\u003ename_len + 1;\n+\n+\t/* bounds check: ensure we start reading within the entry */\n+\tif (offset \u003e= rec_len)\n+\t\treturn 0;\n+\n+\tddh = (struct ext4_dirent_data_header *)((char *)de + offset);\n+\n+\twhile (extra_data_flags) {\n+\t\tif (extra_data_flags \u0026 1) {\n+\t\t\t/* bounds check before reading ddh_length */\n+\t\t\tif (offset + sizeof(*ddh) \u003e rec_len)\n+\t\t\t\treturn dlen;\n+\n+\t\t\t/* validate ddh_length is reasonable — read once to\n+\t\t\t * avoid a TOCTOU double-fetch on the disk field */\n+\t\t\t{\n+\t\t\t\tu8 ddh_len = ddh-\u003eddh_length;\n+\n+\t\t\t\tif (ddh_len == 0 || ddh_len \u003e rec_len - offset)\n+\t\t\t\t\treturn dlen;\n+\t\t\t\tdlen += ddh_len + (dlen == 0);\n+\t\t\t\toffset += ddh_len;\n+\t\t\t}\n+\t\t\tddh = ext4_dirdata_next(ddh);\n+\t\t}\n+\t\textra_data_flags \u003e\u003e= 1;\n+\t}\n+\treturn dlen;\n+}\n+\n+/*\n+ * ext4_dir_entry_len() - Compute the required rec_len for a directory entry.\n+ * @de:        directory entry (used to read name_len and any dirdata length)\n+ * @blocksize: size of the buffer @de lives in (the real directory block\n+ *             size, or the smaller inline-data buffer size for inline\n+ *             directories) -- used only to decode @de-\u003erec_len's \"0/65535\n+ *             means rest of buffer\" sentinel correctly.\n+ * @dir:       directory inode (may be NULL for '.' and '..' entries, which\n+ *             never carry the casefold+fscrypt hash regardless of the\n+ *             directory's feature flags)\n+ *\n+ * Returns the minimum record length needed to hold @de, rounded up to the\n+ * directory alignment and including room for the casefold+fscrypt hash if\n+ * the directory requires it.\n+ */\n+static inline unsigned int ext4_dir_entry_len(struct ext4_dir_entry_2 *de,\n+\t\t\t\t\t      unsigned int blocksize,\n+\t\t\t\t\t      const struct inode *dir)\n+{\n+\tunsigned int rec_len = ext4_rec_len_from_disk(de-\u003erec_len, blocksize);\n+\tunsigned int dirdata = ext4_dirent_get_data_len(de, rec_len);\n+\n+\treturn ext4_dirent_rec_len(de-\u003ename_len + dirdata, dir);\n+}\n+\n extern const struct iomap_ops ext4_iomap_ops;\n extern const struct iomap_ops ext4_iomap_report_ops;\n \ndiff --git a/fs/ext4/fast_commit.c b/fs/ext4/fast_commit.c\nindex 8e2259799614e9..006d45e9ce8798 100644\n--- a/fs/ext4/fast_commit.c\n+++ b/fs/ext4/fast_commit.c\n@@ -2697,6 +2697,7 @@ static const char * const fc_ineligible_reasons[] = {\n \t[EXT4_FC_REASON_MIGRATE] = \"Inode format migration\",\n \t[EXT4_FC_REASON_VERITY] = \"fs-verity enable\",\n \t[EXT4_FC_REASON_MOVE_EXT] = \"Move extents\",\n+\t[EXT4_FC_REASON_DIRDATA] = \"Dirdata LUFID (not recorded by fast-commit)\",\n };\n \n int ext4_fc_info_show(struct seq_file *seq, void *v)\ndiff --git a/fs/ext4/fast_commit.h b/fs/ext4/fast_commit.h\nindex 2f77a37fb10180..899faeb4e6e064 100644\n--- a/fs/ext4/fast_commit.h\n+++ b/fs/ext4/fast_commit.h\n@@ -100,6 +100,7 @@ enum {\n \tEXT4_FC_REASON_MIGRATE,\n \tEXT4_FC_REASON_VERITY,\n \tEXT4_FC_REASON_MOVE_EXT,\n+\tEXT4_FC_REASON_DIRDATA,\n \tEXT4_FC_REASON_MAX\n };\n \ndiff --git a/fs/ext4/inline.c b/fs/ext4/inline.c\nindex 8045e4ff270c7f..1bbf89e18076f4 100644\n--- a/fs/ext4/inline.c\n+++ b/fs/ext4/inline.c\n@@ -973,11 +973,20 @@ static int ext4_add_dirent_to_inline(handle_t *handle,\n \t\t\t\t     struct ext4_iloc *iloc,\n \t\t\t\t     void *inline_start, int inline_size)\n {\n-\tint\t\terr;\n+\tint\t\terr, dlen = 0;\n \tstruct ext4_dir_entry_2 *de;\n+\tstruct ext4_dirent_fid *dfid = NULL;\n+\n+\t/* fname-\u003edfid was set in ext4_add_entry() */\n+\tdfid = fname-\u003edfid;\n+\tif (dfid)\n+\t\tdlen = dfid-\u003edf_header.ddh_length;\n+\tif (ext4_hash_in_dirent(dir) \u0026\u0026\n+\t    ext4_has_feature_dirdata(dir-\u003ei_sb))\n+\t\tdlen += sizeof(struct ext4_dirent_hash);\n \n \terr = ext4_find_dest_de(dir, iloc-\u003ebh, inline_start,\n-\t\t\t\tinline_size, fname, \u0026de);\n+\t\t\t\tinline_size, fname, \u0026de, dlen);\n \tif (err)\n \t\treturn err;\n \n@@ -986,7 +995,7 @@ static int ext4_add_dirent_to_inline(handle_t *handle,\n \t\t\t\t\t    EXT4_JTR_NONE);\n \tif (err)\n \t\treturn err;\n-\text4_insert_dentry(dir, inode, de, inline_size, fname);\n+\text4_insert_dentry_data(dir, inode, de, inline_size, fname, dfid);\n \n \text4_show_inline_dir(dir, iloc-\u003ebh, inline_start, inline_size);\n \n@@ -1055,7 +1064,7 @@ static int ext4_update_inline_dir(handle_t *handle, struct inode *dir,\n \tint old_size = EXT4_I(dir)-\u003ei_inline_size - EXT4_MIN_INLINE_DATA_SIZE;\n \tint new_size = get_max_inline_xattr_value_size(dir, iloc);\n \n-\tif (new_size - old_size \u003c= ext4_dir_rec_len(1, NULL))\n+\tif (new_size - old_size \u003c= ext4_dirent_rec_len(1, NULL))\n \t\treturn -ENOSPC;\n \n \tret = ext4_update_inline_data(handle, dir,\n@@ -1264,6 +1273,7 @@ int ext4_inlinedir_to_tree(struct file *dir_file,\n \tint err = 0, count = 0;\n \tunsigned int parent_ino;\n \tint pos;\n+\tunsigned int de_len;\n \tstruct ext4_dir_entry_2 *de;\n \tstruct inode *inode = file_inode(dir_file);\n \tint ret, inline_size = 0;\n@@ -1309,7 +1319,7 @@ int ext4_inlinedir_to_tree(struct file *dir_file,\n \t\t\tfake.name_len = 1;\n \t\t\tmemcpy(fake.name, \".\", 2);\n \t\t\tfake.rec_len = ext4_rec_len_to_disk(\n-\t\t\t\t\t  ext4_dir_rec_len(fake.name_len, NULL),\n+\t\t\t\t\t  ext4_dirent_rec_len(fake.name_len, NULL),\n \t\t\t\t\t  inline_size);\n \t\t\text4_set_de_type(inode-\u003ei_sb, \u0026fake, S_IFDIR);\n \t\t\tde = \u0026fake;\n@@ -1319,26 +1329,34 @@ int ext4_inlinedir_to_tree(struct file *dir_file,\n \t\t\tfake.name_len = 2;\n \t\t\tmemcpy(fake.name, \"..\", 3);\n \t\t\tfake.rec_len = ext4_rec_len_to_disk(\n-\t\t\t\t\t  ext4_dir_rec_len(fake.name_len, NULL),\n+\t\t\t\t\t  ext4_dirent_rec_len(fake.name_len, NULL),\n \t\t\t\t\t  inline_size);\n \t\t\text4_set_de_type(inode-\u003ei_sb, \u0026fake, S_IFDIR);\n \t\t\tde = \u0026fake;\n \t\t\tpos = EXT4_INLINE_DOTDOT_SIZE;\n \t\t} else {\n+\t\t\tif (pos + EXT4_BASE_DIR_LEN \u003e inline_size) {\n+\t\t\t\tret = count;\n+\t\t\t\tgoto out;\n+\t\t\t}\n \t\t\tde = (struct ext4_dir_entry_2 *)(dir_buf + pos);\n-\t\t\tpos += ext4_rec_len_from_disk(de-\u003erec_len, inline_size);\n+\t\t\tde_len = ext4_rec_len_from_disk(de-\u003erec_len, inline_size);\n+\t\t\tif (de_len \u003c EXT4_BASE_DIR_LEN ||\n+\t\t\t    pos + de_len \u003e (unsigned int)inline_size) {\n+\t\t\t\tret = count;\n+\t\t\t\tgoto out;\n+\t\t\t}\n \t\t\tif (ext4_check_dir_entry(inode, dir_file, de,\n \t\t\t\t\t iloc.bh, dir_buf,\n \t\t\t\t\t inline_size, pos)) {\n \t\t\t\tret = count;\n \t\t\t\tgoto out;\n \t\t\t}\n+\t\t\tpos += de_len;\n \t\t}\n \n-\t\tif (ext4_hash_in_dirent(dir)) {\n-\t\t\thinfo-\u003ehash = EXT4_DIRENT_HASH(de);\n-\t\t\thinfo-\u003eminor_hash = EXT4_DIRENT_MINOR_HASH(de);\n-\t\t} else {\n+\t\tif (!(ext4_dirdata_get(de, dir, inline_size, NULL, hinfo) \u0026\n+\t\t\t\t\t\t\tEXT4_DIRENT_CFHASH)) {\n \t\t\terr = ext4fs_dirhash(dir, de-\u003ename, de-\u003ename_len, hinfo);\n \t\t\tif (err) {\n \t\t\t\tret = err;\n@@ -1354,7 +1372,8 @@ int ext4_inlinedir_to_tree(struct file *dir_file,\n \t\ttmp_str.name = de-\u003ename;\n \t\ttmp_str.len = de-\u003ename_len;\n \t\terr = ext4_htree_store_dirent(dir_file, hinfo-\u003ehash,\n-\t\t\t\t\t      hinfo-\u003eminor_hash, de, \u0026tmp_str);\n+\t\t\t\t\t      hinfo-\u003eminor_hash, inline_size,\n+\t\t\t\t\t      de, \u0026tmp_str);\n \t\tif (err) {\n \t\t\tret = err;\n \t\t\tgoto out;\n@@ -1427,8 +1446,8 @@ int ext4_read_inline_dir(struct file *file,\n \t * So we will use extra_offset and extra_size to indicate them\n \t * during the inline dir iteration.\n \t */\n-\tdotdot_offset = ext4_dir_rec_len(1, NULL);\n-\tdotdot_size = dotdot_offset + ext4_dir_rec_len(2, NULL);\n+\tdotdot_offset = ext4_dirent_rec_len(1, NULL);\n+\tdotdot_size = dotdot_offset + ext4_dirent_rec_len(2, NULL);\n \textra_offset = dotdot_size - EXT4_INLINE_DOTDOT_SIZE;\n \textra_size = extra_offset + inline_size;\n \n@@ -1463,7 +1482,7 @@ int ext4_read_inline_dir(struct file *file,\n \t\t\t * failure will be detected in the\n \t\t\t * dirent test below. */\n \t\t\tif (ext4_rec_len_from_disk(de-\u003erec_len, extra_size)\n-\t\t\t\t\u003c ext4_dir_rec_len(1, NULL))\n+\t\t\t\t\u003c ext4_dirent_rec_len(1, NULL))\n \t\t\t\tbreak;\n \t\t\ti += ext4_rec_len_from_disk(de-\u003erec_len,\n \t\t\t\t\t\t    extra_size);\ndiff --git a/fs/ext4/ioctl.c b/fs/ext4/ioctl.c\nindex c8387e6a2c6e90..b5421d59f7a032 100644\n--- a/fs/ext4/ioctl.c\n+++ b/fs/ext4/ioctl.c\n@@ -1535,6 +1535,87 @@ static int ext4_ioctl_set_tune_sb(struct file *filp,\n \treturn ret;\n }\n \n+/*\n+ * ext4_ioctl_set_lufid() - Set LUFID on a directory entry\n+ * @filp:\tfile pointer (parent directory)\n+ * @arg:\tpointer to ext4_set_lufid structure with filename and LUFID data\n+ *\n+ * This ioctl allows setting LUFID data on an existing\n+ * directory entry. It is called on the parent directory with a filename and\n+ * LUFID data.\n+ */\n+static long ext4_ioctl_set_lufid(struct file *filp, unsigned long arg)\n+{\n+\tstruct inode *dir = file_inode(filp);\n+\tstruct mnt_idmap *idmap = file_mnt_idmap(filp);\n+\tstruct ext4_set_lufid lufid_args;\n+\tstruct {\n+\t\t__u32 edp_magic;\n+\t\tstruct ext4_dirent_data_header df_header;\n+\t\tchar df_fid[255];\n+\t} edp;\n+\tint err;\n+\n+\t/* Check if parent is a directory */\n+\tif (!S_ISDIR(dir-\u003ei_mode))\n+\t\treturn -ENOTDIR;\n+\n+\t/* This ioctl mutates directory entries; merely having the directory\n+\t * open (which only ever requires read access) is not enough */\n+\terr = inode_permission(idmap, dir, MAY_WRITE);\n+\tif (err)\n+\t\treturn err;\n+\n+\t/* Copy arguments from user space */\n+\tif (copy_from_user(\u0026lufid_args, (struct ext4_set_lufid __user *)arg,\n+\t\t\t   sizeof(lufid_args)))\n+\t\treturn -EFAULT;\n+\n+\t/* Validate parameters.  esl_name_len is NUL-excluded length (1-255). */\n+\tif (lufid_args.esl_name_len == 0 || lufid_args.esl_name_len \u003e EXT4_NAME_LEN)\n+\t\treturn -EINVAL;\n+\n+\t/* ddh_length (esl_data_len + the header byte below) must itself fit\n+\t * in the __u8 ddh_length field without wrapping */\n+\tif (lufid_args.esl_data_len == 0 ||\n+\t    lufid_args.esl_data_len \u003e 255 - sizeof(edp.df_header))\n+\t\treturn -EINVAL;\n+\n+\t/* Ensure filename is NUL-terminated at exactly esl_name_len */\n+\tif (lufid_args.esl_name[lufid_args.esl_name_len] != '\\0')\n+\t\treturn -EINVAL;\n+\n+\t/* '.' and '..' are not ordinary entries -- they must stay the first\n+\t * two entries in the directory's first block, so they can't go\n+\t * through the general delete+re-add path this ioctl uses */\n+\tif (!strcmp(lufid_args.esl_name, \".\") || !strcmp(lufid_args.esl_name, \"..\"))\n+\t\treturn -EINVAL;\n+\n+\t/* Prepare the dentry param struct with LUFID data. ddh_length is\n+\t * documented (see struct ext4_dirent_data_header) as the length of\n+\t * the header plus the whole data blob -- include the header here so\n+\t * every dirdata reader/writer that takes ddh_length at face value\n+\t * (e.g. ext4_dirdata_set()'s memcpy) copies the full LUFID payload\n+\t * instead of silently dropping its last byte. */\n+\tedp.edp_magic = EXT4_LUFID_MAGIC;\n+\tedp.df_header.ddh_length = lufid_args.esl_data_len +\n+\t\t\t\t    sizeof(edp.df_header);\n+\tmemcpy(edp.df_fid, lufid_args.esl_data, lufid_args.esl_data_len);\n+\n+\t/* Want write access */\n+\terr = mnt_want_write_file(filp);\n+\tif (err)\n+\t\treturn err;\n+\n+\t/* Call the helper function to do the actual work */\n+\terr = ext4_dirdata_set_lufid(dir, lufid_args.esl_name,\n+\t\t\t\t    lufid_args.esl_name_len,\n+\t\t\t\t    (struct ext4_dentry_param *)\u0026edp);\n+\n+\tmnt_drop_write_file(filp);\n+\treturn err;\n+}\n+\n static long __ext4_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)\n {\n \tstruct inode *inode = file_inode(filp);\n@@ -1921,6 +2002,8 @@ static long __ext4_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)\n \t\t\t\t\t      (void __user *)arg);\n \tcase EXT4_IOC_SET_TUNE_SB_PARAM:\n \t\treturn ext4_ioctl_set_tune_sb(filp, (void __user *)arg);\n+\tcase EXT4_IOC_SET_LUFID:\n+\t\treturn ext4_ioctl_set_lufid(filp, arg);\n \tdefault:\n \t\treturn -ENOTTY;\n \t}\n@@ -2000,6 +2083,7 @@ long ext4_compat_ioctl(struct file *file, unsigned int cmd, unsigned long arg)\n \tcase FS_IOC_SETFSLABEL:\n \tcase EXT4_IOC_GETFSUUID:\n \tcase EXT4_IOC_SETFSUUID:\n+\tcase EXT4_IOC_SET_LUFID:\n \t\tbreak;\n \tdefault:\n \t\treturn -ENOIOCTLCMD;\ndiff --git a/fs/ext4/namei.c b/fs/ext4/namei.c\nindex cc49ae04a6f649..ba39ecc690bc2d 100644\n--- a/fs/ext4/namei.c\n+++ b/fs/ext4/namei.c\n@@ -102,7 +102,7 @@ static struct buffer_head *ext4_append(handle_t *handle,\n }\n \n static int ext4_dx_csum_verify(struct inode *inode,\n-\t\t\t       struct ext4_dir_entry *dirent);\n+\t\t\t       struct ext4_dir_entry_2 *dirent);\n \n /*\n  * Hints to ext4_read_dirblock regarding whether we expect a directory\n@@ -128,7 +128,7 @@ static struct buffer_head *__ext4_read_dirblock(struct inode *inode,\n \t\t\t\t\t\tunsigned int line)\n {\n \tstruct buffer_head *bh;\n-\tstruct ext4_dir_entry *dirent;\n+\tstruct ext4_dir_entry_2 *dirent;\n \tint is_dx_block = 0;\n \n \tif (block \u003e= inode-\u003ei_size \u003e\u003e inode-\u003ei_blkbits) {\n@@ -160,7 +160,7 @@ static struct buffer_head *__ext4_read_dirblock(struct inode *inode,\n \t}\n \tif (!bh)\n \t\treturn NULL;\n-\tdirent = (struct ext4_dir_entry *) bh-\u003eb_data;\n+\tdirent = (struct ext4_dir_entry_2 *) bh-\u003eb_data;\n \t/* Determine whether or not we have an index block */\n \tif (is_dx(inode)) {\n \t\tif (block == 0)\n@@ -244,22 +244,13 @@ struct dx_entry\n  * hash version mod 4 should never be 0.  Sincerely, the paranoia department.\n  */\n \n-struct dx_root\n+struct dx_root_info\n {\n-\tstruct fake_dirent dot;\n-\tchar dot_name[4];\n-\tstruct fake_dirent dotdot;\n-\tchar dotdot_name[4];\n-\tstruct dx_root_info\n-\t{\n-\t\t__le32 reserved_zero;\n-\t\tu8 hash_version;\n-\t\tu8 info_length; /* 8 */\n-\t\tu8 indirect_levels;\n-\t\tu8 unused_flags;\n-\t}\n-\tinfo;\n-\tstruct dx_entry\tentries[];\n+\t__le32 reserved_zero;\n+\tu8 hash_version;\n+\tu8 info_length; /* 8 */\n+\tu8 indirect_levels;\n+\tu8 unused_flags;\n };\n \n struct dx_node\n@@ -314,17 +305,16 @@ static struct ext4_dir_entry_tail *get_dirent_tail(struct inode *inode,\n \t\t\t\t\t\t   struct buffer_head *bh)\n {\n \tstruct ext4_dir_entry_tail *t;\n-\tint blocksize = EXT4_BLOCK_SIZE(inode-\u003ei_sb);\n \n #ifdef PARANOID\n-\tstruct ext4_dir_entry *d, *top;\n+\tstruct ext4_dir_entry_2 *d, *top;\n \n-\td = (struct ext4_dir_entry *)bh-\u003eb_data;\n-\ttop = (struct ext4_dir_entry *)(bh-\u003eb_data +\n-\t\t(blocksize - sizeof(struct ext4_dir_entry_tail)));\n-\twhile (d \u003c top \u0026\u0026 ext4_rec_len_from_disk(d-\u003erec_len, blocksize))\n-\t\td = (struct ext4_dir_entry *)(((void *)d) +\n-\t\t    ext4_rec_len_from_disk(d-\u003erec_len, blocksize));\n+\td = (struct ext4_dir_entry_2 *)bh-\u003eb_data;\n+\ttop = (struct ext4_dir_entry_2 *)(bh-\u003eb_data +\n+\t\t(EXT4_BLOCK_SIZE(inode-\u003ei_sb) - sizeof(struct ext4_dir_entry_tail)));\n+\twhile (d \u003c top \u0026\u0026 ext4_rec_len_from_disk(d-\u003erec_len, EXT4_BLOCK_SIZE(inode-\u003ei_sb)))\n+\t\td = (struct ext4_dir_entry_2 *)(((void *)d) +\n+\t\t    ext4_rec_len_from_disk(d-\u003erec_len, EXT4_BLOCK_SIZE(inode-\u003ei_sb)));\n \n \tif (d != top)\n \t\treturn NULL;\n@@ -334,11 +324,7 @@ static struct ext4_dir_entry_tail *get_dirent_tail(struct inode *inode,\n \tt = EXT4_DIRENT_TAIL(bh-\u003eb_data, EXT4_BLOCK_SIZE(inode-\u003ei_sb));\n #endif\n \n-\tif (t-\u003edet_reserved_zero1 ||\n-\t    (ext4_rec_len_from_disk(t-\u003edet_rec_len, blocksize) !=\n-\t     sizeof(struct ext4_dir_entry_tail)) ||\n-\t    t-\u003edet_reserved_zero2 ||\n-\t    t-\u003edet_reserved_ft != EXT4_FT_DIR_CSUM)\n+\tif (!ext4_dir_entry_is_tail((struct ext4_dir_entry_2 *)t))\n \t\treturn NULL;\n \n \treturn t;\n@@ -410,35 +396,43 @@ int ext4_handle_dirty_dirblock(handle_t *handle,\n }\n \n static struct dx_countlimit *get_dx_countlimit(struct inode *inode,\n-\t\t\t\t\t       struct ext4_dir_entry *dirent,\n+\t\t\t\t\t       struct ext4_dir_entry_2 *dirent,\n \t\t\t\t\t       int *offset)\n {\n-\tstruct ext4_dir_entry *dp;\n+\tstruct ext4_dir_entry_2 *de;\n \tstruct dx_root_info *root;\n-\tint count_offset;\n+\tint count_offset, dotdot_rec_len;\n \tint blocksize = EXT4_BLOCK_SIZE(inode-\u003ei_sb);\n \tunsigned int rlen = ext4_rec_len_from_disk(dirent-\u003erec_len, blocksize);\n \n-\tif (rlen == blocksize)\n-\t\tcount_offset = 8;\n-\telse if (rlen == 12) {\n-\t\tdp = (struct ext4_dir_entry *)(((void *)dirent) + 12);\n-\t\tif (ext4_rec_len_from_disk(dp-\u003erec_len, blocksize) != blocksize - 12)\n+\tif (rlen == blocksize) {\n+\t\tcount_offset = sizeof(struct dx_node);\n+\t} else {\n+\t\tif (rlen \u003c EXT4_BASE_DIR_LEN ||\n+\t\t    rlen + EXT4_BASE_DIR_LEN \u003e blocksize)\n+\t\t\treturn NULL;\n+\t\tde = (struct ext4_dir_entry_2 *)(((char *)dirent) + rlen);\n+\t\tif (le16_to_cpu(de-\u003erec_len) != (blocksize - rlen))\n \t\t\treturn NULL;\n-\t\troot = (struct dx_root_info *)(((void *)dp + 12));\n+\t\t/* de-\u003erec_len covers whole dx_root block, calculate actual length.\n+\t\t * This is the '..' entry, which never carries the casefold+fscrypt\n+\t\t * hash, so pass NULL for dir regardless of the directory's flags */\n+\t\tdotdot_rec_len = ext4_dir_entry_len(de, blocksize, NULL);\n+\t\tif (rlen + dotdot_rec_len + sizeof(struct dx_root_info) \u003e blocksize)\n+\t\t\treturn NULL;\n+\t\troot = (struct dx_root_info *)(((char *)de + dotdot_rec_len));\n \t\tif (root-\u003ereserved_zero ||\n \t\t    root-\u003einfo_length != sizeof(struct dx_root_info))\n \t\t\treturn NULL;\n-\t\tcount_offset = 32;\n-\t} else\n-\t\treturn NULL;\n+\t\tcount_offset = root-\u003einfo_length + rlen + dotdot_rec_len;\n+\t}\n \n \tif (offset)\n \t\t*offset = count_offset;\n \treturn (struct dx_countlimit *)(((void *)dirent) + count_offset);\n }\n \n-static __le32 ext4_dx_csum(struct inode *inode, struct ext4_dir_entry *dirent,\n+static __le32 ext4_dx_csum(struct inode *inode, struct ext4_dir_entry_2 *dirent,\n \t\t\t   int count_offset, int count, struct dx_tail *t)\n {\n \tstruct ext4_inode_info *ei = EXT4_I(inode);\n@@ -456,7 +450,7 @@ static __le32 ext4_dx_csum(struct inode *inode, struct ext4_dir_entry *dirent,\n }\n \n static int ext4_dx_csum_verify(struct inode *inode,\n-\t\t\t       struct ext4_dir_entry *dirent)\n+\t\t\t       struct ext4_dir_entry_2 *dirent)\n {\n \tstruct dx_countlimit *c;\n \tstruct dx_tail *t;\n@@ -477,6 +471,10 @@ static int ext4_dx_csum_verify(struct inode *inode,\n \t\twarn_no_space_for_csum(inode);\n \t\treturn 0;\n \t}\n+\tif (count \u003e limit) {\n+\t\tEXT4_ERROR_INODE(inode, \"dir seems corrupt?  Run e2fsck -D.\");\n+\t\treturn 0;\n+\t}\n \tt = (struct dx_tail *)(((struct dx_entry *)c) + limit);\n \n \tif (t-\u003edt_checksum != ext4_dx_csum(inode, dirent, count_offset,\n@@ -485,7 +483,7 @@ static int ext4_dx_csum_verify(struct inode *inode,\n \treturn 1;\n }\n \n-static void ext4_dx_csum_set(struct inode *inode, struct ext4_dir_entry *dirent)\n+static void ext4_dx_csum_set(struct inode *inode, struct ext4_dir_entry_2 *dirent)\n {\n \tstruct dx_countlimit *c;\n \tstruct dx_tail *t;\n@@ -506,6 +504,10 @@ static void ext4_dx_csum_set(struct inode *inode, struct ext4_dir_entry *dirent)\n \t\twarn_no_space_for_csum(inode);\n \t\treturn;\n \t}\n+\tif (count \u003e limit) {\n+\t\tEXT4_ERROR_INODE(inode, \"dir seems corrupt?  Run e2fsck -D.\");\n+\t\treturn;\n+\t}\n \tt = (struct dx_tail *)(((struct dx_entry *)c) + limit);\n \n \tt-\u003edt_checksum = ext4_dx_csum(inode, dirent, count_offset, count, t);\n@@ -515,7 +517,7 @@ static inline int ext4_handle_dirty_dx_node(handle_t *handle,\n \t\t\t\t\t    struct inode *inode,\n \t\t\t\t\t    struct buffer_head *bh)\n {\n-\text4_dx_csum_set(inode, (struct ext4_dir_entry *)bh-\u003eb_data);\n+\text4_dx_csum_set(inode, (struct ext4_dir_entry_2 *)bh-\u003eb_data);\n \treturn ext4_handle_dirty_metadata(handle, inode, bh);\n }\n \n@@ -533,6 +535,30 @@ ext4_next_entry(struct ext4_dir_entry_2 *p, unsigned long blocksize)\n  * Future: use high four bits of block for coalesce-on-delete flags\n  * Mask them off for now.\n  */\n+static struct dx_root_info *dx_get_dx_info(struct inode *dir, void *de_buf)\n+{\n+\tunsigned int blocksize = dir-\u003ei_sb-\u003es_blocksize;\n+\tvoid *base = de_buf;\n+\n+\t/* '.' and '..' never carry the casefold+fscrypt hash, so pass NULL\n+\t * for dir regardless of the directory's flags */\n+\tif (ext4_rec_len_from_disk(((struct ext4_dir_entry_2 *)de_buf)-\u003erec_len,\n+\t\t\t\t   blocksize) \u003c EXT4_BASE_DIR_LEN)\n+\t\treturn ERR_PTR(-EFSCORRUPTED);\n+\tde_buf += ext4_dir_entry_len(de_buf, blocksize, NULL);\n+\n+\t/* dx root info is after dotdot entry */\n+\tif (de_buf \u003c base || (char *)de_buf - (char *)base +\n+\t    EXT4_BASE_DIR_LEN \u003e blocksize)\n+\t\treturn ERR_PTR(-EFSCORRUPTED);\n+\tde_buf += ext4_dir_entry_len(de_buf, blocksize, NULL);\n+\n+\tif (de_buf \u003c base || (char *)de_buf - (char *)base +\n+\t\t\t      sizeof(struct dx_root_info) \u003e blocksize)\n+\t\treturn ERR_PTR(-EFSCORRUPTED);\n+\n+\treturn (struct dx_root_info *)de_buf;\n+}\n \n static inline ext4_lblk_t dx_get_block(struct dx_entry *entry)\n {\n@@ -574,11 +600,17 @@ static inline void dx_set_limit(struct dx_entry *entries, unsigned value)\n \t((struct dx_countlimit *) entries)-\u003elimit = cpu_to_le16(value);\n }\n \n-static inline unsigned dx_root_limit(struct inode *dir, unsigned infosize)\n+static inline unsigned dx_root_limit(struct inode *dir,\n+\tstruct ext4_dir_entry_2 *dot_de)\n {\n-\tunsigned int entry_space = dir-\u003ei_sb-\u003es_blocksize -\n-\t\t\text4_dir_rec_len(1, NULL) -\n-\t\t\text4_dir_rec_len(2, NULL) - infosize;\n+\tstruct dx_root_info *info;\n+\tunsigned int entry_space;\n+\n+\tinfo = dx_get_dx_info(dir, dot_de);\n+\tif (IS_ERR(info))\n+\t\treturn 0;\n+\tentry_space = dir-\u003ei_sb-\u003es_blocksize - ((char *)info - (char *)dot_de) -\n+\t\tinfo-\u003einfo_length;\n \n \tif (ext4_has_feature_metadata_csum(dir-\u003ei_sb))\n \t\tentry_space -= sizeof(struct dx_tail);\n@@ -587,8 +619,10 @@ static inline unsigned dx_root_limit(struct inode *dir, unsigned infosize)\n \n static inline unsigned dx_node_limit(struct inode *dir)\n {\n+\t/* dx_node fake_dirent is always 8 bytes — it never carries a casefold\n+\t * hash, so pass NULL to suppress the hash-size term. */\n \tunsigned int entry_space = dir-\u003ei_sb-\u003es_blocksize -\n-\t\t\text4_dir_rec_len(0, dir);\n+\t\t\text4_dirent_rec_len(0, NULL);\n \n \tif (ext4_has_feature_metadata_csum(dir-\u003ei_sb))\n \t\tentry_space -= sizeof(struct dx_tail);\n@@ -698,7 +732,9 @@ static struct stats dx_show_leaf(struct inode *dir,\n \t\t\t\t       (unsigned) ((char *) de - base));\n #endif\n \t\t\t}\n-\t\t\tspace += ext4_dir_rec_len(de-\u003ename_len, dir);\n+\t\t\tif ((char *)de + EXT4_BASE_DIR_LEN \u003e base + size)\n+\t\t\t\tbreak;\n+\t\t\tspace += ext4_dir_entry_len(de, size, dir);\n \t\t\tnames++;\n \t\t}\n \t\tde = ext4_next_entry(de, size);\n@@ -780,7 +816,7 @@ dx_probe(struct ext4_filename *fname, struct inode *dir,\n {\n \tunsigned count, indirect, level, i;\n \tstruct dx_entry *at, *entries, *p, *q, *m;\n-\tstruct dx_root *root;\n+\tstruct dx_root_info *info;\n \tstruct dx_frame *frame = frame_in;\n \tstruct dx_frame *ret_err = ERR_PTR(ERR_BAD_DX_DIR);\n \tu32 hash;\n@@ -792,23 +828,31 @@ dx_probe(struct ext4_filename *fname, struct inode *dir,\n \tif (IS_ERR(frame-\u003ebh))\n \t\treturn (struct dx_frame *) frame-\u003ebh;\n \n-\troot = (struct dx_root *) frame-\u003ebh-\u003eb_data;\n-\tif (root-\u003einfo.hash_version != DX_HASH_TEA \u0026\u0026\n-\t    root-\u003einfo.hash_version != DX_HASH_HALF_MD4 \u0026\u0026\n-\t    root-\u003einfo.hash_version != DX_HASH_LEGACY \u0026\u0026\n-\t    root-\u003einfo.hash_version != DX_HASH_SIPHASH) {\n-\t\text4_warning_inode(dir, \"Unrecognised inode hash code %u\",\n-\t\t\t\t   root-\u003einfo.hash_version);\n+\tinfo = dx_get_dx_info(dir, (struct ext4_dir_entry_2 *)frame-\u003ebh-\u003eb_data);\n+\tif (IS_ERR(info))\n+\t\tgoto fail;\n+\tif (info-\u003einfo_length != sizeof(struct dx_root_info)) {\n+\t\text4_warning_inode(dir, \"corrupted htree: info_length %u\",\n+\t\t\t\t   info-\u003einfo_length);\n+\t\tgoto fail;\n+\t}\n+\tif (info-\u003ehash_version != DX_HASH_TEA \u0026\u0026\n+\t    info-\u003ehash_version != DX_HASH_HALF_MD4 \u0026\u0026\n+\t    info-\u003ehash_version != DX_HASH_LEGACY \u0026\u0026\n+\t    info-\u003ehash_version != DX_HASH_SIPHASH) {\n+\t\text4_warning(dir-\u003ei_sb,\n+\t\t\t\"Unrecognised inode hash code %d for directory #%llu\",\n+\t\t\tinfo-\u003ehash_version, dir-\u003ei_ino);\n \t\tgoto fail;\n \t}\n \tif (ext4_hash_in_dirent(dir)) {\n-\t\tif (root-\u003einfo.hash_version != DX_HASH_SIPHASH) {\n+\t\tif (info-\u003ehash_version != DX_HASH_SIPHASH) {\n \t\t\text4_warning_inode(dir,\n \t\t\t\t\"Hash in dirent, but hash is not SIPHASH\");\n \t\t\tgoto fail;\n \t\t}\n \t} else {\n-\t\tif (root-\u003einfo.hash_version == DX_HASH_SIPHASH) {\n+\t\tif (info-\u003ehash_version == DX_HASH_SIPHASH) {\n \t\t\text4_warning_inode(dir,\n \t\t\t\t\"Hash code is SIPHASH, but hash not in dirent\");\n \t\t\tgoto fail;\n@@ -816,7 +860,7 @@ dx_probe(struct ext4_filename *fname, struct inode *dir,\n \t}\n \tif (fname)\n \t\thinfo = \u0026fname-\u003ehinfo;\n-\thinfo-\u003ehash_version = root-\u003einfo.hash_version;\n+\thinfo-\u003ehash_version = info-\u003ehash_version;\n \tif (hinfo-\u003ehash_version \u003c= DX_HASH_TEA)\n \t\thinfo-\u003ehash_version += EXT4_SB(dir-\u003ei_sb)-\u003es_hash_unsigned;\n \thinfo-\u003eseed = EXT4_SB(dir-\u003ei_sb)-\u003es_hash_seed;\n@@ -832,13 +876,13 @@ dx_probe(struct ext4_filename *fname, struct inode *dir,\n \t}\n \thash = hinfo-\u003ehash;\n \n-\tif (root-\u003einfo.unused_flags \u0026 1) {\n+\tif (info-\u003eunused_flags \u0026 1) {\n \t\text4_warning_inode(dir, \"Unimplemented hash flags: %#06x\",\n-\t\t\t\t   root-\u003einfo.unused_flags);\n+\t\t\t\t   info-\u003eunused_flags);\n \t\tgoto fail;\n \t}\n \n-\tindirect = root-\u003einfo.indirect_levels;\n+\tindirect = info-\u003eindirect_levels;\n \tif (indirect \u003e= ext4_dir_htree_level(dir-\u003ei_sb)) {\n \t\text4_warning(dir-\u003ei_sb,\n \t\t\t     \"Directory (ino: %llu) htree depth %#06x exceed\"\n@@ -851,14 +895,15 @@ dx_probe(struct ext4_filename *fname, struct inode *dir,\n \t\tgoto fail;\n \t}\n \n-\tentries = (struct dx_entry *)(((char *)\u0026root-\u003einfo) +\n-\t\t\t\t      root-\u003einfo.info_length);\n+\tentries = (struct dx_entry *)(((char *)info) + info-\u003einfo_length);\n \n-\tif (dx_get_limit(entries) != dx_root_limit(dir,\n-\t\t\t\t\t\t   root-\u003einfo.info_length)) {\n+\tif (dx_get_limit(entries) !=\n+\t    dx_root_limit(dir, (struct ext4_dir_entry_2 *)frame-\u003ebh-\u003eb_data)) {\n \t\text4_warning_inode(dir, \"dx entry: limit %u != root limit %u\",\n \t\t\t\t   dx_get_limit(entries),\n-\t\t\t\t   dx_root_limit(dir, root-\u003einfo.info_length));\n+\t\t\t\t   dx_root_limit(dir,\n+\t\t\t\t   (struct ext4_dir_entry_2 *)frame-\u003ebh-\u003eb_data\n+\t\t\t\t   ));\n \t\tgoto fail;\n \t}\n \n@@ -935,7 +980,7 @@ dx_probe(struct ext4_filename *fname, struct inode *dir,\n \treturn ret_err;\n }\n \n-static void dx_release(struct dx_frame *frames)\n+static void dx_release(struct inode *dir, struct dx_frame *frames)\n {\n \tstruct dx_root_info *info;\n \tint i;\n@@ -944,7 +989,9 @@ static void dx_release(struct dx_frame *frames)\n \tif (frames[0].bh == NULL)\n \t\treturn;\n \n-\tinfo = \u0026((struct dx_root *)frames[0].bh-\u003eb_data)-\u003einfo;\n+\tinfo = dx_get_dx_info(dir, (struct ext4_dir_entry_2 *)frames[0].bh-\u003eb_data);\n+\tif (IS_ERR(info))\n+\t\treturn;\n \t/* save local copy, \"info\" may be freed after brelse() */\n \tindirect_levels = info-\u003eindirect_levels;\n \tfor (i = 0; i \u003c= indirect_levels; i++) {\n@@ -1056,7 +1103,7 @@ static int htree_dirblock_to_tree(struct file *dir_file,\n \t/* csum entries are not larger in the casefolded encrypted case */\n \ttop = (struct ext4_dir_entry_2 *) ((char *) de +\n \t\t\t\t\t   dir-\u003ei_sb-\u003es_blocksize -\n-\t\t\t\t\t   ext4_dir_rec_len(0,\n+\t\t\t\t\t   ext4_dirent_rec_len(0,\n \t\t\t\t\t\t\t   csum ? NULL : dir));\n \t/* Check if the directory is encrypted */\n \tif (IS_ENCRYPTED(dir)) {\n@@ -1081,22 +1128,23 @@ static int htree_dirblock_to_tree(struct file *dir_file,\n \t\t\t/* silently ignore the rest of the block */\n \t\t\tbreak;\n \t\t}\n-\t\tif (ext4_hash_in_dirent(dir)) {\n-\t\t\tif (de-\u003ename_len \u0026\u0026 de-\u003einode) {\n-\t\t\t\thinfo-\u003ehash = EXT4_DIRENT_HASH(de);\n-\t\t\t\thinfo-\u003eminor_hash = EXT4_DIRENT_MINOR_HASH(de);\n-\t\t\t} else {\n-\t\t\t\thinfo-\u003ehash = 0;\n-\t\t\t\thinfo-\u003eminor_hash = 0;\n-\t\t\t}\n+\t\tif (de-\u003ename_len \u0026\u0026 de-\u003einode) {\n+\t\t\t/* check for saved hash first, or generate it from name */\n+\t\t\tif (!(ext4_dirdata_get(de, dir, dir-\u003ei_sb-\u003es_blocksize,\n+\t\t\t\t\t       NULL, hinfo) \u0026\n+\t\t\t      EXT4_DIRENT_CFHASH)) {\n+\t\t\t\terr = ext4fs_dirhash(dir, de-\u003ename,\n+\t\t\t\t\t\t     de-\u003ename_len, hinfo);\n+\t\t\t\tif (err \u003c 0) {\n+\t\t\t\t\tcount = err;\n+\t\t\t\t\tgoto errout;\n+\t\t\t\t}\n+\t\t\t }\n \t\t} else {\n-\t\t\terr = ext4fs_dirhash(dir, de-\u003ename,\n-\t\t\t\t\t     de-\u003ename_len, hinfo);\n-\t\t\tif (err \u003c 0) {\n-\t\t\t\tcount = err;\n-\t\t\t\tgoto errout;\n-\t\t\t}\n+\t\t\thinfo-\u003ehash = 0;\n+\t\t\thinfo-\u003eminor_hash = 0;\n \t\t}\n+\n \t\tif ((hinfo-\u003ehash \u003c start_hash) ||\n \t\t    ((hinfo-\u003ehash == start_hash) \u0026\u0026\n \t\t     (hinfo-\u003eminor_hash \u003c start_minor_hash)))\n@@ -1107,8 +1155,8 @@ static int htree_dirblock_to_tree(struct file *dir_file,\n \t\t\ttmp_str.name = de-\u003ename;\n \t\t\ttmp_str.len = de-\u003ename_len;\n \t\t\terr = ext4_htree_store_dirent(dir_file,\n-\t\t\t\t   hinfo-\u003ehash, hinfo-\u003eminor_hash, de,\n-\t\t\t\t   \u0026tmp_str);\n+\t\t\t\t   hinfo-\u003ehash, hinfo-\u003eminor_hash,\n+\t\t\t\t   dir-\u003ei_sb-\u003es_blocksize, de, \u0026tmp_str);\n \t\t} else {\n \t\t\tint save_len = fname_crypto_str.len;\n \t\t\tstruct fscrypt_str de_name = FSTR_INIT(de-\u003ename,\n@@ -1123,8 +1171,9 @@ static int htree_dirblock_to_tree(struct file *dir_file,\n \t\t\t\tgoto errout;\n \t\t\t}\n \t\t\terr = ext4_htree_store_dirent(dir_file,\n-\t\t\t\t   hinfo-\u003ehash, hinfo-\u003eminor_hash, de,\n-\t\t\t\t\t\u0026fname_crypto_str);\n+\t\t\t\t\t\thinfo-\u003ehash, hinfo-\u003eminor_hash,\n+\t\t\t\t\t\tdir-\u003ei_sb-\u003es_blocksize, de,\n+\t\t\t\t\t\t\u0026fname_crypto_str);\n \t\t\tfname_crypto_str.len = save_len;\n \t\t}\n \t\tif (err != 0) {\n@@ -1202,6 +1251,7 @@ int ext4_htree_fill_tree(struct file *dir_file, __u32 start_hash,\n \t\ttmp_str.name = de-\u003ename;\n \t\ttmp_str.len = de-\u003ename_len;\n \t\terr = ext4_htree_store_dirent(dir_file, 0, 0,\n+\t\t\t\t\t      dir-\u003ei_sb-\u003es_blocksize,\n \t\t\t\t\t      de, \u0026tmp_str);\n \t\tif (err != 0)\n \t\t\tgoto errout;\n@@ -1213,6 +1263,7 @@ int ext4_htree_fill_tree(struct file *dir_file, __u32 start_hash,\n \t\ttmp_str.name = de-\u003ename;\n \t\ttmp_str.len = de-\u003ename_len;\n \t\terr = ext4_htree_store_dirent(dir_file, 2, 0,\n+\t\t\t\t\t      dir-\u003ei_sb-\u003es_blocksize,\n \t\t\t\t\t      de, \u0026tmp_str);\n \t\tif (err != 0)\n \t\t\tgoto errout;\n@@ -1250,12 +1301,12 @@ int ext4_htree_fill_tree(struct file *dir_file, __u32 start_hash,\n \t\t    (count \u0026\u0026 ((hashval \u0026 1) == 0)))\n \t\t\tbreak;\n \t}\n-\tdx_release(frames);\n+\tdx_release(dir, frames);\n \tdxtrace(printk(KERN_DEBUG \"Fill tree: returned %d entries, \"\n \t\t       \"next hash: %x\\n\", count, *next_hash));\n \treturn count;\n errout:\n-\tdx_release(frames);\n+\tdx_release(dir, frames);\n \treturn (err);\n }\n \n@@ -1273,6 +1324,207 @@ static inline int search_dirblock(struct buffer_head *bh,\n  * Directory block splitting, compacting\n  */\n \n+/*\n+ * ext4_dirdata_get() - Read dirdata fields from a directory entry.\n+ * @de:         directory entry\n+ * @dir:        directory inode (used for fscrypt+casefold hash fallback)\n+ * @dfid:      if non-NULL and EXT4_DIRENT_LUFID is set, LUFID data is copied\n+ * \t\there\n+ * @hinfo:\tif non-NULL, receives the casefold hash and minor hash\n+ *\n+ * Reads any dirdata stored in @de.  If the dirdata feature is not enabled,\n+ * falls back to reading the hash stored inline after the filename (for\n+ * compatibility with the older casefold+fscrypt format).\n+ *\n+ * Returns a bitmask of EXT4_DIRENT_* flags indicating which fields were read.\n+ *\n+ * Compatibility note: enabling EXT4_FEATURE_INCOMPAT_DIRDATA on a filesystem\n+ * that already has casefolded+encrypted directories is NOT safe without a\n+ * prior migration pass.  Before dirdata, the casefold+fscrypt hash was stored\n+ * as a raw 8 bytes immediately after the filename with no flag in file_type.\n+ * After dirdata is enabled, this function expects the hash to be present only\n+ * when EXT4_DIRENT_CFHASH (0x40) is set in file_type; existing entries that\n+ * carry the raw hash are silently misread as having no hash at all, breaking\n+ * directory lookups.  e2fsck must be run to convert all affected entries to\n+ * the EXT4_DIRENT_CFHASH extension format before the feature flag is set with\n+ * tune2fs.  Detection heuristic: in a casefold+encrypted directory, an entry\n+ * with rec_len \u003e= round_up(name_len, 4) + 8 and no EXT4_DIRENT_CFHASH bit\n+ * carries a raw pre-dirdata hash that must be migrated.\n+ */\n+unsigned char ext4_dirdata_get(struct ext4_dir_entry_2 *de, struct inode *dir,\n+\t\t\t       int buf_size,\n+\t\t\t       struct ext4_dirent_fid *dfid,\n+\t\t\t       struct dx_hash_info *hinfo)\n+{\n+\tunsigned char ret = 0;\n+\tunsigned int data_offset = de-\u003ename_len + 1;\n+\tunsigned int rec_len = ext4_rec_len_from_disk(de-\u003erec_len, buf_size);\n+\n+\t/* data_offset is relative to de-\u003ename, which itself starts\n+\t * EXT4_BASE_DIR_LEN bytes into the entry -- rec_len is relative to\n+\t * the start of the entry, so add the header size before comparing,\n+\t * or this lets reads run EXT4_BASE_DIR_LEN bytes past the entry. */\n+\tif (EXT4_BASE_DIR_LEN + data_offset \u003e rec_len)\n+\t\treturn ret;\n+\n+\t/* compatibility: hash stored inline after filename (no dirdata) */\n+\tif (hinfo \u0026\u0026 !ext4_has_feature_dirdata(dir-\u003ei_sb) \u0026\u0026\n+\t    ext4_hash_in_dirent(dir)) {\n+\t\thinfo-\u003ehash = EXT4_DIRENT_HASH(de);\n+\t\thinfo-\u003eminor_hash = EXT4_DIRENT_MINOR_HASH(de);\n+\t\tret |= EXT4_DIRENT_CFHASH;\n+\t\treturn ret;\n+\t}\n+\n+\t/* EXT4_DIRENT_* bits are only meaningful when the feature is enabled */\n+\tif (!ext4_has_feature_dirdata(dir-\u003ei_sb))\n+\t\treturn ret;\n+\n+\tif (de-\u003efile_type \u0026 EXT4_DIRENT_LUFID) {\n+\t\tstruct ext4_dirent_fid *disk_fid =\n+\t\t\t(struct ext4_dirent_fid *)((char *)de +\n+\t\t\tEXT4_BASE_DIR_LEN + data_offset);\n+\t\tunsigned int dlen;\n+\t\t/* struct ext4_fid df_fid[] does not provide the array size.\n+\t\t * First, verify that the header lies within the valid area, then\n+ \t\t * verify that the entire record fits within it. */\n+\t\tif (EXT4_BASE_DIR_LEN + data_offset +\n+\t\t    sizeof(disk_fid-\u003edf_header) \u003e rec_len)\n+\t\t\treturn ret;\n+\n+\t\tdlen = disk_fid-\u003edf_header.ddh_length;\n+\t\tif (dlen == 0 ||\n+\t\t    EXT4_BASE_DIR_LEN + data_offset + dlen \u003e rec_len)\n+\t\t\treturn ret;\n+\n+\t\tif (dfid) {\n+\t\t\t/* The size cap below ensures the record holds at least\n+\t\t\t * one complete struct ext4_fid. */\n+\t\t\tif (dlen \u003e sizeof(struct ext4_dirent_data_header) +\n+\t\t\t\t    sizeof(struct ext4_fid))\n+\t\t\t\treturn ret;\n+\t\t\tmemcpy(dfid, disk_fid, dlen);\n+\t\t\tret |= EXT4_DIRENT_LUFID;\n+\t\t}\n+\t\tdata_offset += dlen;\n+\t}\n+\n+\t/* Skip INO64 for now*/\n+\tif (de-\u003efile_type \u0026 EXT4_DIRENT_INO64) {\n+\t\tstruct ext4_dirent_data_header *ddh =\n+\t\t       (struct ext4_dirent_data_header *)((char *)de + EXT4_BASE_DIR_LEN + data_offset);\n+\t\tunsigned int dlen;\n+\n+\t\tif (EXT4_BASE_DIR_LEN + data_offset + sizeof(*ddh) \u003e rec_len)\n+\t\t\treturn ret;\n+\n+\t\tdlen = ddh-\u003eddh_length;\n+\t\tif (dlen \u003c sizeof(*ddh) ||\n+\t\t    EXT4_BASE_DIR_LEN + data_offset + dlen \u003e rec_len)\n+\t\t\treturn ret;\n+\n+\t\tdata_offset += dlen;\n+\t}\n+\n+\tif (!hinfo)\n+\t\treturn ret;\n+\n+\tif (de-\u003efile_type \u0026 EXT4_DIRENT_CFHASH) {\n+\t\tstruct ext4_dirent_hash *dh =\n+\t\t\t(struct ext4_dirent_hash *)((char *)de + EXT4_BASE_DIR_LEN + data_offset);\n+\t\tunsigned int dlen;\n+\n+\t\tif (EXT4_BASE_DIR_LEN + data_offset + sizeof(*dh) \u003e rec_len)\n+\t\t\treturn ret;\n+\t\tdlen = dh-\u003edh_header.ddh_length;\n+\t\tif (dlen \u003c sizeof(*dh) ||\n+\t\t    EXT4_BASE_DIR_LEN + data_offset + dlen \u003e rec_len)\n+\t\t\treturn ret;\n+\n+\t\thinfo-\u003ehash = le32_to_cpu(dh-\u003edh_hash.hash);\n+\t\thinfo-\u003eminor_hash = le32_to_cpu(dh-\u003edh_hash.minor_hash);\n+\t\tret |= EXT4_DIRENT_CFHASH;\n+\t}\n+\n+\treturn ret;\n+}\n+\n+/*\n+ * ext4_dirdata_set() - Write dirdata fields into a directory entry.\n+ * @de:    directory entry (name must already be set)\n+ * @dir:   directory inode\n+ * @data:  LUFID data to store (or NULL)\n+ * @fname: filename info carrying the casefold hash\n+ *\n+ * Writes any required dirdata into @de after the filename.  If the dirdata\n+ * feature is not enabled, falls back to writing the hash inline after the\n+ * filename (for compatibility with the older casefold+fscrypt format).\n+ *\n+ * See ext4_dirdata_get() for the compatibility constraint: enabling the\n+ * dirdata feature on a filesystem with existing casefolded+encrypted\n+ * directories requires an e2fsck migration pass before tune2fs sets the\n+ * EXT4_FEATURE_INCOMPAT_DIRDATA superblock flag.\n+ */\n+static void ext4_dirdata_set(struct ext4_dir_entry_2 *de, struct inode *dir,\n+\t\t\t     struct ext4_dirent_fid *dfid,\n+\t\t\t     struct ext4_filename *fname)\n+{\n+\tstruct dx_hash_info *hinfo = \u0026fname-\u003ehinfo;\n+\tunsigned int data_offset = de-\u003ename_len + 1;\n+\tunsigned int rec_len = ext4_rec_len_from_disk(de-\u003erec_len,\n+\t\t\t\t\t\t       dir-\u003ei_sb-\u003es_blocksize);\n+\n+\t/* Clear the gap byte between the filename and the first dirdata\n+\t * extension to avoid leaking stale memory to disk.  Use pointer\n+\t * arithmetic rather than de-\u003ename[name_len] to stay within the\n+\t * declared name[] array bounds under FORTIFY_SOURCE.  Only write it\n+\t * when there is actually room (entries that exactly fill their slot\n+\t * have rec_len == EXT4_BASE_DIR_LEN + name_len with no gap). */\n+\tif (EXT4_BASE_DIR_LEN + data_offset \u003c= rec_len)\n+\t\t*((char *)de + EXT4_BASE_DIR_LEN + de-\u003ename_len) = 0;\n+\n+\tif (dfid) {\n+\t\tunsigned int dlen = dfid-\u003edf_header.ddh_length;\n+\n+\t\tif (EXT4_BASE_DIR_LEN + data_offset + dlen \u003e rec_len) {\n+\t\t\tEXT4_ERROR_INODE(dir, \"Can not insert FID\");\n+\t\t\treturn;\n+\t\t}\n+\n+\t\tmemcpy((char *)de + EXT4_BASE_DIR_LEN + data_offset, dfid, dlen);\n+\t\tde-\u003efile_type |= EXT4_DIRENT_LUFID;\n+\t\tdata_offset += dlen;\n+\t}\n+\n+\tif (ext4_hash_in_dirent(dir)) {\n+\t\tif (ext4_has_feature_dirdata(dir-\u003ei_sb)) {\n+\t\t\tstruct ext4_dirent_hash *dh =\n+\t\t\t    (struct ext4_dirent_hash *)((char *)de + EXT4_BASE_DIR_LEN + data_offset);\n+\n+\t\t\tif (EXT4_BASE_DIR_LEN + data_offset + sizeof(*dh) \u003e rec_len) {\n+\t\t\t\tEXT4_ERROR_INODE(dir, \"Can not insert dhash dirdata\");\n+\t\t\t\treturn;\n+\t\t\t}\n+\n+\t\t\tdh-\u003edh_header.ddh_length = sizeof(*dh);\n+\t\t\tdh-\u003edh_hash.hash = cpu_to_le32(hinfo-\u003ehash);\n+\t\t\tdh-\u003edh_hash.minor_hash = cpu_to_le32(hinfo-\u003eminor_hash);\n+\t\t\tde-\u003efile_type |= EXT4_DIRENT_CFHASH;\n+\t\t} else {\n+\t\t\t/* Compatibility: store hash inline after filename */\n+\t\t\tif (EXT4_BASE_DIR_LEN + data_offset +\n+\t\t\t    sizeof(struct ext4_dir_entry_hash) \u003e rec_len) {\n+\t\t\t\tEXT4_ERROR_INODE(dir, \"Can not insert dhash\");\n+\t\t\t\treturn;\n+\t\t\t}\n+\n+\t\t\tEXT4_DIRENT_HASHES(de)-\u003ehash = cpu_to_le32(hinfo-\u003ehash);\n+\t\t\tEXT4_DIRENT_HASHES(de)-\u003eminor_hash =\n+\t\t\t\t\t\tcpu_to_le32(hinfo-\u003eminor_hash);\n+\t\t}\n+\t}\n+}\n+\n /*\n  * Create map of hash values, offsets, and sizes, stored at end of block.\n  * Returns number of entries mapped.\n@@ -1296,9 +1548,9 @@ static int dx_make_map(struct inode *dir, struct buffer_head *bh,\n \t\t\t\t\t ((char *)de) - base))\n \t\t\treturn -EFSCORRUPTED;\n \t\tif (de-\u003ename_len \u0026\u0026 de-\u003einode) {\n-\t\t\tif (ext4_hash_in_dirent(dir))\n-\t\t\t\th.hash = EXT4_DIRENT_HASH(de);\n-\t\t\telse {\n+\t\t\tif (!(ext4_dirdata_get(de, dir, dir-\u003ei_sb-\u003es_blocksize,\n+\t\t\t\t\t       NULL, \u0026h) \u0026\n+\t\t\t\t\t\tEXT4_DIRENT_CFHASH)) {\n \t\t\t\tint err = ext4fs_dirhash(dir, de-\u003ename,\n \t\t\t\t\t\t     de-\u003ename_len, \u0026h);\n \t\t\t\tif (err \u003c 0)\n@@ -1488,7 +1740,7 @@ int ext4_search_dir(struct buffer_head *bh, char *search_buf, int buf_size,\n }\n \n static int is_dx_internal_node(struct inode *dir, ext4_lblk_t block,\n-\t\t\t       struct ext4_dir_entry *de)\n+\t\t\t       struct ext4_dir_entry_2 *de)\n {\n \tstruct super_block *sb = dir-\u003ei_sb;\n \n@@ -1619,7 +1871,7 @@ static struct buffer_head *__ext4_find_entry(struct inode *dir,\n \t\t}\n \t\tif (!buffer_verified(bh) \u0026\u0026\n \t\t    !is_dx_internal_node(dir, block,\n-\t\t\t\t\t (struct ext4_dir_entry *)bh-\u003eb_data) \u0026\u0026\n+\t\t\t\t\t (struct ext4_dir_entry_2 *)bh-\u003eb_data) \u0026\u0026\n \t\t    !ext4_dirblock_csum_verify(dir, bh)) {\n \t\t\tEXT4_ERROR_INODE_ERR(dir, EFSBADCRC,\n \t\t\t\t\t     \"checksumming directory \"\n@@ -1753,7 +2005,7 @@ static struct buffer_head * ext4_dx_find_entry(struct inode *dir,\n errout:\n \tdxtrace(printk(KERN_DEBUG \"%s not found\\n\", fname-\u003eusr_fname-\u003ename));\n success:\n-\tdx_release(frames);\n+\tdx_release(dir, frames);\n \treturn bh;\n }\n \n@@ -1850,7 +2102,7 @@ dx_move_dirents(struct inode *dir, char *from, char *to,\n \twhile (count--) {\n \t\tstruct ext4_dir_entry_2 *de = (struct ext4_dir_entry_2 *)\n \t\t\t\t\t\t(from + (map-\u003eoffs\u003c\u003c2));\n-\t\trec_len = ext4_dir_rec_len(de-\u003ename_len, dir);\n+\t\trec_len = ext4_dir_entry_len(de, blocksize, dir);\n \n \t\tmemcpy (to, de, rec_len);\n \t\t((struct ext4_dir_entry_2 *) to)-\u003erec_len =\n@@ -1883,7 +2135,7 @@ static struct ext4_dir_entry_2 *dx_pack_dirents(struct inode *dir, char *base,\n \twhile ((char*)de \u003c base + blocksize) {\n \t\tnext = ext4_next_entry(de, blocksize);\n \t\tif (de-\u003einode \u0026\u0026 de-\u003ename_len) {\n-\t\t\trec_len = ext4_dir_rec_len(de-\u003ename_len, dir);\n+\t\t\trec_len = ext4_dir_entry_len(de, blocksize, dir);\n \t\t\tif (de \u003e to)\n \t\t\t\tmemmove(to, de, rec_len);\n \t\t\tto-\u003erec_len = ext4_rec_len_to_disk(rec_len, blocksize);\n@@ -2035,10 +2287,15 @@ static struct ext4_dir_entry_2 *do_split(handle_t *handle, struct inode *dir,\n int ext4_find_dest_de(struct inode *dir, struct buffer_head *bh,\n \t\t      void *buf, int buf_size,\n \t\t      struct ext4_filename *fname,\n-\t\t      struct ext4_dir_entry_2 **dest_de)\n+\t\t      struct ext4_dir_entry_2 **dest_de,\n+\t\t      int dlen)\n {\n \tstruct ext4_dir_entry_2 *de;\n-\tunsigned short reclen = ext4_dir_rec_len(fname_len(fname), dir);\n+\t/* When dirdata extensions are present, a NUL gap byte sits between\n+\t * the filename and the first extension; account for it so the found\n+\t * slot is always large enough for ext4_dirdata_set() to write into. */\n+\tunsigned short reclen = ext4_dirent_rec_len(fname_len(fname) + dlen +\n+\t\t\t\t\t\t    (dlen ? 1 : 0), dir);\n \tint nlen, rlen;\n \tunsigned int offset = 0;\n \tchar *top;\n@@ -2051,7 +2308,7 @@ int ext4_find_dest_de(struct inode *dir, struct buffer_head *bh,\n \t\t\treturn -EFSCORRUPTED;\n \t\tif (ext4_match(dir, fname, de))\n \t\t\treturn -EEXIST;\n-\t\tnlen = ext4_dir_rec_len(de-\u003ename_len, dir);\n+\t\tnlen = ext4_dir_entry_len(de, buf_size, dir);\n \t\trlen = ext4_rec_len_from_disk(de-\u003erec_len, buf_size);\n \t\tif ((de-\u003einode ? rlen - nlen : rlen) \u003e= reclen)\n \t\t\tbreak;\n@@ -2065,16 +2322,13 @@ int ext4_find_dest_de(struct inode *dir, struct buffer_head *bh,\n \treturn 0;\n }\n \n-void ext4_insert_dentry(struct inode *dir,\n-\t\t\tstruct inode *inode,\n-\t\t\tstruct ext4_dir_entry_2 *de,\n-\t\t\tint buf_size,\n-\t\t\tstruct ext4_filename *fname)\n+void ext4_insert_dentry_data(struct inode *dir, struct inode *inode,\n+\t\t\t     struct ext4_dir_entry_2 *de, int buf_size,\n+\t\t\t     struct ext4_filename *fname, void *data)\n {\n-\n \tint nlen, rlen;\n \n-\tnlen = ext4_dir_rec_len(de-\u003ename_len, dir);\n+\tnlen = ext4_dir_entry_len(de, buf_size, dir);\n \trlen = ext4_rec_len_from_disk(de-\u003erec_len, buf_size);\n \tif (de-\u003einode) {\n \t\tstruct ext4_dir_entry_2 *de1 =\n@@ -2088,13 +2342,7 @@ void ext4_insert_dentry(struct inode *dir,\n \text4_set_de_type(inode-\u003ei_sb, de, inode-\u003ei_mode);\n \tde-\u003ename_len = fname_len(fname);\n \tmemcpy(de-\u003ename, fname_name(fname), fname_len(fname));\n-\tif (ext4_hash_in_dirent(dir)) {\n-\t\tstruct dx_hash_info *hinfo = \u0026fname-\u003ehinfo;\n-\n-\t\tEXT4_DIRENT_HASHES(de)-\u003ehash = cpu_to_le32(hinfo-\u003ehash);\n-\t\tEXT4_DIRENT_HASHES(de)-\u003eminor_hash =\n-\t\t\t\t\t\tcpu_to_le32(hinfo-\u003eminor_hash);\n-\t}\n+\text4_dirdata_set(de, dir, data, fname);\n }\n \n /*\n@@ -2112,14 +2360,28 @@ static int add_dirent_to_buf(handle_t *handle, struct ext4_filename *fname,\n {\n \tunsigned int\tblocksize = dir-\u003ei_sb-\u003es_blocksize;\n \tint\t\tcsum_size = 0;\n-\tint\t\terr, err2;\n+\tint\t\terr, err2, dlen = 0;\n+\tstruct ext4_dirent_fid *dfid;\n \n \tif (ext4_has_feature_metadata_csum(inode-\u003ei_sb))\n \t\tcsum_size = sizeof(struct ext4_dir_entry_tail);\n \n+\t/* dfid is resolved once by ext4_add_entry() and stored in fname so\n+\t * that all add_dirent_to_buf() calls within the same add operation\n+\t * use a consistent value without touching the shared i_dirdata field. */\n+\tdfid = fname-\u003edfid;\n \tif (!de) {\n+\t\tif (dfid)\n+\t\t\tdlen = dfid-\u003edf_header.ddh_length;\n+\t\t/* ext4_dirent_rec_len() does not add CFHASH size when dirdata is\n+\t\t * enabled (it assumes the caller included it in name_len).  Add\n+\t\t * it here so ext4_find_dest_de() allocates a slot large enough\n+\t\t * for ext4_dirdata_set() to write the hash extension. */\n+\t\tif (ext4_hash_in_dirent(dir) \u0026\u0026\n+\t\t    ext4_has_feature_dirdata(dir-\u003ei_sb))\n+\t\t\tdlen += sizeof(struct ext4_dirent_hash);\n \t\terr = ext4_find_dest_de(dir, bh, bh-\u003eb_data,\n-\t\t\t\t\tblocksize - csum_size, fname, \u0026de);\n+\t\t\t\t\tblocksize - csum_size, fname, \u0026de, dlen);\n \t\tif (err)\n \t\t\treturn err;\n \t}\n@@ -2132,7 +2394,7 @@ static int add_dirent_to_buf(handle_t *handle, struct ext4_filename *fname,\n \t}\n \n \t/* By now the buffer is marked for journaling */\n-\text4_insert_dentry(dir, inode, de, blocksize, fname);\n+\text4_insert_dentry_data(dir, inode, de, blocksize, fname, dfid);\n \n \t/*\n \t * XXX shouldn't update any times until successful\n@@ -2156,44 +2418,38 @@ static int add_dirent_to_buf(handle_t *handle, struct ext4_filename *fname,\n \treturn err ? err : err2;\n }\n \n-static bool ext4_check_dx_root(struct inode *dir, struct dx_root *root)\n+static bool ext4_check_dx_root(struct inode *dir,\n+\t\t\t       struct ext4_dir_entry_2 *dot_de,\n+\t\t\t       struct ext4_dir_entry_2 *dotdot_de,\n+\t\t\t       struct ext4_dir_entry_2 **entry)\n {\n-\tstruct fake_dirent *fde;\n \tconst char *error_msg;\n-\tunsigned int rlen;\n \tunsigned int blocksize = dir-\u003ei_sb-\u003es_blocksize;\n-\tchar *blockend = (char *)root + dir-\u003ei_sb-\u003es_blocksize;\n+\tstruct ext4_dir_entry_2 *de = NULL;\n \n-\tfde = \u0026root-\u003edot;\n-\tif (unlikely(fde-\u003ename_len != 1)) {\n+\tif (unlikely(dot_de-\u003ename_len != 1)) {\n \t\terror_msg = \"invalid name_len for '.'\";\n \t\tgoto corrupted;\n \t}\n-\tif (unlikely(strncmp(root-\u003edot_name, \".\", fde-\u003ename_len))) {\n+\tif (unlikely(strncmp(dot_de-\u003ename, \".\", dot_de-\u003ename_len))) {\n \t\terror_msg = \"invalid name for '.'\";\n \t\tgoto corrupted;\n \t}\n-\trlen = ext4_rec_len_from_disk(fde-\u003erec_len, blocksize);\n-\tif (unlikely((char *)fde + rlen \u003e= blockend)) {\n-\t\terror_msg = \"invalid rec_len for '.'\";\n-\t\tgoto corrupted;\n-\t}\n \n-\tfde = \u0026root-\u003edotdot;\n-\tif (unlikely(fde-\u003ename_len != 2)) {\n+\tif (unlikely(dotdot_de-\u003ename_len != 2)) {\n \t\terror_msg = \"invalid name_len for '..'\";\n \t\tgoto corrupted;\n \t}\n-\tif (unlikely(strncmp(root-\u003edotdot_name, \"..\", fde-\u003ename_len))) {\n+\tif (unlikely(strncmp(dotdot_de-\u003ename, \"..\", dotdot_de-\u003ename_len))) {\n \t\terror_msg = \"invalid name for '..'\";\n \t\tgoto corrupted;\n \t}\n-\trlen = ext4_rec_len_from_disk(fde-\u003erec_len, blocksize);\n-\tif (unlikely((char *)fde + rlen \u003e= blockend)) {\n+\tde = ext4_next_entry(dotdot_de, blocksize);\n+\tif ((char *)de \u003e= (((char *)dot_de) + blocksize)) {\n \t\terror_msg = \"invalid rec_len for '..'\";\n \t\tgoto corrupted;\n \t}\n-\n+\t*entry = de;\n \treturn true;\n \n corrupted:\n@@ -2211,16 +2467,15 @@ static int make_indexed_dir(handle_t *handle, struct ext4_filename *fname,\n \t\t\t    struct inode *inode, struct buffer_head *bh)\n {\n \tstruct buffer_head *bh2;\n-\tstruct dx_root\t*root;\n \tstruct dx_frame\tframes[EXT4_HTREE_LEVEL], *frame;\n \tstruct dx_entry *entries;\n-\tstruct ext4_dir_entry_2\t*de, *de2;\n+\tstruct ext4_dir_entry_2\t*de, *de2, *dot_de, *dotdot_de;\n \tchar\t\t*data2, *top;\n \tunsigned\tlen;\n \tint\t\tretval;\n \tunsigned\tblocksize;\n \text4_lblk_t  block;\n-\tstruct fake_dirent *fde;\n+\tstruct dx_root_info *dx_info;\n \tint csum_size = 0;\n \n \tif (ext4_has_feature_metadata_csum(inode-\u003ei_sb))\n@@ -2237,17 +2492,20 @@ static int make_indexed_dir(handle_t *handle, struct ext4_filename *fname,\n \t\treturn retval;\n \t}\n \n-\troot = (struct dx_root *) bh-\u003eb_data;\n-\tif (!ext4_check_dx_root(dir, root)) {\n+\tdot_de = (struct ext4_dir_entry_2 *)bh-\u003eb_data;\n+\tif (ext4_rec_len_from_disk(dot_de-\u003erec_len, blocksize) \u003c EXT4_BASE_DIR_LEN ||\n+\t    ext4_rec_len_from_disk(dot_de-\u003erec_len, blocksize) \u003e= blocksize - EXT4_BASE_DIR_LEN) {\n+\t\tbrelse(bh);\n+\t\treturn -EFSCORRUPTED;\n+\t}\n+\tdotdot_de = ext4_next_entry(dot_de, blocksize);\n+\tif (!ext4_check_dx_root(dir, dot_de, dotdot_de, \u0026de)) {\n \t\tbrelse(bh);\n \t\treturn -EFSCORRUPTED;\n \t}\n \n \t/* The 0th block becomes the root, move the dirents out */\n-\tfde = \u0026root-\u003edotdot;\n-\tde = (struct ext4_dir_entry_2 *)((char *)fde +\n-\t\text4_rec_len_from_disk(fde-\u003erec_len, blocksize));\n-\tlen = ((char *) root) + (blocksize - csum_size) - (char *) de;\n+\tlen = ((char *)dot_de) + (blocksize - csum_size) - (char *)de;\n \n \t/* Allocate new block for the 0th block's dirents */\n \tbh2 = ext4_append(handle, dir, \u0026block);\n@@ -2278,24 +2536,32 @@ static int make_indexed_dir(handle_t *handle, struct ext4_filename *fname,\n \t\text4_initialize_dirent_tail(bh2, blocksize);\n \n \t/* Initialize the root; the dot dirents already exist */\n-\tde = (struct ext4_dir_entry_2 *) (\u0026root-\u003edotdot);\n-\tde-\u003erec_len = ext4_rec_len_to_disk(\n-\t\t\tblocksize - ext4_dir_rec_len(2, NULL), blocksize);\n-\tmemset (\u0026root-\u003einfo, 0, sizeof(root-\u003einfo));\n-\troot-\u003einfo.info_length = sizeof(root-\u003einfo);\n+\tdotdot_de-\u003erec_len =\n+\t\text4_rec_len_to_disk(blocksize - le16_to_cpu(dot_de-\u003erec_len),\n+\t\t\t\t     blocksize);\n+\n+\t/* initialize hashing info */\n+\tdx_info = dx_get_dx_info(dir, dot_de);\n+\tif (IS_ERR(dx_info)) {\n+\t\tbrelse(bh2);\n+\t\tbrelse(bh);\n+\t\treturn PTR_ERR(dx_info);\n+\t}\n+\tmemset(dx_info, 0, sizeof(*dx_info));\n+\tdx_info-\u003einfo_length = sizeof(*dx_info);\n \tif (ext4_hash_in_dirent(dir))\n-\t\troot-\u003einfo.hash_version = DX_HASH_SIPHASH;\n+\t\tdx_info-\u003ehash_version = DX_HASH_SIPHASH;\n \telse\n-\t\troot-\u003einfo.hash_version =\n+\t\tdx_info-\u003ehash_version =\n \t\t\t\tEXT4_SB(dir-\u003ei_sb)-\u003es_def_hash_version;\n \n-\tentries = root-\u003eentries;\n+\tentries = (void *)dx_info + dx_info-\u003einfo_length;\n \tdx_set_block(entries, 1);\n \tdx_set_count(entries, 1);\n-\tdx_set_limit(entries, dx_root_limit(dir, sizeof(root-\u003einfo)));\n+\tdx_set_limit(entries, dx_root_limit(dir, dot_de));\n \n \t/* Initialize as for dx_probe */\n-\tfname-\u003ehinfo.hash_version = root-\u003einfo.hash_version;\n+\tfname-\u003ehinfo.hash_version = dx_info-\u003ehash_version;\n \tif (fname-\u003ehinfo.hash_version \u003c= DX_HASH_TEA)\n \t\tfname-\u003ehinfo.hash_version += EXT4_SB(dir-\u003ei_sb)-\u003es_hash_unsigned;\n \tfname-\u003ehinfo.seed = EXT4_SB(dir-\u003ei_sb)-\u003es_hash_seed;\n@@ -2338,7 +2604,7 @@ static int make_indexed_dir(handle_t *handle, struct ext4_filename *fname,\n \t */\n \tif (retval)\n \t\text4_mark_inode_dirty(handle, dir);\n-\tdx_release(frames);\n+\tdx_release(dir, frames);\n \tbrelse(bh2);\n \treturn retval;\n }\n@@ -2355,7 +2621,8 @@ static int make_indexed_dir(handle_t *handle, struct ext4_filename *fname,\n  */\n static int __ext4_add_entry(handle_t *handle, struct inode *dir,\n \t\t\t  const struct qstr *d_name,\n-\t\t\t  struct inode *inode)\n+\t\t\t  struct inode *inode,\n+\t\t\t  struct ext4_dentry_param *edp)\n {\n \tstruct buffer_head *bh = NULL;\n \tstruct ext4_dir_entry_2 *de;\n@@ -2380,6 +2647,13 @@ static int __ext4_add_entry(handle_t *handle, struct inode *dir,\n \tif (retval)\n \t\treturn retval;\n \n+\t/* Resolve the LUFID payload once here and store it in fname so every\n+\t * add_dirent_to_buf() call in this add operation uses the same value\n+\t * without touching the shared EXT4_I(inode)-\u003ei_dirdata field. */\n+\tfname.dfid = ext4_dentry_get_fid(sb, edp);\n+\tif (fname.dfid)\n+\t\text4_fc_mark_ineligible(sb, EXT4_FC_REASON_DIRDATA, handle);\n+\n \tif (ext4_has_inline_data(dir)) {\n \t\tretval = ext4_try_add_inline_entry(handle, \u0026fname, dir, inode);\n \t\tif (retval \u003c 0)\n@@ -2464,7 +2738,8 @@ static int ext4_add_entry(handle_t *handle, struct dentry *dentry,\n \n \tif (fscrypt_is_nokey_name(dentry))\n \t\treturn -ENOKEY;\n-\treturn __ext4_add_entry(handle, dir, \u0026dentry-\u003ed_name, inode);\n+\treturn __ext4_add_entry(handle, dir, \u0026dentry-\u003ed_name, inode,\n+\t\t\t\tdentry-\u003ed_fsdata);\n }\n \n /*\n@@ -2605,7 +2880,7 @@ static int ext4_dx_add_entry(handle_t *handle, struct ext4_filename *fname,\n \t\t\tif (restart || err)\n \t\t\t\tgoto journal_error;\n \t\t} else {\n-\t\t\tstruct dx_root *dxroot;\n+\t\t\tstruct dx_root_info *info;\n \t\t\tmemcpy((char *) entries2, (char *) entries,\n \t\t\t       icount * sizeof(struct dx_entry));\n \t\t\tdx_set_limit(entries2, dx_node_limit(dir));\n@@ -2613,11 +2888,17 @@ static int ext4_dx_add_entry(handle_t *handle, struct ext4_filename *fname,\n \t\t\t/* Set up root */\n \t\t\tdx_set_count(entries, 1);\n \t\t\tdx_set_block(entries + 0, newblock);\n-\t\t\tdxroot = (struct dx_root *)frames[0].bh-\u003eb_data;\n-\t\t\tdxroot-\u003einfo.indirect_levels += 1;\n+\t\t\tinfo = dx_get_dx_info(dir, (struct ext4_dir_entry_2 *)\n+\t\t\t\t\t      frames[0].bh-\u003eb_data);\n+\t\t\tif (IS_ERR(info)) {\n+\t\t\t\terr = PTR_ERR(info);\n+\t\t\t\tbrelse(bh2);\n+\t\t\t\tgoto journal_error;\n+\t\t\t}\n+\t\t\tinfo-\u003eindirect_levels += 1;\n \t\t\tdxtrace(printk(KERN_DEBUG\n \t\t\t\t       \"Creating %d level index...\\n\",\n-\t\t\t\t       dxroot-\u003einfo.indirect_levels));\n+\t\t\t\t       info-\u003eindirect_levels));\n \t\t\terr = ext4_handle_dirty_dx_node(handle, dir, frame-\u003ebh);\n \t\t\tif (err) {\n \t\t\t\tbrelse(bh2);\n@@ -2641,7 +2922,7 @@ static int ext4_dx_add_entry(handle_t *handle, struct ext4_filename *fname,\n \text4_std_error(dir-\u003ei_sb, err); /* this is a no-op if err == 0 */\n cleanup:\n \tbrelse(bh);\n-\tdx_release(frames);\n+\tdx_release(dir, frames);\n \t/* @restart is true means htree-path has been changed, we need to\n \t * repeat dx_probe() to find out valid htree-path\n \t */\n@@ -2933,7 +3214,7 @@ int ext4_init_dirblock(handle_t *handle, struct inode *inode,\n \n \tde-\u003einode = cpu_to_le32(inode-\u003ei_ino);\n \tde-\u003ename_len = 1;\n-\tde-\u003erec_len = ext4_rec_len_to_disk(ext4_dir_rec_len(de-\u003ename_len, NULL),\n+\tde-\u003erec_len = ext4_rec_len_to_disk(ext4_dirent_rec_len(de-\u003ename_len, NULL),\n \t\t\t\t\t   blocksize);\n \tmemcpy(de-\u003ename, \".\", 2);\n \text4_set_de_type(inode-\u003ei_sb, de, S_IFDIR);\n@@ -2945,7 +3226,7 @@ int ext4_init_dirblock(handle_t *handle, struct inode *inode,\n \text4_set_de_type(inode-\u003ei_sb, de, S_IFDIR);\n \tif (inline_buf) {\n \t\tde-\u003erec_len = ext4_rec_len_to_disk(\n-\t\t\t\t\text4_dir_rec_len(de-\u003ename_len, NULL),\n+\t\t\t\t\text4_dirent_rec_len(de-\u003ename_len, NULL),\n \t\t\t\t\tblocksize);\n \t\tde = ext4_next_entry(de, blocksize);\n \t\theader_size = (char *)de - bh-\u003eb_data;\n@@ -2954,7 +3235,7 @@ int ext4_init_dirblock(handle_t *handle, struct inode *inode,\n \t\t\tblocksize - csum_size);\n \t} else {\n \t\tde-\u003erec_len = ext4_rec_len_to_disk(blocksize -\n-\t\t\t\t\t(csum_size + ext4_dir_rec_len(1, NULL)),\n+\t\t\t\t\t(csum_size + ext4_dirent_rec_len(1, NULL)),\n \t\t\t\t\tblocksize);\n \t}\n \n@@ -2966,8 +3247,9 @@ int ext4_init_dirblock(handle_t *handle, struct inode *inode,\n \treturn ext4_handle_dirty_dirblock(handle, inode, bh);\n }\n \n-int ext4_init_new_dir(handle_t *handle, struct inode *dir,\n-\t\t\t     struct inode *inode)\n+int ext4_init_new_dir_data(handle_t *handle, struct inode *dir,\n+\t\t\t   struct inode *inode,\n+\t\t\t   const void *data1, const void *data2)\n {\n \tstruct buffer_head *dir_block = NULL;\n \text4_lblk_t block = 0;\n@@ -3077,8 +3359,8 @@ bool ext4_empty_dir(struct inode *inode)\n \t}\n \n \tsb = inode-\u003ei_sb;\n-\tif (inode-\u003ei_size \u003c ext4_dir_rec_len(1, NULL) +\n-\t\t\t\t\text4_dir_rec_len(2, NULL)) {\n+\tif (inode-\u003ei_size \u003c ext4_dirent_rec_len(1, NULL) +\n+\t\t\t\t\text4_dirent_rec_len(2, NULL)) {\n \t\tEXT4_ERROR_INODE(inode, \"invalid size\");\n \t\treturn false;\n \t}\n@@ -3467,10 +3749,16 @@ int __ext4_link(struct inode *dir, struct inode *inode,\n \tif (IS_DIRSYNC(dir))\n \t\text4_handle_sync(handle);\n \n+\t/* Fast-commit does not record dirdata in its journal format; force a\n+\t * full journal commit so the LUFID survives crash+replay. */\n+\tif (ext4_dentry_get_fid(dir-\u003ei_sb, dentry ? dentry-\u003ed_fsdata : NULL))\n+\t\text4_fc_mark_ineligible(dir-\u003ei_sb, EXT4_FC_REASON_DIRDATA, handle);\n+\n \tinode_set_ctime_current(inode);\n \text4_inc_count(inode);\n \n-\terr = __ext4_add_entry(handle, dir, d_name, inode);\n+\terr = __ext4_add_entry(handle, dir, d_name, inode,\n+\t\t\t       dentry ? dentry-\u003ed_fsdata : NULL);\n \tif (!err) {\n \t\terr = ext4_mark_inode_dirty(handle, inode);\n \t\t/* this can happen only for tmpfile being\n@@ -3639,7 +3927,8 @@ static int ext4_rename_dir_finish(handle_t *handle, struct ext4_renament *ent,\n }\n \n static int ext4_setent(handle_t *handle, struct ext4_renament *ent,\n-\t\t       unsigned ino, unsigned file_type)\n+\t\t       unsigned ino, unsigned file_type,\n+\t\t       const struct ext4_dirent_fid *src_fid)\n {\n \tint retval, retval2;\n \n@@ -3649,8 +3938,57 @@ static int ext4_setent(handle_t *handle, struct ext4_renament *ent,\n \tif (retval)\n \t\treturn retval;\n \tent-\u003ede-\u003einode = cpu_to_le32(ino);\n-\tif (ext4_has_feature_filetype(ent-\u003edir-\u003ei_sb))\n-\t\tent-\u003ede-\u003efile_type = file_type;\n+\tif (ext4_has_feature_filetype(ent-\u003edir-\u003ei_sb)) {\n+\t\t/* Copy the source LUFID payload into the destination slot when\n+\t\t * both carry a LUFID of the same on-disk size.  LUFID is\n+\t\t * inode-specific and must travel with the inode through rename.\n+\t\t * CFHASH bytes are left in-place: CFHASH is a function of the\n+\t\t * destination filename, and its bytes sit at the correct offset\n+\t\t * because LUFID always precedes CFHASH in the extension layout.\n+\t\t * When src_fid is NULL, the LUFID flag is synced to the LUFID\n+\t\t * bit already present in file_type: callers that clear LUFID\n+\t\t * (rename of a non-LUFID inode, whiteout) pass file_type with\n+\t\t * LUFID=0; ext4_resetent passes the original file_type so the\n+\t\t * flag is restored for error recovery without overwriting the\n+\t\t * LUFID bytes that are still intact on disk. */\n+\t\tif (ext4_has_feature_dirdata(ent-\u003edir-\u003ei_sb)) {\n+\t\t\tif (src_fid \u0026\u0026 (ent-\u003ede-\u003efile_type \u0026 EXT4_DIRENT_LUFID)) {\n+\t\t\t\tunsigned int rec_len =\n+\t\t\t\t\text4_rec_len_from_disk(ent-\u003ede-\u003erec_len,\n+\t\t\t\t\t\tent-\u003edir-\u003ei_sb-\u003es_blocksize);\n+\t\t\t\tunsigned int ddh_off =\n+\t\t\t\t\tEXT4_BASE_DIR_LEN + ent-\u003ede-\u003ename_len + 1;\n+\t\t\t\tstruct ext4_dirent_data_header *ddh =\n+\t\t\t\t\t(struct ext4_dirent_data_header *)\n+\t\t\t\t\t((char *)ent-\u003ede + ddh_off);\n+\t\t\t\tunsigned int copy_len = src_fid-\u003edf_header.ddh_length;\n+\n+\t\t\t\tif (ddh_off + sizeof(*ddh) \u003e rec_len ||\n+\t\t\t\t    ddh-\u003eddh_length != copy_len ||\n+\t\t\t\t    ddh_off + copy_len \u003e rec_len) {\n+\t\t\t\t\t/* Cannot copy: clear the flag so the\n+\t\t\t\t\t * slot does not advertise a stale LUFID\n+\t\t\t\t\t * from the old inode. */\n+\t\t\t\t\tent-\u003ede-\u003efile_type \u0026= ~EXT4_DIRENT_LUFID;\n+\t\t\t\t} else {\n+\t\t\t\t\tmemcpy(ddh, src_fid, copy_len);\n+\t\t\t\t}\n+\t\t\t} else if (!src_fid) {\n+\t\t\t\t/* Sync the LUFID flag with what file_type requests.\n+\t\t\t\t * For normal rename (source has no LUFID) and for\n+\t\t\t\t * the whiteout setent, file_type carries LUFID=0,\n+\t\t\t\t * so we clear the stale flag.  For ext4_resetent\n+\t\t\t\t * (error recovery), file_type is the original\n+\t\t\t\t * file_type with LUFID=1, so we restore the flag —\n+\t\t\t\t * the LUFID bytes are still on disk untouched. */\n+\t\t\t\tent-\u003ede-\u003efile_type =\n+\t\t\t\t\t(ent-\u003ede-\u003efile_type \u0026 ~EXT4_DIRENT_LUFID) |\n+\t\t\t\t\t(file_type \u0026 EXT4_DIRENT_LUFID);\n+\t\t\t}\n+\t\t}\n+\t\tent-\u003ede-\u003efile_type = (file_type \u0026 EXT4_FT_MASK) |\n+\t\t\t\t     (ent-\u003ede-\u003efile_type \u0026 ~EXT4_FT_MASK);\n+\t}\n \tinode_inc_iversion(ent-\u003edir);\n \tinode_set_mtime_to_ts(ent-\u003edir, inode_set_ctime_current(ent-\u003edir));\n \tretval = ext4_mark_inode_dirty(handle, ent-\u003edir);\n@@ -3687,7 +4025,7 @@ static void ext4_resetent(handle_t *handle, struct ext4_renament *ent,\n \t\treturn;\n \t}\n \n-\text4_setent(handle, \u0026old, ino, file_type);\n+\text4_setent(handle, \u0026old, ino, file_type, NULL);\n \tbrelse(old.bh);\n }\n \n@@ -3786,6 +4124,32 @@ static struct inode *ext4_whiteout_for_rename(struct mnt_idmap *idmap,\n \treturn wh;\n }\n \n+/*\n+ * ext4_lufid_snapshot - copy the LUFID record from a directory entry\n+ *\n+ * Allocates and returns a kmemdup'd copy of the full on-disk LUFID record\n+ * (header + FID payload of arbitrary length).  Returns NULL if no LUFID is\n+ * present, the record is malformed, or allocation fails.  Caller must kfree().\n+ */\n+static struct ext4_dirent_fid *\n+ext4_lufid_snapshot(struct ext4_dir_entry_2 *de, unsigned int blocksize)\n+{\n+\tunsigned int ddh_off = EXT4_BASE_DIR_LEN + de-\u003ename_len + 1;\n+\tunsigned int rec_len = ext4_rec_len_from_disk(de-\u003erec_len, blocksize);\n+\tstruct ext4_dirent_fid *disk_fid;\n+\tunsigned int dlen;\n+\n+\tif (!(de-\u003efile_type \u0026 EXT4_DIRENT_LUFID))\n+\t\treturn NULL;\n+\tif (ddh_off + sizeof(disk_fid-\u003edf_header) \u003e rec_len)\n+\t\treturn NULL;\n+\tdisk_fid = (struct ext4_dirent_fid *)((char *)de + ddh_off);\n+\tdlen = disk_fid-\u003edf_header.ddh_length;\n+\tif (dlen \u003c sizeof(disk_fid-\u003edf_header) || ddh_off + dlen \u003e rec_len)\n+\t\treturn NULL;\n+\treturn kmemdup(disk_fid, dlen, GFP_NOFS);\n+}\n+\n /*\n  * Anybody can rename anything with this: the permission checks are left to the\n  * higher-level routines.\n@@ -3814,6 +4178,7 @@ static int ext4_rename(struct mnt_idmap *idmap, struct inode *old_dir,\n \tstruct inode *whiteout = NULL;\n \tint credits;\n \tu8 old_file_type;\n+\tstruct ext4_dirent_fid *old_fid = NULL;\n \n \tif (new.inode \u0026\u0026 new.inode-\u003ei_nlink == 0) {\n \t\tEXT4_ERROR_INODE(new.inode,\n@@ -3925,7 +4290,7 @@ static int ext4_rename(struct mnt_idmap *idmap, struct inode *old_dir,\n \t\t * to be still pointing to the valid old entry.\n \t\t */\n \t\tretval = ext4_setent(handle, \u0026old, whiteout-\u003ei_ino,\n-\t\t\t\t     EXT4_FT_CHRDEV);\n+\t\t\t\t     EXT4_FT_CHRDEV, NULL);\n \t\tif (retval)\n \t\t\tgoto end_rename;\n \t\tretval = ext4_mark_inode_dirty(handle, whiteout);\n@@ -3938,8 +4303,18 @@ static int ext4_rename(struct mnt_idmap *idmap, struct inode *old_dir,\n \t\tif (retval)\n \t\t\tgoto end_rename;\n \t} else {\n+\t\tif (ext4_has_feature_dirdata(old.dir-\u003ei_sb)) {\n+\t\t\told_fid = ext4_lufid_snapshot(old.de,\n+\t\t\t\t\t\t      old.dir-\u003ei_sb-\u003es_blocksize);\n+\t\t\tif (old_fid)\n+\t\t\t\text4_fc_mark_ineligible(old.dir-\u003ei_sb,\n+\t\t\t\t\t\t\tEXT4_FC_REASON_DIRDATA,\n+\t\t\t\t\t\t\thandle);\n+\t\t\telse\n+\t\t\t\told_file_type \u0026= ~EXT4_DIRENT_LUFID;\n+\t\t}\n \t\tretval = ext4_setent(handle, \u0026new,\n-\t\t\t\t     old.inode-\u003ei_ino, old_file_type);\n+\t\t\t\t     old.inode-\u003ei_ino, old_file_type, old_fid);\n \t\tif (retval)\n \t\t\tgoto end_rename;\n \t}\n@@ -4026,6 +4401,7 @@ static int ext4_rename(struct mnt_idmap *idmap, struct inode *old_dir,\n \tretval = 0;\n \n end_rename:\n+\tkfree(old_fid);\n \tif (whiteout) {\n \t\tif (retval) {\n \t\t\text4_resetent(handle, \u0026old,\n@@ -4062,8 +4438,9 @@ static int ext4_cross_rename(struct inode *old_dir, struct dentry *old_dentry,\n \t\t.dentry = new_dentry,\n \t\t.inode = d_inode(new_dentry),\n \t};\n-\tu8 new_file_type;\n+\tu8 new_file_type, old_de_file_type;\n \tint retval;\n+\tstruct ext4_dirent_fid *old_fid = NULL, *new_fid = NULL;\n \n \tif ((ext4_test_inode_flag(new_dir, EXT4_INODE_PROJINHERIT) \u0026\u0026\n \t     !projid_eq(EXT4_I(new_dir)-\u003ei_projid,\n@@ -4142,12 +4519,27 @@ static int ext4_cross_rename(struct inode *old_dir, struct dentry *old_dentry,\n \t\t\tgoto end_rename;\n \t}\n \n+\tif (ext4_has_feature_dirdata(old.dir-\u003ei_sb)) {\n+\t\told_fid = ext4_lufid_snapshot(old.de, old.dir-\u003ei_sb-\u003es_blocksize);\n+\t\tnew_fid = ext4_lufid_snapshot(new.de, new.dir-\u003ei_sb-\u003es_blocksize);\n+\t\tif (old_fid || new_fid)\n+\t\t\text4_fc_mark_ineligible(old.dir-\u003ei_sb,\n+\t\t\t\t\t\tEXT4_FC_REASON_DIRDATA, handle);\n+\t}\n+\n+\told_de_file_type = old.de-\u003efile_type;\n+\tif (!old_fid)\n+\t\told_de_file_type \u0026= ~EXT4_DIRENT_LUFID;\n \tnew_file_type = new.de-\u003efile_type;\n-\tretval = ext4_setent(handle, \u0026new, old.inode-\u003ei_ino, old.de-\u003efile_type);\n+\tif (!new_fid)\n+\t\tnew_file_type \u0026= ~EXT4_DIRENT_LUFID;\n+\tretval = ext4_setent(handle, \u0026new, old.inode-\u003ei_ino, old_de_file_type,\n+\t\t\t     old_fid);\n \tif (retval)\n \t\tgoto end_rename;\n \n-\tretval = ext4_setent(handle, \u0026old, new.inode-\u003ei_ino, new_file_type);\n+\tretval = ext4_setent(handle, \u0026old, new.inode-\u003ei_ino, new_file_type,\n+\t\t\t     new_fid);\n \tif (retval)\n \t\tgoto end_rename;\n \n@@ -4180,6 +4572,8 @@ static int ext4_cross_rename(struct inode *old_dir, struct dentry *old_dentry,\n \tretval = 0;\n \n end_rename:\n+\tkfree(old_fid);\n+\tkfree(new_fid);\n \tbrelse(old.dir_bh);\n \tbrelse(new.dir_bh);\n \tbrelse(old.bh);\n@@ -4216,6 +4610,154 @@ static int ext4_rename2(struct mnt_idmap *idmap,\n \treturn ext4_rename(idmap, old_dir, old_dentry, new_dir, new_dentry, flags);\n }\n \n+/*\n+ * ext4_dirdata_set_lufid() - Set LUFID data on an existing directory entry\n+ * @dir:        parent directory inode\n+ * @filename:   name of the file in the directory\n+ * @namelen:    length of filename\n+ * @edp:        pointer to initialized dentry param with LUFID data\n+ *\n+ * This function finds an existing directory entry, deletes it, and re-creates it\n+ * with LUFID data attached. Used by the EXT4_IOC_SET_LUFID ioctl.\n+ *\n+ * Returns 0 on success, negative error code on failure.\n+ */\n+int ext4_dirdata_set_lufid(struct inode *dir, const char *filename,\n+\t\t\t    int namelen, struct ext4_dentry_param *edp)\n+{\n+\tstruct super_block *sb = dir-\u003ei_sb;\n+\t /* zero-init: safe to free on any path */\n+\tstruct ext4_filename fname = {};\n+\tstruct ext4_dir_entry_2 *de = NULL;\n+\tstruct buffer_head *bh = NULL;\n+\tstruct inode *inode = NULL;\n+\thandle_t *handle = NULL;\n+\tstruct qstr d_name;\n+\tint err = 0;\n+\n+\tif (!ext4_has_feature_dirdata(sb))\n+\t\treturn -EOPNOTSUPP;\n+\n+\tif (namelen \u003e EXT4_NAME_LEN)\n+\t\treturn -ENAMETOOLONG;\n+\tif (namelen != strnlen(filename, namelen + 1))\n+\t\treturn -EINVAL;\n+\n+\td_name.name = filename;\n+\td_name.len = namelen;\n+\n+\terr = ext4_fname_setup_filename(dir, \u0026d_name, 0, \u0026fname);\n+\tif (err)\n+\t\tgoto out_free;\n+\n+\t/* Lock dir with the VFS parent-mutation subclass before starting the\n+\t * journal.  Holding i_rwsem while calling ext4_journal_start() is safe\n+\t * (ext4_setattr, ext4_fallocate etc. do the same).  The lookup must be\n+\t * inside the lock to prevent TOCTOU: the bh/de pointers must be stable\n+\t * from find_entry through delete_entry. */\n+\tinode_lock_nested(dir, I_MUTEX_PARENT);\n+\n+\thandle = ext4_journal_start(dir, EXT4_HT_DIR,\n+\t\t\t\t    3 * EXT4_DATA_TRANS_BLOCKS(sb) +\n+\t\t\t\t    2 * EXT4_INDEX_EXTRA_TRANS_BLOCKS);\n+\tif (IS_ERR(handle)) {\n+\t\terr = PTR_ERR(handle);\n+\t\thandle = NULL;\n+\t\tgoto out_unlock_dir;\n+\t}\n+\n+\tbh = ext4_find_entry(dir, \u0026d_name, \u0026de, NULL);\n+\tif (IS_ERR(bh)) {\n+\t\terr = PTR_ERR(bh);\n+\t\tbh = NULL;\n+\t\tgoto out_journal;\n+\t}\n+\tif (!bh) {\n+\t\terr = -ENOENT;\n+\t\tgoto out_journal;\n+\t}\n+\n+\tinode = ext4_iget(sb, le32_to_cpu(de-\u003einode), EXT4_IGET_NORMAL);\n+\tif (IS_ERR(inode)) {\n+\t\terr = PTR_ERR(inode);\n+\t\tinode = NULL;\n+\t\tgoto out_brelse;\n+\t}\n+\n+\t/* Lock the target inode after the directory (dir-then-child ordering)\n+\t * to serialize concurrent EXT4_IOC_SET_LUFID calls on different\n+\t * hardlinks of the same inode.  Use I_MUTEX_CHILD for directory inodes\n+\t * (consistent with VFS rename/rmdir) and I_MUTEX_NONDIR2 for all\n+\t * others; mixing subclasses on the same inode triggers lockdep cycles\n+\t * with concurrent rename. */\n+\tif (inode != dir) {\n+\t\tif (S_ISDIR(inode-\u003ei_mode))\n+\t\t\tinode_lock_nested(inode, I_MUTEX_CHILD);\n+\t\telse\n+\t\t\tinode_lock_nested(inode, I_MUTEX_NONDIR2);\n+\t}\n+\n+\terr = ext4_delete_entry(handle, dir, de, bh);\n+\tif (err)\n+\t\tgoto out_unlock;\n+\n+\tbrelse(bh);\n+\tbh = NULL;\n+\n+\t/* Re-add with LUFID via dentry-\u003ed_fsdata.  ext4_add_entry() resolves\n+\t * dfid from d_fsdata into fname.dfid and passes it through to\n+\t * add_dirent_to_buf() without touching the shared i_dirdata field,\n+\t * eliminating the race with concurrent link() calls. */\n+\t{\n+\t\tstruct dentry parent_dentry = { .d_inode = dir };\n+\t\tstruct dentry new_dentry = {\n+\t\t\t.d_name = d_name,\n+\t\t\t.d_parent = \u0026parent_dentry,\n+\t\t\t.d_inode = inode,\n+\t\t\t.d_fsdata = edp,\n+\t\t};\n+\t\terr = ext4_add_entry(handle, \u0026new_dentry, inode);\n+\t}\n+\n+\tif (err) {\n+\t\t/* Delete succeeded but re-add failed; try to restore so the\n+\t\t * inode is not left without a directory entry. */\n+\t\tstruct dentry parent_dentry = { .d_inode = dir };\n+\t\tstruct dentry orig_dentry = {\n+\t\t\t.d_name = d_name,\n+\t\t\t.d_parent = \u0026parent_dentry,\n+\t\t\t.d_inode = inode,\n+\t\t};\n+\t\tint rollback_err = ext4_add_entry(handle, \u0026orig_dentry, inode);\n+\n+\t\tif (rollback_err)\n+\t\t\tEXT4_ERROR_INODE(dir,\n+\t\t\t\t\"Failed to set LUFID on '%.*s' (err=%d) and failed to restore the original directory entry (err=%d); inode %llu may be orphaned\",\n+\t\t\t\tnamelen, filename, err, rollback_err,\n+\t\t\t\tinode-\u003ei_ino);\n+\t\tgoto out_unlock;\n+\t}\n+\n+\tinode_set_ctime_current(dir);\n+\tinode_inc_iversion(dir);\n+\text4_mark_inode_dirty(handle, dir);\n+\n+out_unlock:\n+\tif (inode != dir)\n+\t\tinode_unlock(inode);\n+out_brelse:\n+\tbrelse(bh);\n+out_journal:\n+\text4_journal_stop(handle);\n+out_unlock_dir:\n+\tinode_unlock(dir);\n+\tiput(inode);\n+out_free:\n+\text4_fname_free_filename(\u0026fname);\n+\n+\treturn err;\n+}\n+\n /*\n  * directories can handle most operations...\n  */\ndiff --git a/fs/ext4/sysfs.c b/fs/ext4/sysfs.c\nindex 923b375e017faa..80074fb15ee9b7 100644\n--- a/fs/ext4/sysfs.c\n+++ b/fs/ext4/sysfs.c\n@@ -362,6 +362,7 @@ EXT4_ATTR_FEATURE(verity);\n #endif\n EXT4_ATTR_FEATURE(metadata_csum_seed);\n EXT4_ATTR_FEATURE(fast_commit);\n+EXT4_ATTR_FEATURE(dirdata);\n #if IS_ENABLED(CONFIG_UNICODE) \u0026\u0026 defined(CONFIG_FS_ENCRYPTION)\n EXT4_ATTR_FEATURE(encrypted_casefold);\n #endif\n@@ -385,6 +386,7 @@ static struct attribute *ext4_feat_attrs[] = {\n #endif\n \tATTR_LIST(metadata_csum_seed),\n \tATTR_LIST(fast_commit),\n+\tATTR_LIST(dirdata),\n #if IS_ENABLED(CONFIG_UNICODE) \u0026\u0026 defined(CONFIG_FS_ENCRYPTION)\n \tATTR_LIST(encrypted_casefold),\n #endif\ndiff --git a/include/uapi/linux/ext4.h b/include/uapi/linux/ext4.h\nindex 9c683991c32f96..9134fe64947a93 100644\n--- a/include/uapi/linux/ext4.h\n+++ b/include/uapi/linux/ext4.h\n@@ -35,6 +35,7 @@\n #define EXT4_IOC_SETFSUUID\t\t_IOW('f', 44, struct fsuuid)\n #define EXT4_IOC_GET_TUNE_SB_PARAM\t_IOR('f', 45, struct ext4_tune_sb_params)\n #define EXT4_IOC_SET_TUNE_SB_PARAM\t_IOW('f', 46, struct ext4_tune_sb_params)\n+#define EXT4_IOC_SET_LUFID\t\t_IOW('f', 47, struct ext4_set_lufid)\n \n #define EXT4_IOC_SHUTDOWN _IOR('X', 125, __u32)\n \n@@ -92,6 +93,19 @@ struct move_extent {\n \t__u64 moved_len;\t/* moved block length */\n };\n \n+/*\n+ * Structure for EXT4_IOC_SET_LUFID\n+ * Sets LUFID on a directory entry\n+ * Called on parent directory with filename and LUFID data as arguments\n+ */\n+struct ext4_set_lufid {\n+\t__u8 esl_name_len;\t/* length of filename, NOT including NUL terminator\n+\t\t\t\t * (valid range: 1-255, matching EXT4_NAME_LEN) */\n+\tchar  esl_name[255 + 1]; /* filename (NUL-terminated) */\n+\t__u8 esl_data_len;\t/* length of LUFID data */\n+\tchar  esl_data[255]; /* LUFID data (raw bytes) */\n+};\n+\n /*\n  * Flags used by EXT4_IOC_SHUTDOWN\n  */\n"
}

2/1 2026/07/14 19:42 agent patch-evaluator
2m Model:
gemini-3.1-pro-preview

Args:
null

Results:
{
  "EnableConfigs": [
    "EXT4_FS"
  ],
  "FocusSymbols": [
    "ext4_ioctl_set_lufid",
    "ext4_dirdata_set_lufid",
    "ext4_dirdata_get",
    "ext4_dirdata_set",
    "ext4_lufid_snapshot",
    "ext4_insert_dentry_data",
    "ext4_init_new_dir_data",
    "dx_get_dx_info",
    "ext4_check_dx_root",
    "ext4_setent",
    "__ext4_add_entry"
  ],
  "Reasoning": "The patch introduces a new ext4 feature `dirdata` (EXT4_FEATURE_INCOMPAT_DIRDATA) and a new ioctl `EXT4_IOC_SET_LUFID` to attach LUFID data to directory entries. It modifies core directory entry parsing, insertion, splitting, and renaming logic to handle variable-length data appended to directory entries. This is a significant functional change to the ext4 filesystem that should be fuzzed.",
  "WorthFuzzing": true
}

Instruction:
You are an expert Linux kernel maintainer.
Your job is to review a provided patch series and determine
if it makes functional changes to the kernel that should be fuzzed.

IMPORTANT: The changes have ALREADY been applied and committed as the HEAD commit in
your workspace. Do NOT rely on your internal knowledge of the kernel. You must actively
use your code access tools to examine the actual source code and confirm any assumptions.

Return WorthFuzzing=false if the patch only contains:
- Modifications to Documentation/, Kconfig files, or code comments.
- Purely decorative changes, such as logging (e.g., pr_err, printk) or tracepoints.
- Changes to numeric constants or macros that do not functionally alter execution flow.
- Code paths that are impossible to reach in virtualized environments like GCE or QEMU,
even when utilizing software-emulated hardware (e.g., usb gadget, mac80211_hwsim).

If it modifies reachable core kernel logic, drivers, or architectures, use your code search
tools to verify the code can be executed, then return WorthFuzzing=true.

When returning WorthFuzzing=true, you MUST ALSO:
1. Extract any specific kernel functions that should be heavily fuzzed into FocusSymbols.
   Avoid listing generic hot-path functions to prevent skewed test distributions.
2. Identify any specific CONFIG_ options required to properly test this new/modified feature.
   Go and look into the Kconfig files and check for ifdefs around the code, do not make assumptions.
   Do not list too generic configs (we already have them enabled). Only list those that
   specifically cover the modified code. List them in the EnableConfigs output array,
   and DO NOT add a 'CONFIG_' prefix (e.g., return "NET_IPV4" instead of "CONFIG_NET_IPV4").
Prefer calling several tools at the same time to save round-trips.


Use set-results tool to provide results of the analysis.
It must be called exactly once before the final reply.
Ignore results of this tool.

Prompt:
For your convenience, here is the diff of the changes:
commit b44158818cdbbf8e2281ab287f17d971a76bbfdd
Author: syz-cluster <triage@syzkaller.com>
Date:   Tue Jul 14 19:42:05 2026 +0000

    syz-cluster: applied patch under review

diff --git a/fs/ext4/dir.c b/fs/ext4/dir.c
index 17edd678fa87b8..50c48c50a29d88 100644
--- a/fs/ext4/dir.c
+++ b/fs/ext4/dir.c
@@ -89,16 +89,15 @@ int __ext4_check_dir_entry(const char *function, unsigned int line,
 	bool fake = is_fake_dir_entry(de);
 	bool has_csum = ext4_has_feature_metadata_csum(dir->i_sb);
 
-	if (unlikely(rlen < ext4_dir_rec_len(1, fake ? NULL : dir)))
+	if (unlikely(rlen < ext4_dirent_rec_len(1, fake ? NULL : dir)))
 		error_msg = "rec_len is smaller than minimal";
 	else if (unlikely(rlen % 4 != 0))
 		error_msg = "rec_len % 4 != 0";
-	else if (unlikely(rlen < ext4_dir_rec_len(de->name_len,
-							fake ? NULL : dir)))
-		error_msg = "rec_len is too small for name_len";
 	else if (unlikely(next_offset > size))
 		error_msg = "directory entry overrun";
-	else if (unlikely(next_offset > size - ext4_dir_rec_len(1,
+	else if (unlikely(rlen < ext4_dir_entry_len(de, size, fake ? NULL : dir)))
+		error_msg = "rec_len is too small for name_len";
+	else if (unlikely(next_offset > size - ext4_dirent_rec_len(1,
 						  has_csum ? NULL : dir) &&
 			  next_offset != size))
 		error_msg = "directory entry too close to block end";
@@ -245,7 +244,7 @@ static int ext4_readdir(struct file *file, struct dir_context *ctx)
 				 * failure will be detected in the
 				 * dirent test below. */
 				if (ext4_rec_len_from_disk(de->rec_len,
-					sb->s_blocksize) < ext4_dir_rec_len(1,
+					sb->s_blocksize) < ext4_dirent_rec_len(1,
 									inode))
 					break;
 				i += ext4_rec_len_from_disk(de->rec_len,
@@ -468,27 +467,43 @@ void ext4_htree_free_dir_info(struct dir_private_info *p)
  * The decrypted filename is passed in via ent_name.  parameter.
  */
 int ext4_htree_store_dirent(struct file *dir_file, __u32 hash,
-			     __u32 minor_hash,
+			     __u32 minor_hash, int buf_size,
 			    struct ext4_dir_entry_2 *dirent,
 			    struct fscrypt_str *ent_name)
 {
 	struct rb_node **p, *parent = NULL;
 	struct fname *fname, *new_fn;
 	struct dir_private_info *info;
+	int extra_data = 0;
 
 	info = dir_file->private_data;
 	p = &info->root.rb_node;
 
 	/* Create and allocate the fname structure */
-	new_fn = kzalloc_flex(*new_fn, name, ent_name->len + 1);
+	if (dirent->file_type & ~EXT4_FT_MASK) {
+		unsigned int rec_len =
+			ext4_rec_len_from_disk(dirent->rec_len, buf_size);
+		extra_data = ext4_dirent_get_data_len(dirent, rec_len);
+	}
+
+	/* exta_data contains +1 byte for NULL */
+	new_fn = kzalloc_flex(*new_fn, name, ent_name->len +
+			      (extra_data ? extra_data : 1));
 	if (!new_fn)
 		return -ENOMEM;
 	new_fn->hash = hash;
 	new_fn->minor_hash = minor_hash;
 	new_fn->inode = le32_to_cpu(dirent->inode);
-	new_fn->name_len = ent_name->len;
 	new_fn->file_type = dirent->file_type;
+	new_fn->name_len = ent_name->len;
 	memcpy(new_fn->name, ent_name->name, ent_name->len);
+	if (extra_data)
+		/* Dirdata bytes lie past the __counted_by(name_len) bound but
+		 * within the kzalloc_flex allocation; use unsafe_memcpy to
+		 * avoid a FORTIFY_SOURCE false positive. */
+		unsafe_memcpy(new_fn->name + ent_name->len,
+			      dirent->name + dirent->name_len, extra_data,
+			      /* within kzalloc_flex allocation */);
 
 	while (*p) {
 		parent = *p;
diff --git a/fs/ext4/ext4.h b/fs/ext4/ext4.h
index b37c136ea3ab33..15d885cf3bd025 100644
--- a/fs/ext4/ext4.h
+++ b/fs/ext4/ext4.h
@@ -1227,6 +1227,7 @@ struct ext4_inode_info {
 #ifdef CONFIG_FS_ENCRYPTION
 	struct fscrypt_inode_info *i_crypt_info;
 #endif
+	void *i_dirdata;
 };
 
 /*
@@ -2334,6 +2335,7 @@ EXT4_FEATURE_INCOMPAT_FUNCS(casefold,		CASEFOLD)
 					 EXT4_FEATURE_INCOMPAT_FLEX_BG| \
 					 EXT4_FEATURE_INCOMPAT_EA_INODE| \
 					 EXT4_FEATURE_INCOMPAT_MMP | \
+					 EXT4_FEATURE_INCOMPAT_DIRDATA | \
 					 EXT4_FEATURE_INCOMPAT_INLINE_DATA | \
 					 EXT4_FEATURE_INCOMPAT_ENCRYPT | \
 					 EXT4_FEATURE_INCOMPAT_CASEFOLD | \
@@ -2472,10 +2474,8 @@ static inline int ext4_emergency_state(struct super_block *sb)
  * Structure of a directory entry
  */
 #define EXT4_NAME_LEN 255
-/*
- * Base length of the ext4 directory entry excluding the name length
- */
-#define EXT4_BASE_DIR_LEN (sizeof(struct ext4_dir_entry_2) - EXT4_NAME_LEN)
+/* offsetof avoids sizeof()+padding giving 9 instead of the correct on-disk 8 */
+#define EXT4_BASE_DIR_LEN offsetof(struct ext4_dir_entry_2, name)
 
 struct ext4_dir_entry {
 	__le32	inode;			/* Inode number */
@@ -2556,6 +2556,61 @@ struct ext4_dir_entry_tail {
 #define EXT4_FT_SYMLINK		7
 
 #define EXT4_FT_MAX		8
+#define EXT4_FT_MASK		0xf
+
+#if EXT4_FT_MAX > EXT4_FT_MASK
+#error "conflicting EXT4_FT_MAX and EXT4_FT_MASK"
+#endif
+
+/*
+ * d_type has 4 unused bits, so it can hold four types of data. These different
+ * types of data (e.g. fscypt hash, high 32 bits of 64-bit inode number) can be
+ * stored, in flag order, after file-name in ext4 dirent.
+ *
+ * These flags are added to d_type if ext4 dirent has extra data after
+ * filename. This data length is variable and length is stored in first byte
+ * of data. Data starts after filename NUL byte.
+ */
+#define EXT4_DIRENT_LUFID		0x10
+#define EXT4_DIRENT_INO64		0x20
+#define EXT4_DIRENT_CFHASH		0x40
+
+struct ext4_fid {
+	char    fid[16];     /* 128-bit unique file identifier */
+};
+
+struct ext4_dirent_data_header {
+	/* length of this header + the whole data blob */
+	__u8	ddh_length;
+} __packed;
+
+struct ext4_dirent_fid {
+	struct ext4_dirent_data_header df_header;
+	struct ext4_fid                df_fid[];
+};
+
+#define EXT4_LUFID_MAGIC    0xAD200907UL
+struct ext4_dentry_param {
+	__u32			edp_magic;	/* EXT4_LUFID_MAGIC */
+	struct ext4_dirent_fid	edp_dfid;
+};
+
+struct ext4_dirent_hash {
+	struct ext4_dirent_data_header	dh_header;
+	struct ext4_dir_entry_hash	dh_hash;
+} __packed;
+
+static inline
+struct ext4_dirent_fid *ext4_dentry_get_fid(struct super_block *sb,
+					    struct ext4_dentry_param *p)
+{
+	if (!ext4_has_feature_dirdata(sb))
+		return NULL;
+	if (p && p->edp_magic == EXT4_LUFID_MAGIC)
+		return &p->edp_dfid;
+
+	return NULL;
+}
 
 #define EXT4_FT_DIR_CSUM	0xDE
 
@@ -2573,13 +2628,26 @@ struct ext4_dir_entry_tail {
  * casefolded and encrypted need to store the hash as well, so we add room for
  * ext4_extended_dir_entry_2. For all entries related to '.' or '..' you should
  * pass NULL for dir, as those entries do not use the extra fields.
+ *
+ * For directories with the dirdata feature, extra data may follow the filename.
+ * Use ext4_dir_entry_len() to compute the length of a directory entry
+ * including any dirdata, or ext4_dirent_rec_len() directly when the total
+ * name_len (including dirdata length) is already known.
  */
-static inline unsigned int ext4_dir_rec_len(__u8 name_len,
+static inline unsigned int ext4_dirent_rec_len(unsigned int name_len,
 						const struct inode *dir)
 {
-	int rec_len = (name_len + 8 + EXT4_DIR_ROUND);
+	unsigned 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);
 }
@@ -2667,6 +2735,8 @@ struct ext4_filename {
 #if IS_ENABLED(CONFIG_UNICODE)
 	struct qstr cf_name;
 #endif
+	/* LUFID/dirdata payload to embed in the new directory entry, or NULL */
+	struct ext4_dirent_fid *dfid;
 };
 
 #define fname_name(p) ((p)->disk_name.name)
@@ -2978,18 +3048,27 @@ extern int __ext4_check_dir_entry(const char *, unsigned int, struct inode *,
 	unlikely(__ext4_check_dir_entry(__func__, __LINE__, (dir), (filp), \
 				(de), (bh), (buf), (size), (offset)))
 extern int ext4_htree_store_dirent(struct file *dir_file, __u32 hash,
-				__u32 minor_hash,
+				__u32 minor_hash, int buf_size,
 				struct ext4_dir_entry_2 *dirent,
 				struct fscrypt_str *ent_name);
 extern void ext4_htree_free_dir_info(struct dir_private_info *p);
 extern int ext4_find_dest_de(struct inode *dir, struct buffer_head *bh,
 			     void *buf, int buf_size,
 			     struct ext4_filename *fname,
-			     struct ext4_dir_entry_2 **dest_de);
-void ext4_insert_dentry(struct inode *dir, struct inode *inode,
-			struct ext4_dir_entry_2 *de,
-			int buf_size,
-			struct ext4_filename *fname);
+			     struct ext4_dir_entry_2 **dest_de,
+			     int dlen);
+void ext4_insert_dentry_data(struct inode *dir, struct inode *inode,
+			     struct ext4_dir_entry_2 *de,
+			     int buf_size,
+			     struct ext4_filename *fname,
+			     void *data);
+static inline void ext4_insert_dentry(struct inode *dir, struct inode *inode,
+				      struct ext4_dir_entry_2 *de,
+				      int buf_size,
+				      struct ext4_filename *fname)
+{
+	ext4_insert_dentry_data(dir, inode, de, buf_size, fname, NULL);
+}
 static inline void ext4_update_dx_flag(struct inode *inode)
 {
 	if (!ext4_has_feature_dir_index(inode->i_sb) &&
@@ -3003,12 +3082,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);
@@ -3231,10 +3312,18 @@ extern int ext4_ext_migrate(struct inode *);
 extern int ext4_ind_migrate(struct inode *inode);
 
 /* namei.c */
-extern int ext4_init_new_dir(handle_t *handle, struct inode *dir,
-			     struct inode *inode);
+extern int ext4_init_new_dir_data(handle_t *handle, struct inode *dir,
+				  struct inode *inode,
+				  const void *data1, const void *data2);
+static inline int ext4_init_new_dir(handle_t *handle, struct inode *dir,
+				    struct inode *inode)
+{
+	return ext4_init_new_dir_data(handle, dir, inode, NULL, NULL);
+}
 extern int ext4_dirblock_csum_verify(struct inode *inode,
 				     struct buffer_head *bh);
+extern int ext4_dirdata_set_lufid(struct inode *dir, const char *filename,
+			   int namelen, struct ext4_dentry_param *edp);
 extern int ext4_htree_fill_tree(struct file *dir_file, __u32 start_hash,
 				__u32 start_minor_hash, __u32 *next_hash);
 extern int ext4_search_dir(struct buffer_head *bh,
@@ -3807,6 +3896,10 @@ extern int __ext4_unlink(struct inode *dir, const struct qstr *d_name,
 			 struct inode *inode, struct dentry *dentry);
 extern int __ext4_link(struct inode *dir, struct inode *inode,
 		       const struct qstr *d_name, struct dentry *dentry);
+extern unsigned char ext4_dirdata_get(struct ext4_dir_entry_2 *de,
+				      struct inode *dir, int buf_size,
+				      struct ext4_dirent_fid  *lufid,
+				      struct dx_hash_info *hinfo);
 
 #define S_SHIFT 12
 static const unsigned char ext4_type_by_mode[(S_IFMT >> S_SHIFT) + 1] = {
@@ -4004,6 +4097,109 @@ static inline void ext4_clear_io_unwritten_flag(ext4_io_end_t *io_end)
 		io_end->flag &= ~EXT4_IO_END_UNWRITTEN;
 }
 
+/*
+ * Advance to the next dirdata record header starting from @ddh.
+ */
+#define ext4_dirdata_next(ddh) \
+	((struct ext4_dirent_data_header *)((char *)(ddh) + (ddh)->ddh_length))
+
+/*
+ * ext4_dir_entry_is_tail() - Check if a directory entry is a tail entry.
+ * @de: directory entry to check
+ *
+ * Returns true if @de is a directory block tail entry (checksum record).
+ */
+static inline bool ext4_dir_entry_is_tail(struct ext4_dir_entry_2 *de)
+{
+	struct ext4_dir_entry_tail *t = (struct ext4_dir_entry_tail *)de;
+
+	return !t->det_reserved_zero1 &&
+	       le16_to_cpu(t->det_rec_len) == sizeof(*t) &&
+	       !t->det_reserved_zero2 &&
+	       t->det_reserved_ft == EXT4_FT_DIR_CSUM;
+}
+
+/*
+ * ext4_dirent_get_data_len() - Compute the total dirdata length for an entry.
+ * @de: directory entry
+ * @rec_len: the record length of the directory entry (decoded)
+ *
+ * Computes the length of optional data stored after the filename (and its
+ * implicit NUL terminator).  Each extension is indicated by a bit in the
+ * high 4 bits of de->file_type; the first byte of each extension is its
+ * length (including that length byte itself).
+ *
+ * Returns 0 for tail entries and for entries with no dirdata.
+ */
+static inline int ext4_dirent_get_data_len(struct ext4_dir_entry_2 *de,
+					   unsigned int rec_len)
+{
+	__u8 extra_data_flags;
+	struct ext4_dirent_data_header *ddh;
+	int dlen = 0;
+	unsigned int offset;
+
+	if (ext4_dir_entry_is_tail(de))
+		return 0;
+
+	extra_data_flags = (de->file_type & ~EXT4_FT_MASK) >> 4;
+	/* offset from start of entry to after filename + NUL */
+	offset = EXT4_BASE_DIR_LEN + de->name_len + 1;
+
+	/* bounds check: ensure we start reading within the entry */
+	if (offset >= rec_len)
+		return 0;
+
+	ddh = (struct ext4_dirent_data_header *)((char *)de + offset);
+
+	while (extra_data_flags) {
+		if (extra_data_flags & 1) {
+			/* bounds check before reading ddh_length */
+			if (offset + sizeof(*ddh) > rec_len)
+				return dlen;
+
+			/* validate ddh_length is reasonable — read once to
+			 * avoid a TOCTOU double-fetch on the disk field */
+			{
+				u8 ddh_len = ddh->ddh_length;
+
+				if (ddh_len == 0 || ddh_len > rec_len - offset)
+					return dlen;
+				dlen += ddh_len + (dlen == 0);
+				offset += ddh_len;
+			}
+			ddh = ext4_dirdata_next(ddh);
+		}
+		extra_data_flags >>= 1;
+	}
+	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_dirent_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/fast_commit.c b/fs/ext4/fast_commit.c
index 8e2259799614e9..006d45e9ce8798 100644
--- a/fs/ext4/fast_commit.c
+++ b/fs/ext4/fast_commit.c
@@ -2697,6 +2697,7 @@ static const char * const fc_ineligible_reasons[] = {
 	[EXT4_FC_REASON_MIGRATE] = "Inode format migration",
 	[EXT4_FC_REASON_VERITY] = "fs-verity enable",
 	[EXT4_FC_REASON_MOVE_EXT] = "Move extents",
+	[EXT4_FC_REASON_DIRDATA] = "Dirdata LUFID (not recorded by fast-commit)",
 };
 
 int ext4_fc_info_show(struct seq_file *seq, void *v)
diff --git a/fs/ext4/fast_commit.h b/fs/ext4/fast_commit.h
index 2f77a37fb10180..899faeb4e6e064 100644
--- a/fs/ext4/fast_commit.h
+++ b/fs/ext4/fast_commit.h
@@ -100,6 +100,7 @@ enum {
 	EXT4_FC_REASON_MIGRATE,
 	EXT4_FC_REASON_VERITY,
 	EXT4_FC_REASON_MOVE_EXT,
+	EXT4_FC_REASON_DIRDATA,
 	EXT4_FC_REASON_MAX
 };
 
diff --git a/fs/ext4/inline.c b/fs/ext4/inline.c
index 8045e4ff270c7f..1bbf89e18076f4 100644
--- a/fs/ext4/inline.c
+++ b/fs/ext4/inline.c
@@ -973,11 +973,20 @@ static int ext4_add_dirent_to_inline(handle_t *handle,
 				     struct ext4_iloc *iloc,
 				     void *inline_start, int inline_size)
 {
-	int		err;
+	int		err, dlen = 0;
 	struct ext4_dir_entry_2 *de;
+	struct ext4_dirent_fid *dfid = NULL;
+
+	/* fname->dfid was set in ext4_add_entry() */
+	dfid = fname->dfid;
+	if (dfid)
+		dlen = dfid->df_header.ddh_length;
+	if (ext4_hash_in_dirent(dir) &&
+	    ext4_has_feature_dirdata(dir->i_sb))
+		dlen += sizeof(struct ext4_dirent_hash);
 
 	err = ext4_find_dest_de(dir, iloc->bh, inline_start,
-				inline_size, fname, &de);
+				inline_size, fname, &de, dlen);
 	if (err)
 		return err;
 
@@ -986,7 +995,7 @@ static int ext4_add_dirent_to_inline(handle_t *handle,
 					    EXT4_JTR_NONE);
 	if (err)
 		return err;
-	ext4_insert_dentry(dir, inode, de, inline_size, fname);
+	ext4_insert_dentry_data(dir, inode, de, inline_size, fname, dfid);
 
 	ext4_show_inline_dir(dir, iloc->bh, inline_start, inline_size);
 
@@ -1055,7 +1064,7 @@ static int ext4_update_inline_dir(handle_t *handle, struct inode *dir,
 	int old_size = EXT4_I(dir)->i_inline_size - EXT4_MIN_INLINE_DATA_SIZE;
 	int new_size = get_max_inline_xattr_value_size(dir, iloc);
 
-	if (new_size - old_size <= ext4_dir_rec_len(1, NULL))
+	if (new_size - old_size <= ext4_dirent_rec_len(1, NULL))
 		return -ENOSPC;
 
 	ret = ext4_update_inline_data(handle, dir,
@@ -1264,6 +1273,7 @@ int ext4_inlinedir_to_tree(struct file *dir_file,
 	int err = 0, count = 0;
 	unsigned int parent_ino;
 	int pos;
+	unsigned int de_len;
 	struct ext4_dir_entry_2 *de;
 	struct inode *inode = file_inode(dir_file);
 	int ret, inline_size = 0;
@@ -1309,7 +1319,7 @@ int ext4_inlinedir_to_tree(struct file *dir_file,
 			fake.name_len = 1;
 			memcpy(fake.name, ".", 2);
 			fake.rec_len = ext4_rec_len_to_disk(
-					  ext4_dir_rec_len(fake.name_len, NULL),
+					  ext4_dirent_rec_len(fake.name_len, NULL),
 					  inline_size);
 			ext4_set_de_type(inode->i_sb, &fake, S_IFDIR);
 			de = &fake;
@@ -1319,26 +1329,34 @@ int ext4_inlinedir_to_tree(struct file *dir_file,
 			fake.name_len = 2;
 			memcpy(fake.name, "..", 3);
 			fake.rec_len = ext4_rec_len_to_disk(
-					  ext4_dir_rec_len(fake.name_len, NULL),
+					  ext4_dirent_rec_len(fake.name_len, NULL),
 					  inline_size);
 			ext4_set_de_type(inode->i_sb, &fake, S_IFDIR);
 			de = &fake;
 			pos = EXT4_INLINE_DOTDOT_SIZE;
 		} else {
+			if (pos + EXT4_BASE_DIR_LEN > inline_size) {
+				ret = count;
+				goto out;
+			}
 			de = (struct ext4_dir_entry_2 *)(dir_buf + pos);
-			pos += ext4_rec_len_from_disk(de->rec_len, inline_size);
+			de_len = ext4_rec_len_from_disk(de->rec_len, inline_size);
+			if (de_len < EXT4_BASE_DIR_LEN ||
+			    pos + de_len > (unsigned int)inline_size) {
+				ret = count;
+				goto out;
+			}
 			if (ext4_check_dir_entry(inode, dir_file, de,
 					 iloc.bh, dir_buf,
 					 inline_size, pos)) {
 				ret = count;
 				goto out;
 			}
+			pos += de_len;
 		}
 
-		if (ext4_hash_in_dirent(dir)) {
-			hinfo->hash = EXT4_DIRENT_HASH(de);
-			hinfo->minor_hash = EXT4_DIRENT_MINOR_HASH(de);
-		} else {
+		if (!(ext4_dirdata_get(de, dir, inline_size, NULL, hinfo) &
+							EXT4_DIRENT_CFHASH)) {
 			err = ext4fs_dirhash(dir, de->name, de->name_len, hinfo);
 			if (err) {
 				ret = err;
@@ -1354,7 +1372,8 @@ int ext4_inlinedir_to_tree(struct file *dir_file,
 		tmp_str.name = de->name;
 		tmp_str.len = de->name_len;
 		err = ext4_htree_store_dirent(dir_file, hinfo->hash,
-					      hinfo->minor_hash, de, &tmp_str);
+					      hinfo->minor_hash, inline_size,
+					      de, &tmp_str);
 		if (err) {
 			ret = err;
 			goto out;
@@ -1427,8 +1446,8 @@ int ext4_read_inline_dir(struct file *file,
 	 * So we will use extra_offset and extra_size to indicate them
 	 * during the inline dir iteration.
 	 */
-	dotdot_offset = ext4_dir_rec_len(1, NULL);
-	dotdot_size = dotdot_offset + ext4_dir_rec_len(2, NULL);
+	dotdot_offset = ext4_dirent_rec_len(1, NULL);
+	dotdot_size = dotdot_offset + ext4_dirent_rec_len(2, NULL);
 	extra_offset = dotdot_size - EXT4_INLINE_DOTDOT_SIZE;
 	extra_size = extra_offset + inline_size;
 
@@ -1463,7 +1482,7 @@ int ext4_read_inline_dir(struct file *file,
 			 * failure will be detected in the
 			 * dirent test below. */
 			if (ext4_rec_len_from_disk(de->rec_len, extra_size)
-				< ext4_dir_rec_len(1, NULL))
+				< ext4_dirent_rec_len(1, NULL))
 				break;
 			i += ext4_rec_len_from_disk(de->rec_len,
 						    extra_size);
diff --git a/fs/ext4/ioctl.c b/fs/ext4/ioctl.c
index c8387e6a2c6e90..b5421d59f7a032 100644
--- a/fs/ext4/ioctl.c
+++ b/fs/ext4/ioctl.c
@@ -1535,6 +1535,87 @@ static int ext4_ioctl_set_tune_sb(struct file *filp,
 	return ret;
 }
 
+/*
+ * ext4_ioctl_set_lufid() - Set LUFID on a directory entry
+ * @filp:	file pointer (parent directory)
+ * @arg:	pointer to ext4_set_lufid structure with filename and LUFID data
+ *
+ * This ioctl allows setting LUFID data on an existing
+ * directory entry. It is called on the parent directory with a filename and
+ * LUFID data.
+ */
+static long ext4_ioctl_set_lufid(struct file *filp, unsigned long arg)
+{
+	struct inode *dir = file_inode(filp);
+	struct mnt_idmap *idmap = file_mnt_idmap(filp);
+	struct ext4_set_lufid lufid_args;
+	struct {
+		__u32 edp_magic;
+		struct ext4_dirent_data_header df_header;
+		char df_fid[255];
+	} edp;
+	int err;
+
+	/* Check if parent is a directory */
+	if (!S_ISDIR(dir->i_mode))
+		return -ENOTDIR;
+
+	/* This ioctl mutates directory entries; merely having the directory
+	 * open (which only ever requires read access) is not enough */
+	err = inode_permission(idmap, dir, MAY_WRITE);
+	if (err)
+		return err;
+
+	/* Copy arguments from user space */
+	if (copy_from_user(&lufid_args, (struct ext4_set_lufid __user *)arg,
+			   sizeof(lufid_args)))
+		return -EFAULT;
+
+	/* Validate parameters.  esl_name_len is NUL-excluded length (1-255). */
+	if (lufid_args.esl_name_len == 0 || lufid_args.esl_name_len > EXT4_NAME_LEN)
+		return -EINVAL;
+
+	/* ddh_length (esl_data_len + the header byte below) must itself fit
+	 * in the __u8 ddh_length field without wrapping */
+	if (lufid_args.esl_data_len == 0 ||
+	    lufid_args.esl_data_len > 255 - sizeof(edp.df_header))
+		return -EINVAL;
+
+	/* Ensure filename is NUL-terminated at exactly esl_name_len */
+	if (lufid_args.esl_name[lufid_args.esl_name_len] != '\0')
+		return -EINVAL;
+
+	/* '.' and '..' are not ordinary entries -- they must stay the first
+	 * two entries in the directory's first block, so they can't go
+	 * through the general delete+re-add path this ioctl uses */
+	if (!strcmp(lufid_args.esl_name, ".") || !strcmp(lufid_args.esl_name, ".."))
+		return -EINVAL;
+
+	/* Prepare the dentry param struct with LUFID data. ddh_length is
+	 * documented (see struct ext4_dirent_data_header) as the length of
+	 * the header plus the whole data blob -- include the header here so
+	 * every dirdata reader/writer that takes ddh_length at face value
+	 * (e.g. ext4_dirdata_set()'s memcpy) copies the full LUFID payload
+	 * instead of silently dropping its last byte. */
+	edp.edp_magic = EXT4_LUFID_MAGIC;
+	edp.df_header.ddh_length = lufid_args.esl_data_len +
+				    sizeof(edp.df_header);
+	memcpy(edp.df_fid, lufid_args.esl_data, lufid_args.esl_data_len);
+
+	/* Want write access */
+	err = mnt_want_write_file(filp);
+	if (err)
+		return err;
+
+	/* Call the helper function to do the actual work */
+	err = ext4_dirdata_set_lufid(dir, lufid_args.esl_name,
+				    lufid_args.esl_name_len,
+				    (struct ext4_dentry_param *)&edp);
+
+	mnt_drop_write_file(filp);
+	return err;
+}
+
 static long __ext4_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
 {
 	struct inode *inode = file_inode(filp);
@@ -1921,6 +2002,8 @@ static long __ext4_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
 					      (void __user *)arg);
 	case EXT4_IOC_SET_TUNE_SB_PARAM:
 		return ext4_ioctl_set_tune_sb(filp, (void __user *)arg);
+	case EXT4_IOC_SET_LUFID:
+		return ext4_ioctl_set_lufid(filp, arg);
 	default:
 		return -ENOTTY;
 	}
@@ -2000,6 +2083,7 @@ long ext4_compat_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
 	case FS_IOC_SETFSLABEL:
 	case EXT4_IOC_GETFSUUID:
 	case EXT4_IOC_SETFSUUID:
+	case EXT4_IOC_SET_LUFID:
 		break;
 	default:
 		return -ENOIOCTLCMD;
diff --git a/fs/ext4/namei.c b/fs/ext4/namei.c
index cc49ae04a6f649..ba39ecc690bc2d 100644
--- a/fs/ext4/namei.c
+++ b/fs/ext4/namei.c
@@ -102,7 +102,7 @@ static struct buffer_head *ext4_append(handle_t *handle,
 }
 
 static int ext4_dx_csum_verify(struct inode *inode,
-			       struct ext4_dir_entry *dirent);
+			       struct ext4_dir_entry_2 *dirent);
 
 /*
  * Hints to ext4_read_dirblock regarding whether we expect a directory
@@ -128,7 +128,7 @@ static struct buffer_head *__ext4_read_dirblock(struct inode *inode,
 						unsigned int line)
 {
 	struct buffer_head *bh;
-	struct ext4_dir_entry *dirent;
+	struct ext4_dir_entry_2 *dirent;
 	int is_dx_block = 0;
 
 	if (block >= inode->i_size >> inode->i_blkbits) {
@@ -160,7 +160,7 @@ static struct buffer_head *__ext4_read_dirblock(struct inode *inode,
 	}
 	if (!bh)
 		return NULL;
-	dirent = (struct ext4_dir_entry *) bh->b_data;
+	dirent = (struct ext4_dir_entry_2 *) bh->b_data;
 	/* Determine whether or not we have an index block */
 	if (is_dx(inode)) {
 		if (block == 0)
@@ -244,22 +244,13 @@ struct dx_entry
  * hash version mod 4 should never be 0.  Sincerely, the paranoia department.
  */
 
-struct dx_root
+struct dx_root_info
 {
-	struct fake_dirent dot;
-	char dot_name[4];
-	struct fake_dirent dotdot;
-	char dotdot_name[4];
-	struct dx_root_info
-	{
-		__le32 reserved_zero;
-		u8 hash_version;
-		u8 info_length; /* 8 */
-		u8 indirect_levels;
-		u8 unused_flags;
-	}
-	info;
-	struct dx_entry	entries[];
+	__le32 reserved_zero;
+	u8 hash_version;
+	u8 info_length; /* 8 */
+	u8 indirect_levels;
+	u8 unused_flags;
 };
 
 struct dx_node
@@ -314,17 +305,16 @@ static struct ext4_dir_entry_tail *get_dirent_tail(struct inode *inode,
 						   struct buffer_head *bh)
 {
 	struct ext4_dir_entry_tail *t;
-	int blocksize = EXT4_BLOCK_SIZE(inode->i_sb);
 
 #ifdef PARANOID
-	struct ext4_dir_entry *d, *top;
+	struct ext4_dir_entry_2 *d, *top;
 
-	d = (struct ext4_dir_entry *)bh->b_data;
-	top = (struct ext4_dir_entry *)(bh->b_data +
-		(blocksize - sizeof(struct ext4_dir_entry_tail)));
-	while (d < top && ext4_rec_len_from_disk(d->rec_len, blocksize))
-		d = (struct ext4_dir_entry *)(((void *)d) +
-		    ext4_rec_len_from_disk(d->rec_len, blocksize));
+	d = (struct ext4_dir_entry_2 *)bh->b_data;
+	top = (struct ext4_dir_entry_2 *)(bh->b_data +
+		(EXT4_BLOCK_SIZE(inode->i_sb) - sizeof(struct ext4_dir_entry_tail)));
+	while (d < top && ext4_rec_len_from_disk(d->rec_len, EXT4_BLOCK_SIZE(inode->i_sb)))
+		d = (struct ext4_dir_entry_2 *)(((void *)d) +
+		    ext4_rec_len_from_disk(d->rec_len, EXT4_BLOCK_SIZE(inode->i_sb)));
 
 	if (d != top)
 		return NULL;
@@ -334,11 +324,7 @@ static struct ext4_dir_entry_tail *get_dirent_tail(struct inode *inode,
 	t = EXT4_DIRENT_TAIL(bh->b_data, EXT4_BLOCK_SIZE(inode->i_sb));
 #endif
 
-	if (t->det_reserved_zero1 ||
-	    (ext4_rec_len_from_disk(t->det_rec_len, blocksize) !=
-	     sizeof(struct ext4_dir_entry_tail)) ||
-	    t->det_reserved_zero2 ||
-	    t->det_reserved_ft != EXT4_FT_DIR_CSUM)
+	if (!ext4_dir_entry_is_tail((struct ext4_dir_entry_2 *)t))
 		return NULL;
 
 	return t;
@@ -410,35 +396,43 @@ int ext4_handle_dirty_dirblock(handle_t *handle,
 }
 
 static struct dx_countlimit *get_dx_countlimit(struct inode *inode,
-					       struct ext4_dir_entry *dirent,
+					       struct ext4_dir_entry_2 *dirent,
 					       int *offset)
 {
-	struct ext4_dir_entry *dp;
+	struct ext4_dir_entry_2 *de;
 	struct dx_root_info *root;
-	int count_offset;
+	int count_offset, dotdot_rec_len;
 	int blocksize = EXT4_BLOCK_SIZE(inode->i_sb);
 	unsigned int rlen = ext4_rec_len_from_disk(dirent->rec_len, blocksize);
 
-	if (rlen == blocksize)
-		count_offset = 8;
-	else if (rlen == 12) {
-		dp = (struct ext4_dir_entry *)(((void *)dirent) + 12);
-		if (ext4_rec_len_from_disk(dp->rec_len, blocksize) != blocksize - 12)
+	if (rlen == blocksize) {
+		count_offset = sizeof(struct dx_node);
+	} else {
+		if (rlen < EXT4_BASE_DIR_LEN ||
+		    rlen + EXT4_BASE_DIR_LEN > blocksize)
+			return NULL;
+		de = (struct ext4_dir_entry_2 *)(((char *)dirent) + rlen);
+		if (le16_to_cpu(de->rec_len) != (blocksize - rlen))
 			return NULL;
-		root = (struct dx_root_info *)(((void *)dp + 12));
+		/* de->rec_len covers whole dx_root block, calculate actual length.
+		 * This is the '..' entry, which never carries the casefold+fscrypt
+		 * hash, so pass NULL for dir regardless of the directory's flags */
+		dotdot_rec_len = ext4_dir_entry_len(de, blocksize, NULL);
+		if (rlen + dotdot_rec_len + sizeof(struct dx_root_info) > blocksize)
+			return NULL;
+		root = (struct dx_root_info *)(((char *)de + dotdot_rec_len));
 		if (root->reserved_zero ||
 		    root->info_length != sizeof(struct dx_root_info))
 			return NULL;
-		count_offset = 32;
-	} else
-		return NULL;
+		count_offset = root->info_length + rlen + dotdot_rec_len;
+	}
 
 	if (offset)
 		*offset = count_offset;
 	return (struct dx_countlimit *)(((void *)dirent) + count_offset);
 }
 
-static __le32 ext4_dx_csum(struct inode *inode, struct ext4_dir_entry *dirent,
+static __le32 ext4_dx_csum(struct inode *inode, struct ext4_dir_entry_2 *dirent,
 			   int count_offset, int count, struct dx_tail *t)
 {
 	struct ext4_inode_info *ei = EXT4_I(inode);
@@ -456,7 +450,7 @@ static __le32 ext4_dx_csum(struct inode *inode, struct ext4_dir_entry *dirent,
 }
 
 static int ext4_dx_csum_verify(struct inode *inode,
-			       struct ext4_dir_entry *dirent)
+			       struct ext4_dir_entry_2 *dirent)
 {
 	struct dx_countlimit *c;
 	struct dx_tail *t;
@@ -477,6 +471,10 @@ static int ext4_dx_csum_verify(struct inode *inode,
 		warn_no_space_for_csum(inode);
 		return 0;
 	}
+	if (count > limit) {
+		EXT4_ERROR_INODE(inode, "dir seems corrupt?  Run e2fsck -D.");
+		return 0;
+	}
 	t = (struct dx_tail *)(((struct dx_entry *)c) + limit);
 
 	if (t->dt_checksum != ext4_dx_csum(inode, dirent, count_offset,
@@ -485,7 +483,7 @@ static int ext4_dx_csum_verify(struct inode *inode,
 	return 1;
 }
 
-static void ext4_dx_csum_set(struct inode *inode, struct ext4_dir_entry *dirent)
+static void ext4_dx_csum_set(struct inode *inode, struct ext4_dir_entry_2 *dirent)
 {
 	struct dx_countlimit *c;
 	struct dx_tail *t;
@@ -506,6 +504,10 @@ static void ext4_dx_csum_set(struct inode *inode, struct ext4_dir_entry *dirent)
 		warn_no_space_for_csum(inode);
 		return;
 	}
+	if (count > limit) {
+		EXT4_ERROR_INODE(inode, "dir seems corrupt?  Run e2fsck -D.");
+		return;
+	}
 	t = (struct dx_tail *)(((struct dx_entry *)c) + limit);
 
 	t->dt_checksum = ext4_dx_csum(inode, dirent, count_offset, count, t);
@@ -515,7 +517,7 @@ static inline int ext4_handle_dirty_dx_node(handle_t *handle,
 					    struct inode *inode,
 					    struct buffer_head *bh)
 {
-	ext4_dx_csum_set(inode, (struct ext4_dir_entry *)bh->b_data);
+	ext4_dx_csum_set(inode, (struct ext4_dir_entry_2 *)bh->b_data);
 	return ext4_handle_dirty_metadata(handle, inode, bh);
 }
 
@@ -533,6 +535,30 @@ ext4_next_entry(struct ext4_dir_entry_2 *p, unsigned long blocksize)
  * Future: use high four bits of block for coalesce-on-delete flags
  * Mask them off for now.
  */
+static struct dx_root_info *dx_get_dx_info(struct inode *dir, void *de_buf)
+{
+	unsigned int blocksize = dir->i_sb->s_blocksize;
+	void *base = de_buf;
+
+	/* '.' and '..' never carry the casefold+fscrypt hash, so pass NULL
+	 * for dir regardless of the directory's flags */
+	if (ext4_rec_len_from_disk(((struct ext4_dir_entry_2 *)de_buf)->rec_len,
+				   blocksize) < EXT4_BASE_DIR_LEN)
+		return ERR_PTR(-EFSCORRUPTED);
+	de_buf += ext4_dir_entry_len(de_buf, blocksize, NULL);
+
+	/* dx root info is after dotdot entry */
+	if (de_buf < base || (char *)de_buf - (char *)base +
+	    EXT4_BASE_DIR_LEN > blocksize)
+		return ERR_PTR(-EFSCORRUPTED);
+	de_buf += ext4_dir_entry_len(de_buf, blocksize, NULL);
+
+	if (de_buf < base || (char *)de_buf - (char *)base +
+			      sizeof(struct dx_root_info) > blocksize)
+		return ERR_PTR(-EFSCORRUPTED);
+
+	return (struct dx_root_info *)de_buf;
+}
 
 static inline ext4_lblk_t dx_get_block(struct dx_entry *entry)
 {
@@ -574,11 +600,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);
@@ -587,8 +619,10 @@ static inline unsigned dx_root_limit(struct inode *dir, unsigned infosize)
 
 static inline unsigned dx_node_limit(struct inode *dir)
 {
+	/* dx_node fake_dirent is always 8 bytes — it never carries a casefold
+	 * hash, so pass NULL to suppress the hash-size term. */
 	unsigned int entry_space = dir->i_sb->s_blocksize -
-			ext4_dir_rec_len(0, dir);
+			ext4_dirent_rec_len(0, NULL);
 
 	if (ext4_has_feature_metadata_csum(dir->i_sb))
 		entry_space -= sizeof(struct dx_tail);
@@ -698,7 +732,9 @@ static struct stats dx_show_leaf(struct inode *dir,
 				       (unsigned) ((char *) de - base));
 #endif
 			}
-			space += ext4_dir_rec_len(de->name_len, dir);
+			if ((char *)de + EXT4_BASE_DIR_LEN > base + size)
+				break;
+			space += ext4_dir_entry_len(de, size, dir);
 			names++;
 		}
 		de = ext4_next_entry(de, size);
@@ -780,7 +816,7 @@ dx_probe(struct ext4_filename *fname, struct inode *dir,
 {
 	unsigned count, indirect, level, i;
 	struct dx_entry *at, *entries, *p, *q, *m;
-	struct dx_root *root;
+	struct dx_root_info *info;
 	struct dx_frame *frame = frame_in;
 	struct dx_frame *ret_err = ERR_PTR(ERR_BAD_DX_DIR);
 	u32 hash;
@@ -792,23 +828,31 @@ dx_probe(struct ext4_filename *fname, struct inode *dir,
 	if (IS_ERR(frame->bh))
 		return (struct dx_frame *) frame->bh;
 
-	root = (struct dx_root *) frame->bh->b_data;
-	if (root->info.hash_version != DX_HASH_TEA &&
-	    root->info.hash_version != DX_HASH_HALF_MD4 &&
-	    root->info.hash_version != DX_HASH_LEGACY &&
-	    root->info.hash_version != DX_HASH_SIPHASH) {
-		ext4_warning_inode(dir, "Unrecognised inode hash code %u",
-				   root->info.hash_version);
+	info = dx_get_dx_info(dir, (struct ext4_dir_entry_2 *)frame->bh->b_data);
+	if (IS_ERR(info))
+		goto fail;
+	if (info->info_length != sizeof(struct dx_root_info)) {
+		ext4_warning_inode(dir, "corrupted htree: info_length %u",
+				   info->info_length);
+		goto fail;
+	}
+	if (info->hash_version != DX_HASH_TEA &&
+	    info->hash_version != DX_HASH_HALF_MD4 &&
+	    info->hash_version != DX_HASH_LEGACY &&
+	    info->hash_version != DX_HASH_SIPHASH) {
+		ext4_warning(dir->i_sb,
+			"Unrecognised inode hash code %d for directory #%llu",
+			info->hash_version, dir->i_ino);
 		goto fail;
 	}
 	if (ext4_hash_in_dirent(dir)) {
-		if (root->info.hash_version != DX_HASH_SIPHASH) {
+		if (info->hash_version != DX_HASH_SIPHASH) {
 			ext4_warning_inode(dir,
 				"Hash in dirent, but hash is not SIPHASH");
 			goto fail;
 		}
 	} else {
-		if (root->info.hash_version == DX_HASH_SIPHASH) {
+		if (info->hash_version == DX_HASH_SIPHASH) {
 			ext4_warning_inode(dir,
 				"Hash code is SIPHASH, but hash not in dirent");
 			goto fail;
@@ -816,7 +860,7 @@ dx_probe(struct ext4_filename *fname, struct inode *dir,
 	}
 	if (fname)
 		hinfo = &fname->hinfo;
-	hinfo->hash_version = root->info.hash_version;
+	hinfo->hash_version = info->hash_version;
 	if (hinfo->hash_version <= DX_HASH_TEA)
 		hinfo->hash_version += EXT4_SB(dir->i_sb)->s_hash_unsigned;
 	hinfo->seed = EXT4_SB(dir->i_sb)->s_hash_seed;
@@ -832,13 +876,13 @@ dx_probe(struct ext4_filename *fname, struct inode *dir,
 	}
 	hash = hinfo->hash;
 
-	if (root->info.unused_flags & 1) {
+	if (info->unused_flags & 1) {
 		ext4_warning_inode(dir, "Unimplemented hash flags: %#06x",
-				   root->info.unused_flags);
+				   info->unused_flags);
 		goto fail;
 	}
 
-	indirect = root->info.indirect_levels;
+	indirect = info->indirect_levels;
 	if (indirect >= ext4_dir_htree_level(dir->i_sb)) {
 		ext4_warning(dir->i_sb,
 			     "Directory (ino: %llu) htree depth %#06x exceed"
@@ -851,14 +895,15 @@ dx_probe(struct ext4_filename *fname, struct inode *dir,
 		goto fail;
 	}
 
-	entries = (struct dx_entry *)(((char *)&root->info) +
-				      root->info.info_length);
+	entries = (struct dx_entry *)(((char *)info) + info->info_length);
 
-	if (dx_get_limit(entries) != dx_root_limit(dir,
-						   root->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, root->info.info_length));
+				   dx_root_limit(dir,
+				   (struct ext4_dir_entry_2 *)frame->bh->b_data
+				   ));
 		goto fail;
 	}
 
@@ -935,7 +980,7 @@ dx_probe(struct ext4_filename *fname, struct inode *dir,
 	return ret_err;
 }
 
-static void dx_release(struct dx_frame *frames)
+static void dx_release(struct inode *dir, struct dx_frame *frames)
 {
 	struct dx_root_info *info;
 	int i;
@@ -944,7 +989,9 @@ static void dx_release(struct dx_frame *frames)
 	if (frames[0].bh == NULL)
 		return;
 
-	info = &((struct dx_root *)frames[0].bh->b_data)->info;
+	info = dx_get_dx_info(dir, (struct ext4_dir_entry_2 *)frames[0].bh->b_data);
+	if (IS_ERR(info))
+		return;
 	/* save local copy, "info" may be freed after brelse() */
 	indirect_levels = info->indirect_levels;
 	for (i = 0; i <= indirect_levels; i++) {
@@ -1056,7 +1103,7 @@ static int htree_dirblock_to_tree(struct file *dir_file,
 	/* csum entries are not larger in the casefolded encrypted case */
 	top = (struct ext4_dir_entry_2 *) ((char *) de +
 					   dir->i_sb->s_blocksize -
-					   ext4_dir_rec_len(0,
+					   ext4_dirent_rec_len(0,
 							   csum ? NULL : dir));
 	/* Check if the directory is encrypted */
 	if (IS_ENCRYPTED(dir)) {
@@ -1081,22 +1128,23 @@ static int htree_dirblock_to_tree(struct file *dir_file,
 			/* silently ignore the rest of the block */
 			break;
 		}
-		if (ext4_hash_in_dirent(dir)) {
-			if (de->name_len && de->inode) {
-				hinfo->hash = EXT4_DIRENT_HASH(de);
-				hinfo->minor_hash = EXT4_DIRENT_MINOR_HASH(de);
-			} else {
-				hinfo->hash = 0;
-				hinfo->minor_hash = 0;
-			}
+		if (de->name_len && de->inode) {
+			/* check for saved hash first, or generate it from name */
+			if (!(ext4_dirdata_get(de, dir, dir->i_sb->s_blocksize,
+					       NULL, hinfo) &
+			      EXT4_DIRENT_CFHASH)) {
+				err = ext4fs_dirhash(dir, de->name,
+						     de->name_len, hinfo);
+				if (err < 0) {
+					count = err;
+					goto errout;
+				}
+			 }
 		} else {
-			err = ext4fs_dirhash(dir, de->name,
-					     de->name_len, hinfo);
-			if (err < 0) {
-				count = err;
-				goto errout;
-			}
+			hinfo->hash = 0;
+			hinfo->minor_hash = 0;
 		}
+
 		if ((hinfo->hash < start_hash) ||
 		    ((hinfo->hash == start_hash) &&
 		     (hinfo->minor_hash < start_minor_hash)))
@@ -1107,8 +1155,8 @@ static int htree_dirblock_to_tree(struct file *dir_file,
 			tmp_str.name = de->name;
 			tmp_str.len = de->name_len;
 			err = ext4_htree_store_dirent(dir_file,
-				   hinfo->hash, hinfo->minor_hash, de,
-				   &tmp_str);
+				   hinfo->hash, hinfo->minor_hash,
+				   dir->i_sb->s_blocksize, de, &tmp_str);
 		} else {
 			int save_len = fname_crypto_str.len;
 			struct fscrypt_str de_name = FSTR_INIT(de->name,
@@ -1123,8 +1171,9 @@ static int htree_dirblock_to_tree(struct file *dir_file,
 				goto errout;
 			}
 			err = ext4_htree_store_dirent(dir_file,
-				   hinfo->hash, hinfo->minor_hash, de,
-					&fname_crypto_str);
+						hinfo->hash, hinfo->minor_hash,
+						dir->i_sb->s_blocksize, de,
+						&fname_crypto_str);
 			fname_crypto_str.len = save_len;
 		}
 		if (err != 0) {
@@ -1202,6 +1251,7 @@ int ext4_htree_fill_tree(struct file *dir_file, __u32 start_hash,
 		tmp_str.name = de->name;
 		tmp_str.len = de->name_len;
 		err = ext4_htree_store_dirent(dir_file, 0, 0,
+					      dir->i_sb->s_blocksize,
 					      de, &tmp_str);
 		if (err != 0)
 			goto errout;
@@ -1213,6 +1263,7 @@ int ext4_htree_fill_tree(struct file *dir_file, __u32 start_hash,
 		tmp_str.name = de->name;
 		tmp_str.len = de->name_len;
 		err = ext4_htree_store_dirent(dir_file, 2, 0,
+					      dir->i_sb->s_blocksize,
 					      de, &tmp_str);
 		if (err != 0)
 			goto errout;
@@ -1250,12 +1301,12 @@ int ext4_htree_fill_tree(struct file *dir_file, __u32 start_hash,
 		    (count && ((hashval & 1) == 0)))
 			break;
 	}
-	dx_release(frames);
+	dx_release(dir, frames);
 	dxtrace(printk(KERN_DEBUG "Fill tree: returned %d entries, "
 		       "next hash: %x\n", count, *next_hash));
 	return count;
 errout:
-	dx_release(frames);
+	dx_release(dir, frames);
 	return (err);
 }
 
@@ -1273,6 +1324,207 @@ static inline int search_dirblock(struct buffer_head *bh,
  * Directory block splitting, compacting
  */
 
+/*
+ * ext4_dirdata_get() - Read dirdata fields from a directory entry.
+ * @de:         directory entry
+ * @dir:        directory inode (used for fscrypt+casefold hash fallback)
+ * @dfid:      if non-NULL and EXT4_DIRENT_LUFID is set, LUFID data is copied
+ * 		here
+ * @hinfo:	if non-NULL, receives the casefold hash and minor hash
+ *
+ * Reads any dirdata stored in @de.  If the dirdata feature is not enabled,
+ * falls back to reading the hash stored inline after the filename (for
+ * compatibility with the older casefold+fscrypt format).
+ *
+ * Returns a bitmask of EXT4_DIRENT_* flags indicating which fields were read.
+ *
+ * Compatibility note: enabling EXT4_FEATURE_INCOMPAT_DIRDATA on a filesystem
+ * that already has casefolded+encrypted directories is NOT safe without a
+ * prior migration pass.  Before dirdata, the casefold+fscrypt hash was stored
+ * as a raw 8 bytes immediately after the filename with no flag in file_type.
+ * After dirdata is enabled, this function expects the hash to be present only
+ * when EXT4_DIRENT_CFHASH (0x40) is set in file_type; existing entries that
+ * carry the raw hash are silently misread as having no hash at all, breaking
+ * directory lookups.  e2fsck must be run to convert all affected entries to
+ * the EXT4_DIRENT_CFHASH extension format before the feature flag is set with
+ * tune2fs.  Detection heuristic: in a casefold+encrypted directory, an entry
+ * with rec_len >= round_up(name_len, 4) + 8 and no EXT4_DIRENT_CFHASH bit
+ * carries a raw pre-dirdata hash that must be migrated.
+ */
+unsigned char ext4_dirdata_get(struct ext4_dir_entry_2 *de, struct inode *dir,
+			       int buf_size,
+			       struct ext4_dirent_fid *dfid,
+			       struct dx_hash_info *hinfo)
+{
+	unsigned char ret = 0;
+	unsigned int data_offset = de->name_len + 1;
+	unsigned int rec_len = ext4_rec_len_from_disk(de->rec_len, buf_size);
+
+	/* data_offset is relative to de->name, which itself starts
+	 * EXT4_BASE_DIR_LEN bytes into the entry -- rec_len is relative to
+	 * the start of the entry, so add the header size before comparing,
+	 * or this lets reads run EXT4_BASE_DIR_LEN bytes past the entry. */
+	if (EXT4_BASE_DIR_LEN + data_offset > rec_len)
+		return ret;
+
+	/* compatibility: hash stored inline after filename (no dirdata) */
+	if (hinfo && !ext4_has_feature_dirdata(dir->i_sb) &&
+	    ext4_hash_in_dirent(dir)) {
+		hinfo->hash = EXT4_DIRENT_HASH(de);
+		hinfo->minor_hash = EXT4_DIRENT_MINOR_HASH(de);
+		ret |= EXT4_DIRENT_CFHASH;
+		return ret;
+	}
+
+	/* EXT4_DIRENT_* bits are only meaningful when the feature is enabled */
+	if (!ext4_has_feature_dirdata(dir->i_sb))
+		return ret;
+
+	if (de->file_type & EXT4_DIRENT_LUFID) {
+		struct ext4_dirent_fid *disk_fid =
+			(struct ext4_dirent_fid *)((char *)de +
+			EXT4_BASE_DIR_LEN + data_offset);
+		unsigned int dlen;
+		/* struct ext4_fid df_fid[] does not provide the array size.
+		 * First, verify that the header lies within the valid area, then
+ 		 * verify that the entire record fits within it. */
+		if (EXT4_BASE_DIR_LEN + data_offset +
+		    sizeof(disk_fid->df_header) > rec_len)
+			return ret;
+
+		dlen = disk_fid->df_header.ddh_length;
+		if (dlen == 0 ||
+		    EXT4_BASE_DIR_LEN + data_offset + dlen > rec_len)
+			return ret;
+
+		if (dfid) {
+			/* The size cap below ensures the record holds at least
+			 * one complete struct ext4_fid. */
+			if (dlen > sizeof(struct ext4_dirent_data_header) +
+				    sizeof(struct ext4_fid))
+				return ret;
+			memcpy(dfid, disk_fid, dlen);
+			ret |= EXT4_DIRENT_LUFID;
+		}
+		data_offset += dlen;
+	}
+
+	/* Skip INO64 for now*/
+	if (de->file_type & EXT4_DIRENT_INO64) {
+		struct ext4_dirent_data_header *ddh =
+		       (struct ext4_dirent_data_header *)((char *)de + EXT4_BASE_DIR_LEN + data_offset);
+		unsigned int dlen;
+
+		if (EXT4_BASE_DIR_LEN + data_offset + sizeof(*ddh) > rec_len)
+			return ret;
+
+		dlen = ddh->ddh_length;
+		if (dlen < sizeof(*ddh) ||
+		    EXT4_BASE_DIR_LEN + data_offset + dlen > rec_len)
+			return ret;
+
+		data_offset += dlen;
+	}
+
+	if (!hinfo)
+		return ret;
+
+	if (de->file_type & EXT4_DIRENT_CFHASH) {
+		struct ext4_dirent_hash *dh =
+			(struct ext4_dirent_hash *)((char *)de + EXT4_BASE_DIR_LEN + data_offset);
+		unsigned int dlen;
+
+		if (EXT4_BASE_DIR_LEN + data_offset + sizeof(*dh) > rec_len)
+			return ret;
+		dlen = dh->dh_header.ddh_length;
+		if (dlen < sizeof(*dh) ||
+		    EXT4_BASE_DIR_LEN + data_offset + dlen > rec_len)
+			return ret;
+
+		hinfo->hash = le32_to_cpu(dh->dh_hash.hash);
+		hinfo->minor_hash = le32_to_cpu(dh->dh_hash.minor_hash);
+		ret |= EXT4_DIRENT_CFHASH;
+	}
+
+	return ret;
+}
+
+/*
+ * ext4_dirdata_set() - Write dirdata fields into a directory entry.
+ * @de:    directory entry (name must already be set)
+ * @dir:   directory inode
+ * @data:  LUFID data to store (or NULL)
+ * @fname: filename info carrying the casefold hash
+ *
+ * Writes any required dirdata into @de after the filename.  If the dirdata
+ * feature is not enabled, falls back to writing the hash inline after the
+ * filename (for compatibility with the older casefold+fscrypt format).
+ *
+ * See ext4_dirdata_get() for the compatibility constraint: enabling the
+ * dirdata feature on a filesystem with existing casefolded+encrypted
+ * directories requires an e2fsck migration pass before tune2fs sets the
+ * EXT4_FEATURE_INCOMPAT_DIRDATA superblock flag.
+ */
+static void ext4_dirdata_set(struct ext4_dir_entry_2 *de, struct inode *dir,
+			     struct ext4_dirent_fid *dfid,
+			     struct ext4_filename *fname)
+{
+	struct dx_hash_info *hinfo = &fname->hinfo;
+	unsigned int data_offset = de->name_len + 1;
+	unsigned int rec_len = ext4_rec_len_from_disk(de->rec_len,
+						       dir->i_sb->s_blocksize);
+
+	/* Clear the gap byte between the filename and the first dirdata
+	 * extension to avoid leaking stale memory to disk.  Use pointer
+	 * arithmetic rather than de->name[name_len] to stay within the
+	 * declared name[] array bounds under FORTIFY_SOURCE.  Only write it
+	 * when there is actually room (entries that exactly fill their slot
+	 * have rec_len == EXT4_BASE_DIR_LEN + name_len with no gap). */
+	if (EXT4_BASE_DIR_LEN + data_offset <= rec_len)
+		*((char *)de + EXT4_BASE_DIR_LEN + de->name_len) = 0;
+
+	if (dfid) {
+		unsigned int dlen = dfid->df_header.ddh_length;
+
+		if (EXT4_BASE_DIR_LEN + data_offset + dlen > rec_len) {
+			EXT4_ERROR_INODE(dir, "Can not insert FID");
+			return;
+		}
+
+		memcpy((char *)de + EXT4_BASE_DIR_LEN + data_offset, dfid, dlen);
+		de->file_type |= EXT4_DIRENT_LUFID;
+		data_offset += dlen;
+	}
+
+	if (ext4_hash_in_dirent(dir)) {
+		if (ext4_has_feature_dirdata(dir->i_sb)) {
+			struct ext4_dirent_hash *dh =
+			    (struct ext4_dirent_hash *)((char *)de + EXT4_BASE_DIR_LEN + data_offset);
+
+			if (EXT4_BASE_DIR_LEN + data_offset + sizeof(*dh) > rec_len) {
+				EXT4_ERROR_INODE(dir, "Can not insert dhash dirdata");
+				return;
+			}
+
+			dh->dh_header.ddh_length = sizeof(*dh);
+			dh->dh_hash.hash = cpu_to_le32(hinfo->hash);
+			dh->dh_hash.minor_hash = cpu_to_le32(hinfo->minor_hash);
+			de->file_type |= EXT4_DIRENT_CFHASH;
+		} else {
+			/* Compatibility: store hash inline after filename */
+			if (EXT4_BASE_DIR_LEN + data_offset +
+			    sizeof(struct ext4_dir_entry_hash) > rec_len) {
+				EXT4_ERROR_INODE(dir, "Can not insert dhash");
+				return;
+			}
+
+			EXT4_DIRENT_HASHES(de)->hash = cpu_to_le32(hinfo->hash);
+			EXT4_DIRENT_HASHES(de)->minor_hash =
+						cpu_to_le32(hinfo->minor_hash);
+		}
+	}
+}
+
 /*
  * Create map of hash values, offsets, and sizes, stored at end of block.
  * Returns number of entries mapped.
@@ -1296,9 +1548,9 @@ static int dx_make_map(struct inode *dir, struct buffer_head *bh,
 					 ((char *)de) - base))
 			return -EFSCORRUPTED;
 		if (de->name_len && de->inode) {
-			if (ext4_hash_in_dirent(dir))
-				h.hash = EXT4_DIRENT_HASH(de);
-			else {
+			if (!(ext4_dirdata_get(de, dir, dir->i_sb->s_blocksize,
+					       NULL, &h) &
+						EXT4_DIRENT_CFHASH)) {
 				int err = ext4fs_dirhash(dir, de->name,
 						     de->name_len, &h);
 				if (err < 0)
@@ -1488,7 +1740,7 @@ int ext4_search_dir(struct buffer_head *bh, char *search_buf, int buf_size,
 }
 
 static int is_dx_internal_node(struct inode *dir, ext4_lblk_t block,
-			       struct ext4_dir_entry *de)
+			       struct ext4_dir_entry_2 *de)
 {
 	struct super_block *sb = dir->i_sb;
 
@@ -1619,7 +1871,7 @@ static struct buffer_head *__ext4_find_entry(struct inode *dir,
 		}
 		if (!buffer_verified(bh) &&
 		    !is_dx_internal_node(dir, block,
-					 (struct ext4_dir_entry *)bh->b_data) &&
+					 (struct ext4_dir_entry_2 *)bh->b_data) &&
 		    !ext4_dirblock_csum_verify(dir, bh)) {
 			EXT4_ERROR_INODE_ERR(dir, EFSBADCRC,
 					     "checksumming directory "
@@ -1753,7 +2005,7 @@ static struct buffer_head * ext4_dx_find_entry(struct inode *dir,
 errout:
 	dxtrace(printk(KERN_DEBUG "%s not found\n", fname->usr_fname->name));
 success:
-	dx_release(frames);
+	dx_release(dir, frames);
 	return bh;
 }
 
@@ -1850,7 +2102,7 @@ dx_move_dirents(struct inode *dir, char *from, char *to,
 	while (count--) {
 		struct ext4_dir_entry_2 *de = (struct ext4_dir_entry_2 *)
 						(from + (map->offs<<2));
-		rec_len = ext4_dir_rec_len(de->name_len, dir);
+		rec_len = ext4_dir_entry_len(de, blocksize, dir);
 
 		memcpy (to, de, rec_len);
 		((struct ext4_dir_entry_2 *) to)->rec_len =
@@ -1883,7 +2135,7 @@ static struct ext4_dir_entry_2 *dx_pack_dirents(struct inode *dir, char *base,
 	while ((char*)de < base + blocksize) {
 		next = ext4_next_entry(de, blocksize);
 		if (de->inode && de->name_len) {
-			rec_len = ext4_dir_rec_len(de->name_len, dir);
+			rec_len = ext4_dir_entry_len(de, blocksize, dir);
 			if (de > to)
 				memmove(to, de, rec_len);
 			to->rec_len = ext4_rec_len_to_disk(rec_len, blocksize);
@@ -2035,10 +2287,15 @@ static struct ext4_dir_entry_2 *do_split(handle_t *handle, struct inode *dir,
 int ext4_find_dest_de(struct inode *dir, struct buffer_head *bh,
 		      void *buf, int buf_size,
 		      struct ext4_filename *fname,
-		      struct ext4_dir_entry_2 **dest_de)
+		      struct ext4_dir_entry_2 **dest_de,
+		      int dlen)
 {
 	struct ext4_dir_entry_2 *de;
-	unsigned short reclen = ext4_dir_rec_len(fname_len(fname), dir);
+	/* When dirdata extensions are present, a NUL gap byte sits between
+	 * the filename and the first extension; account for it so the found
+	 * slot is always large enough for ext4_dirdata_set() to write into. */
+	unsigned short reclen = ext4_dirent_rec_len(fname_len(fname) + dlen +
+						    (dlen ? 1 : 0), dir);
 	int nlen, rlen;
 	unsigned int offset = 0;
 	char *top;
@@ -2051,7 +2308,7 @@ int ext4_find_dest_de(struct inode *dir, struct buffer_head *bh,
 			return -EFSCORRUPTED;
 		if (ext4_match(dir, fname, de))
 			return -EEXIST;
-		nlen = ext4_dir_rec_len(de->name_len, dir);
+		nlen = ext4_dir_entry_len(de, buf_size, dir);
 		rlen = ext4_rec_len_from_disk(de->rec_len, buf_size);
 		if ((de->inode ? rlen - nlen : rlen) >= reclen)
 			break;
@@ -2065,16 +2322,13 @@ int ext4_find_dest_de(struct inode *dir, struct buffer_head *bh,
 	return 0;
 }
 
-void ext4_insert_dentry(struct inode *dir,
-			struct inode *inode,
-			struct ext4_dir_entry_2 *de,
-			int buf_size,
-			struct ext4_filename *fname)
+void ext4_insert_dentry_data(struct inode *dir, struct inode *inode,
+			     struct ext4_dir_entry_2 *de, int buf_size,
+			     struct ext4_filename *fname, void *data)
 {
-
 	int nlen, rlen;
 
-	nlen = ext4_dir_rec_len(de->name_len, dir);
+	nlen = ext4_dir_entry_len(de, buf_size, dir);
 	rlen = ext4_rec_len_from_disk(de->rec_len, buf_size);
 	if (de->inode) {
 		struct ext4_dir_entry_2 *de1 =
@@ -2088,13 +2342,7 @@ void ext4_insert_dentry(struct inode *dir,
 	ext4_set_de_type(inode->i_sb, de, inode->i_mode);
 	de->name_len = fname_len(fname);
 	memcpy(de->name, fname_name(fname), fname_len(fname));
-	if (ext4_hash_in_dirent(dir)) {
-		struct dx_hash_info *hinfo = &fname->hinfo;
-
-		EXT4_DIRENT_HASHES(de)->hash = cpu_to_le32(hinfo->hash);
-		EXT4_DIRENT_HASHES(de)->minor_hash =
-						cpu_to_le32(hinfo->minor_hash);
-	}
+	ext4_dirdata_set(de, dir, data, fname);
 }
 
 /*
@@ -2112,14 +2360,28 @@ static int add_dirent_to_buf(handle_t *handle, struct ext4_filename *fname,
 {
 	unsigned int	blocksize = dir->i_sb->s_blocksize;
 	int		csum_size = 0;
-	int		err, err2;
+	int		err, err2, dlen = 0;
+	struct ext4_dirent_fid *dfid;
 
 	if (ext4_has_feature_metadata_csum(inode->i_sb))
 		csum_size = sizeof(struct ext4_dir_entry_tail);
 
+	/* dfid is resolved once by ext4_add_entry() and stored in fname so
+	 * that all add_dirent_to_buf() calls within the same add operation
+	 * use a consistent value without touching the shared i_dirdata field. */
+	dfid = fname->dfid;
 	if (!de) {
+		if (dfid)
+			dlen = dfid->df_header.ddh_length;
+		/* ext4_dirent_rec_len() does not add CFHASH size when dirdata is
+		 * enabled (it assumes the caller included it in name_len).  Add
+		 * it here so ext4_find_dest_de() allocates a slot large enough
+		 * for ext4_dirdata_set() to write the hash extension. */
+		if (ext4_hash_in_dirent(dir) &&
+		    ext4_has_feature_dirdata(dir->i_sb))
+			dlen += sizeof(struct ext4_dirent_hash);
 		err = ext4_find_dest_de(dir, bh, bh->b_data,
-					blocksize - csum_size, fname, &de);
+					blocksize - csum_size, fname, &de, dlen);
 		if (err)
 			return err;
 	}
@@ -2132,7 +2394,7 @@ static int add_dirent_to_buf(handle_t *handle, struct ext4_filename *fname,
 	}
 
 	/* By now the buffer is marked for journaling */
-	ext4_insert_dentry(dir, inode, de, blocksize, fname);
+	ext4_insert_dentry_data(dir, inode, de, blocksize, fname, dfid);
 
 	/*
 	 * XXX shouldn't update any times until successful
@@ -2156,44 +2418,38 @@ static int add_dirent_to_buf(handle_t *handle, struct ext4_filename *fname,
 	return err ? err : err2;
 }
 
-static bool ext4_check_dx_root(struct inode *dir, struct dx_root *root)
+static bool ext4_check_dx_root(struct inode *dir,
+			       struct ext4_dir_entry_2 *dot_de,
+			       struct ext4_dir_entry_2 *dotdot_de,
+			       struct ext4_dir_entry_2 **entry)
 {
-	struct fake_dirent *fde;
 	const char *error_msg;
-	unsigned int rlen;
 	unsigned int blocksize = dir->i_sb->s_blocksize;
-	char *blockend = (char *)root + dir->i_sb->s_blocksize;
+	struct ext4_dir_entry_2 *de = NULL;
 
-	fde = &root->dot;
-	if (unlikely(fde->name_len != 1)) {
+	if (unlikely(dot_de->name_len != 1)) {
 		error_msg = "invalid name_len for '.'";
 		goto corrupted;
 	}
-	if (unlikely(strncmp(root->dot_name, ".", fde->name_len))) {
+	if (unlikely(strncmp(dot_de->name, ".", dot_de->name_len))) {
 		error_msg = "invalid name for '.'";
 		goto corrupted;
 	}
-	rlen = ext4_rec_len_from_disk(fde->rec_len, blocksize);
-	if (unlikely((char *)fde + rlen >= blockend)) {
-		error_msg = "invalid rec_len for '.'";
-		goto corrupted;
-	}
 
-	fde = &root->dotdot;
-	if (unlikely(fde->name_len != 2)) {
+	if (unlikely(dotdot_de->name_len != 2)) {
 		error_msg = "invalid name_len for '..'";
 		goto corrupted;
 	}
-	if (unlikely(strncmp(root->dotdot_name, "..", fde->name_len))) {
+	if (unlikely(strncmp(dotdot_de->name, "..", dotdot_de->name_len))) {
 		error_msg = "invalid name for '..'";
 		goto corrupted;
 	}
-	rlen = ext4_rec_len_from_disk(fde->rec_len, blocksize);
-	if (unlikely((char *)fde + rlen >= blockend)) {
+	de = ext4_next_entry(dotdot_de, blocksize);
+	if ((char *)de >= (((char *)dot_de) + blocksize)) {
 		error_msg = "invalid rec_len for '..'";
 		goto corrupted;
 	}
-
+	*entry = de;
 	return true;
 
 corrupted:
@@ -2211,16 +2467,15 @@ static int make_indexed_dir(handle_t *handle, struct ext4_filename *fname,
 			    struct inode *inode, struct buffer_head *bh)
 {
 	struct buffer_head *bh2;
-	struct dx_root	*root;
 	struct dx_frame	frames[EXT4_HTREE_LEVEL], *frame;
 	struct dx_entry *entries;
-	struct ext4_dir_entry_2	*de, *de2;
+	struct ext4_dir_entry_2	*de, *de2, *dot_de, *dotdot_de;
 	char		*data2, *top;
 	unsigned	len;
 	int		retval;
 	unsigned	blocksize;
 	ext4_lblk_t  block;
-	struct fake_dirent *fde;
+	struct dx_root_info *dx_info;
 	int csum_size = 0;
 
 	if (ext4_has_feature_metadata_csum(inode->i_sb))
@@ -2237,17 +2492,20 @@ static int make_indexed_dir(handle_t *handle, struct ext4_filename *fname,
 		return retval;
 	}
 
-	root = (struct dx_root *) bh->b_data;
-	if (!ext4_check_dx_root(dir, root)) {
+	dot_de = (struct ext4_dir_entry_2 *)bh->b_data;
+	if (ext4_rec_len_from_disk(dot_de->rec_len, blocksize) < EXT4_BASE_DIR_LEN ||
+	    ext4_rec_len_from_disk(dot_de->rec_len, blocksize) >= blocksize - EXT4_BASE_DIR_LEN) {
+		brelse(bh);
+		return -EFSCORRUPTED;
+	}
+	dotdot_de = ext4_next_entry(dot_de, blocksize);
+	if (!ext4_check_dx_root(dir, dot_de, dotdot_de, &de)) {
 		brelse(bh);
 		return -EFSCORRUPTED;
 	}
 
 	/* The 0th block becomes the root, move the dirents out */
-	fde = &root->dotdot;
-	de = (struct ext4_dir_entry_2 *)((char *)fde +
-		ext4_rec_len_from_disk(fde->rec_len, blocksize));
-	len = ((char *) root) + (blocksize - csum_size) - (char *) de;
+	len = ((char *)dot_de) + (blocksize - csum_size) - (char *)de;
 
 	/* Allocate new block for the 0th block's dirents */
 	bh2 = ext4_append(handle, dir, &block);
@@ -2278,24 +2536,32 @@ static int make_indexed_dir(handle_t *handle, struct ext4_filename *fname,
 		ext4_initialize_dirent_tail(bh2, blocksize);
 
 	/* Initialize the root; the dot dirents already exist */
-	de = (struct ext4_dir_entry_2 *) (&root->dotdot);
-	de->rec_len = ext4_rec_len_to_disk(
-			blocksize - ext4_dir_rec_len(2, NULL), blocksize);
-	memset (&root->info, 0, sizeof(root->info));
-	root->info.info_length = sizeof(root->info);
+	dotdot_de->rec_len =
+		ext4_rec_len_to_disk(blocksize - le16_to_cpu(dot_de->rec_len),
+				     blocksize);
+
+	/* initialize hashing info */
+	dx_info = dx_get_dx_info(dir, dot_de);
+	if (IS_ERR(dx_info)) {
+		brelse(bh2);
+		brelse(bh);
+		return PTR_ERR(dx_info);
+	}
+	memset(dx_info, 0, sizeof(*dx_info));
+	dx_info->info_length = sizeof(*dx_info);
 	if (ext4_hash_in_dirent(dir))
-		root->info.hash_version = DX_HASH_SIPHASH;
+		dx_info->hash_version = DX_HASH_SIPHASH;
 	else
-		root->info.hash_version =
+		dx_info->hash_version =
 				EXT4_SB(dir->i_sb)->s_def_hash_version;
 
-	entries = root->entries;
+	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(root->info)));
+	dx_set_limit(entries, dx_root_limit(dir, dot_de));
 
 	/* Initialize as for dx_probe */
-	fname->hinfo.hash_version = root->info.hash_version;
+	fname->hinfo.hash_version = dx_info->hash_version;
 	if (fname->hinfo.hash_version <= DX_HASH_TEA)
 		fname->hinfo.hash_version += EXT4_SB(dir->i_sb)->s_hash_unsigned;
 	fname->hinfo.seed = EXT4_SB(dir->i_sb)->s_hash_seed;
@@ -2338,7 +2604,7 @@ static int make_indexed_dir(handle_t *handle, struct ext4_filename *fname,
 	 */
 	if (retval)
 		ext4_mark_inode_dirty(handle, dir);
-	dx_release(frames);
+	dx_release(dir, frames);
 	brelse(bh2);
 	return retval;
 }
@@ -2355,7 +2621,8 @@ static int make_indexed_dir(handle_t *handle, struct ext4_filename *fname,
  */
 static int __ext4_add_entry(handle_t *handle, struct inode *dir,
 			  const struct qstr *d_name,
-			  struct inode *inode)
+			  struct inode *inode,
+			  struct ext4_dentry_param *edp)
 {
 	struct buffer_head *bh = NULL;
 	struct ext4_dir_entry_2 *de;
@@ -2380,6 +2647,13 @@ static int __ext4_add_entry(handle_t *handle, struct inode *dir,
 	if (retval)
 		return retval;
 
+	/* Resolve the LUFID payload once here and store it in fname so every
+	 * add_dirent_to_buf() call in this add operation uses the same value
+	 * without touching the shared EXT4_I(inode)->i_dirdata field. */
+	fname.dfid = ext4_dentry_get_fid(sb, edp);
+	if (fname.dfid)
+		ext4_fc_mark_ineligible(sb, EXT4_FC_REASON_DIRDATA, handle);
+
 	if (ext4_has_inline_data(dir)) {
 		retval = ext4_try_add_inline_entry(handle, &fname, dir, inode);
 		if (retval < 0)
@@ -2464,7 +2738,8 @@ static int ext4_add_entry(handle_t *handle, struct dentry *dentry,
 
 	if (fscrypt_is_nokey_name(dentry))
 		return -ENOKEY;
-	return __ext4_add_entry(handle, dir, &dentry->d_name, inode);
+	return __ext4_add_entry(handle, dir, &dentry->d_name, inode,
+				dentry->d_fsdata);
 }
 
 /*
@@ -2605,7 +2880,7 @@ static int ext4_dx_add_entry(handle_t *handle, struct ext4_filename *fname,
 			if (restart || err)
 				goto journal_error;
 		} else {
-			struct dx_root *dxroot;
+			struct dx_root_info *info;
 			memcpy((char *) entries2, (char *) entries,
 			       icount * sizeof(struct dx_entry));
 			dx_set_limit(entries2, dx_node_limit(dir));
@@ -2613,11 +2888,17 @@ static int ext4_dx_add_entry(handle_t *handle, struct ext4_filename *fname,
 			/* Set up root */
 			dx_set_count(entries, 1);
 			dx_set_block(entries + 0, newblock);
-			dxroot = (struct dx_root *)frames[0].bh->b_data;
-			dxroot->info.indirect_levels += 1;
+			info = dx_get_dx_info(dir, (struct ext4_dir_entry_2 *)
+					      frames[0].bh->b_data);
+			if (IS_ERR(info)) {
+				err = PTR_ERR(info);
+				brelse(bh2);
+				goto journal_error;
+			}
+			info->indirect_levels += 1;
 			dxtrace(printk(KERN_DEBUG
 				       "Creating %d level index...\n",
-				       dxroot->info.indirect_levels));
+				       info->indirect_levels));
 			err = ext4_handle_dirty_dx_node(handle, dir, frame->bh);
 			if (err) {
 				brelse(bh2);
@@ -2641,7 +2922,7 @@ static int ext4_dx_add_entry(handle_t *handle, struct ext4_filename *fname,
 	ext4_std_error(dir->i_sb, err); /* this is a no-op if err == 0 */
 cleanup:
 	brelse(bh);
-	dx_release(frames);
+	dx_release(dir, frames);
 	/* @restart is true means htree-path has been changed, we need to
 	 * repeat dx_probe() to find out valid htree-path
 	 */
@@ -2933,7 +3214,7 @@ int ext4_init_dirblock(handle_t *handle, struct inode *inode,
 
 	de->inode = cpu_to_le32(inode->i_ino);
 	de->name_len = 1;
-	de->rec_len = ext4_rec_len_to_disk(ext4_dir_rec_len(de->name_len, NULL),
+	de->rec_len = ext4_rec_len_to_disk(ext4_dirent_rec_len(de->name_len, NULL),
 					   blocksize);
 	memcpy(de->name, ".", 2);
 	ext4_set_de_type(inode->i_sb, de, S_IFDIR);
@@ -2945,7 +3226,7 @@ int ext4_init_dirblock(handle_t *handle, struct inode *inode,
 	ext4_set_de_type(inode->i_sb, de, S_IFDIR);
 	if (inline_buf) {
 		de->rec_len = ext4_rec_len_to_disk(
-					ext4_dir_rec_len(de->name_len, NULL),
+					ext4_dirent_rec_len(de->name_len, NULL),
 					blocksize);
 		de = ext4_next_entry(de, blocksize);
 		header_size = (char *)de - bh->b_data;
@@ -2954,7 +3235,7 @@ int ext4_init_dirblock(handle_t *handle, struct inode *inode,
 			blocksize - csum_size);
 	} else {
 		de->rec_len = ext4_rec_len_to_disk(blocksize -
-					(csum_size + ext4_dir_rec_len(1, NULL)),
+					(csum_size + ext4_dirent_rec_len(1, NULL)),
 					blocksize);
 	}
 
@@ -2966,8 +3247,9 @@ int ext4_init_dirblock(handle_t *handle, struct inode *inode,
 	return ext4_handle_dirty_dirblock(handle, inode, bh);
 }
 
-int ext4_init_new_dir(handle_t *handle, struct inode *dir,
-			     struct inode *inode)
+int ext4_init_new_dir_data(handle_t *handle, struct inode *dir,
+			   struct inode *inode,
+			   const void *data1, const void *data2)
 {
 	struct buffer_head *dir_block = NULL;
 	ext4_lblk_t block = 0;
@@ -3077,8 +3359,8 @@ bool ext4_empty_dir(struct inode *inode)
 	}
 
 	sb = inode->i_sb;
-	if (inode->i_size < ext4_dir_rec_len(1, NULL) +
-					ext4_dir_rec_len(2, NULL)) {
+	if (inode->i_size < ext4_dirent_rec_len(1, NULL) +
+					ext4_dirent_rec_len(2, NULL)) {
 		EXT4_ERROR_INODE(inode, "invalid size");
 		return false;
 	}
@@ -3467,10 +3749,16 @@ int __ext4_link(struct inode *dir, struct inode *inode,
 	if (IS_DIRSYNC(dir))
 		ext4_handle_sync(handle);
 
+	/* Fast-commit does not record dirdata in its journal format; force a
+	 * full journal commit so the LUFID survives crash+replay. */
+	if (ext4_dentry_get_fid(dir->i_sb, dentry ? dentry->d_fsdata : NULL))
+		ext4_fc_mark_ineligible(dir->i_sb, EXT4_FC_REASON_DIRDATA, handle);
+
 	inode_set_ctime_current(inode);
 	ext4_inc_count(inode);
 
-	err = __ext4_add_entry(handle, dir, d_name, inode);
+	err = __ext4_add_entry(handle, dir, d_name, inode,
+			       dentry ? dentry->d_fsdata : NULL);
 	if (!err) {
 		err = ext4_mark_inode_dirty(handle, inode);
 		/* this can happen only for tmpfile being
@@ -3639,7 +3927,8 @@ static int ext4_rename_dir_finish(handle_t *handle, struct ext4_renament *ent,
 }
 
 static int ext4_setent(handle_t *handle, struct ext4_renament *ent,
-		       unsigned ino, unsigned file_type)
+		       unsigned ino, unsigned file_type,
+		       const struct ext4_dirent_fid *src_fid)
 {
 	int retval, retval2;
 
@@ -3649,8 +3938,57 @@ static int ext4_setent(handle_t *handle, struct ext4_renament *ent,
 	if (retval)
 		return retval;
 	ent->de->inode = cpu_to_le32(ino);
-	if (ext4_has_feature_filetype(ent->dir->i_sb))
-		ent->de->file_type = file_type;
+	if (ext4_has_feature_filetype(ent->dir->i_sb)) {
+		/* Copy the source LUFID payload into the destination slot when
+		 * both carry a LUFID of the same on-disk size.  LUFID is
+		 * inode-specific and must travel with the inode through rename.
+		 * CFHASH bytes are left in-place: CFHASH is a function of the
+		 * destination filename, and its bytes sit at the correct offset
+		 * because LUFID always precedes CFHASH in the extension layout.
+		 * When src_fid is NULL, the LUFID flag is synced to the LUFID
+		 * bit already present in file_type: callers that clear LUFID
+		 * (rename of a non-LUFID inode, whiteout) pass file_type with
+		 * LUFID=0; ext4_resetent passes the original file_type so the
+		 * flag is restored for error recovery without overwriting the
+		 * LUFID bytes that are still intact on disk. */
+		if (ext4_has_feature_dirdata(ent->dir->i_sb)) {
+			if (src_fid && (ent->de->file_type & EXT4_DIRENT_LUFID)) {
+				unsigned int rec_len =
+					ext4_rec_len_from_disk(ent->de->rec_len,
+						ent->dir->i_sb->s_blocksize);
+				unsigned int ddh_off =
+					EXT4_BASE_DIR_LEN + ent->de->name_len + 1;
+				struct ext4_dirent_data_header *ddh =
+					(struct ext4_dirent_data_header *)
+					((char *)ent->de + ddh_off);
+				unsigned int copy_len = src_fid->df_header.ddh_length;
+
+				if (ddh_off + sizeof(*ddh) > rec_len ||
+				    ddh->ddh_length != copy_len ||
+				    ddh_off + copy_len > rec_len) {
+					/* Cannot copy: clear the flag so the
+					 * slot does not advertise a stale LUFID
+					 * from the old inode. */
+					ent->de->file_type &= ~EXT4_DIRENT_LUFID;
+				} else {
+					memcpy(ddh, src_fid, copy_len);
+				}
+			} else if (!src_fid) {
+				/* Sync the LUFID flag with what file_type requests.
+				 * For normal rename (source has no LUFID) and for
+				 * the whiteout setent, file_type carries LUFID=0,
+				 * so we clear the stale flag.  For ext4_resetent
+				 * (error recovery), file_type is the original
+				 * file_type with LUFID=1, so we restore the flag —
+				 * the LUFID bytes are still on disk untouched. */
+				ent->de->file_type =
+					(ent->de->file_type & ~EXT4_DIRENT_LUFID) |
+					(file_type & EXT4_DIRENT_LUFID);
+			}
+		}
+		ent->de->file_type = (file_type & EXT4_FT_MASK) |
+				     (ent->de->file_type & ~EXT4_FT_MASK);
+	}
 	inode_inc_iversion(ent->dir);
 	inode_set_mtime_to_ts(ent->dir, inode_set_ctime_current(ent->dir));
 	retval = ext4_mark_inode_dirty(handle, ent->dir);
@@ -3687,7 +4025,7 @@ static void ext4_resetent(handle_t *handle, struct ext4_renament *ent,
 		return;
 	}
 
-	ext4_setent(handle, &old, ino, file_type);
+	ext4_setent(handle, &old, ino, file_type, NULL);
 	brelse(old.bh);
 }
 
@@ -3786,6 +4124,32 @@ static struct inode *ext4_whiteout_for_rename(struct mnt_idmap *idmap,
 	return wh;
 }
 
+/*
+ * ext4_lufid_snapshot - copy the LUFID record from a directory entry
+ *
+ * Allocates and returns a kmemdup'd copy of the full on-disk LUFID record
+ * (header + FID payload of arbitrary length).  Returns NULL if no LUFID is
+ * present, the record is malformed, or allocation fails.  Caller must kfree().
+ */
+static struct ext4_dirent_fid *
+ext4_lufid_snapshot(struct ext4_dir_entry_2 *de, unsigned int blocksize)
+{
+	unsigned int ddh_off = EXT4_BASE_DIR_LEN + de->name_len + 1;
+	unsigned int rec_len = ext4_rec_len_from_disk(de->rec_len, blocksize);
+	struct ext4_dirent_fid *disk_fid;
+	unsigned int dlen;
+
+	if (!(de->file_type & EXT4_DIRENT_LUFID))
+		return NULL;
+	if (ddh_off + sizeof(disk_fid->df_header) > rec_len)
+		return NULL;
+	disk_fid = (struct ext4_dirent_fid *)((char *)de + ddh_off);
+	dlen = disk_fid->df_header.ddh_length;
+	if (dlen < sizeof(disk_fid->df_header) || ddh_off + dlen > rec_len)
+		return NULL;
+	return kmemdup(disk_fid, dlen, GFP_NOFS);
+}
+
 /*
  * Anybody can rename anything with this: the permission checks are left to the
  * higher-level routines.
@@ -3814,6 +4178,7 @@ static int ext4_rename(struct mnt_idmap *idmap, struct inode *old_dir,
 	struct inode *whiteout = NULL;
 	int credits;
 	u8 old_file_type;
+	struct ext4_dirent_fid *old_fid = NULL;
 
 	if (new.inode && new.inode->i_nlink == 0) {
 		EXT4_ERROR_INODE(new.inode,
@@ -3925,7 +4290,7 @@ static int ext4_rename(struct mnt_idmap *idmap, struct inode *old_dir,
 		 * to be still pointing to the valid old entry.
 		 */
 		retval = ext4_setent(handle, &old, whiteout->i_ino,
-				     EXT4_FT_CHRDEV);
+				     EXT4_FT_CHRDEV, NULL);
 		if (retval)
 			goto end_rename;
 		retval = ext4_mark_inode_dirty(handle, whiteout);
@@ -3938,8 +4303,18 @@ static int ext4_rename(struct mnt_idmap *idmap, struct inode *old_dir,
 		if (retval)
 			goto end_rename;
 	} else {
+		if (ext4_has_feature_dirdata(old.dir->i_sb)) {
+			old_fid = ext4_lufid_snapshot(old.de,
+						      old.dir->i_sb->s_blocksize);
+			if (old_fid)
+				ext4_fc_mark_ineligible(old.dir->i_sb,
+							EXT4_FC_REASON_DIRDATA,
+							handle);
+			else
+				old_file_type &= ~EXT4_DIRENT_LUFID;
+		}
 		retval = ext4_setent(handle, &new,
-				     old.inode->i_ino, old_file_type);
+				     old.inode->i_ino, old_file_type, old_fid);
 		if (retval)
 			goto end_rename;
 	}
@@ -4026,6 +4401,7 @@ static int ext4_rename(struct mnt_idmap *idmap, struct inode *old_dir,
 	retval = 0;
 
 end_rename:
+	kfree(old_fid);
 	if (whiteout) {
 		if (retval) {
 			ext4_resetent(handle, &old,
@@ -4062,8 +4438,9 @@ static int ext4_cross_rename(struct inode *old_dir, struct dentry *old_dentry,
 		.dentry = new_dentry,
 		.inode = d_inode(new_dentry),
 	};
-	u8 new_file_type;
+	u8 new_file_type, old_de_file_type;
 	int retval;
+	struct ext4_dirent_fid *old_fid = NULL, *new_fid = NULL;
 
 	if ((ext4_test_inode_flag(new_dir, EXT4_INODE_PROJINHERIT) &&
 	     !projid_eq(EXT4_I(new_dir)->i_projid,
@@ -4142,12 +4519,27 @@ static int ext4_cross_rename(struct inode *old_dir, struct dentry *old_dentry,
 			goto end_rename;
 	}
 
+	if (ext4_has_feature_dirdata(old.dir->i_sb)) {
+		old_fid = ext4_lufid_snapshot(old.de, old.dir->i_sb->s_blocksize);
+		new_fid = ext4_lufid_snapshot(new.de, new.dir->i_sb->s_blocksize);
+		if (old_fid || new_fid)
+			ext4_fc_mark_ineligible(old.dir->i_sb,
+						EXT4_FC_REASON_DIRDATA, handle);
+	}
+
+	old_de_file_type = old.de->file_type;
+	if (!old_fid)
+		old_de_file_type &= ~EXT4_DIRENT_LUFID;
 	new_file_type = new.de->file_type;
-	retval = ext4_setent(handle, &new, old.inode->i_ino, old.de->file_type);
+	if (!new_fid)
+		new_file_type &= ~EXT4_DIRENT_LUFID;
+	retval = ext4_setent(handle, &new, old.inode->i_ino, old_de_file_type,
+			     old_fid);
 	if (retval)
 		goto end_rename;
 
-	retval = ext4_setent(handle, &old, new.inode->i_ino, new_file_type);
+	retval = ext4_setent(handle, &old, new.inode->i_ino, new_file_type,
+			     new_fid);
 	if (retval)
 		goto end_rename;
 
@@ -4180,6 +4572,8 @@ static int ext4_cross_rename(struct inode *old_dir, struct dentry *old_dentry,
 	retval = 0;
 
 end_rename:
+	kfree(old_fid);
+	kfree(new_fid);
 	brelse(old.dir_bh);
 	brelse(new.dir_bh);
 	brelse(old.bh);
@@ -4216,6 +4610,154 @@ static int ext4_rename2(struct mnt_idmap *idmap,
 	return ext4_rename(idmap, old_dir, old_dentry, new_dir, new_dentry, flags);
 }
 
+/*
+ * ext4_dirdata_set_lufid() - Set LUFID data on an existing directory entry
+ * @dir:        parent directory inode
+ * @filename:   name of the file in the directory
+ * @namelen:    length of filename
+ * @edp:        pointer to initialized dentry param with LUFID data
+ *
+ * This function finds an existing directory entry, deletes it, and re-creates it
+ * with LUFID data attached. Used by the EXT4_IOC_SET_LUFID ioctl.
+ *
+ * Returns 0 on success, negative error code on failure.
+ */
+int ext4_dirdata_set_lufid(struct inode *dir, const char *filename,
+			    int namelen, struct ext4_dentry_param *edp)
+{
+	struct super_block *sb = dir->i_sb;
+	 /* zero-init: safe to free on any path */
+	struct ext4_filename fname = {};
+	struct ext4_dir_entry_2 *de = NULL;
+	struct buffer_head *bh = NULL;
+	struct inode *inode = NULL;
+	handle_t *handle = NULL;
+	struct qstr d_name;
+	int err = 0;
+
+	if (!ext4_has_feature_dirdata(sb))
+		return -EOPNOTSUPP;
+
+	if (namelen > EXT4_NAME_LEN)
+		return -ENAMETOOLONG;
+	if (namelen != strnlen(filename, namelen + 1))
+		return -EINVAL;
+
+	d_name.name = filename;
+	d_name.len = namelen;
+
+	err = ext4_fname_setup_filename(dir, &d_name, 0, &fname);
+	if (err)
+		goto out_free;
+
+	/* Lock dir with the VFS parent-mutation subclass before starting the
+	 * journal.  Holding i_rwsem while calling ext4_journal_start() is safe
+	 * (ext4_setattr, ext4_fallocate etc. do the same).  The lookup must be
+	 * inside the lock to prevent TOCTOU: the bh/de pointers must be stable
+	 * from find_entry through delete_entry. */
+	inode_lock_nested(dir, I_MUTEX_PARENT);
+
+	handle = ext4_journal_start(dir, EXT4_HT_DIR,
+				    3 * EXT4_DATA_TRANS_BLOCKS(sb) +
+				    2 * EXT4_INDEX_EXTRA_TRANS_BLOCKS);
+	if (IS_ERR(handle)) {
+		err = PTR_ERR(handle);
+		handle = NULL;
+		goto out_unlock_dir;
+	}
+
+	bh = ext4_find_entry(dir, &d_name, &de, NULL);
+	if (IS_ERR(bh)) {
+		err = PTR_ERR(bh);
+		bh = NULL;
+		goto out_journal;
+	}
+	if (!bh) {
+		err = -ENOENT;
+		goto out_journal;
+	}
+
+	inode = ext4_iget(sb, le32_to_cpu(de->inode), EXT4_IGET_NORMAL);
+	if (IS_ERR(inode)) {
+		err = PTR_ERR(inode);
+		inode = NULL;
+		goto out_brelse;
+	}
+
+	/* Lock the target inode after the directory (dir-then-child ordering)
+	 * to serialize concurrent EXT4_IOC_SET_LUFID calls on different
+	 * hardlinks of the same inode.  Use I_MUTEX_CHILD for directory inodes
+	 * (consistent with VFS rename/rmdir) and I_MUTEX_NONDIR2 for all
+	 * others; mixing subclasses on the same inode triggers lockdep cycles
+	 * with concurrent rename. */
+	if (inode != dir) {
+		if (S_ISDIR(inode->i_mode))
+			inode_lock_nested(inode, I_MUTEX_CHILD);
+		else
+			inode_lock_nested(inode, I_MUTEX_NONDIR2);
+	}
+
+	err = ext4_delete_entry(handle, dir, de, bh);
+	if (err)
+		goto out_unlock;
+
+	brelse(bh);
+	bh = NULL;
+
+	/* Re-add with LUFID via dentry->d_fsdata.  ext4_add_entry() resolves
+	 * dfid from d_fsdata into fname.dfid and passes it through to
+	 * add_dirent_to_buf() without touching the shared i_dirdata field,
+	 * eliminating the race with concurrent link() calls. */
+	{
+		struct dentry parent_dentry = { .d_inode = dir };
+		struct dentry new_dentry = {
+			.d_name = d_name,
+			.d_parent = &parent_dentry,
+			.d_inode = inode,
+			.d_fsdata = edp,
+		};
+		err = ext4_add_entry(handle, &new_dentry, inode);
+	}
+
+	if (err) {
+		/* Delete succeeded but re-add failed; try to restore so the
+		 * inode is not left without a directory entry. */
+		struct dentry parent_dentry = { .d_inode = dir };
+		struct dentry orig_dentry = {
+			.d_name = d_name,
+			.d_parent = &parent_dentry,
+			.d_inode = inode,
+		};
+		int rollback_err = ext4_add_entry(handle, &orig_dentry, inode);
+
+		if (rollback_err)
+			EXT4_ERROR_INODE(dir,
+				"Failed to set LUFID on '%.*s' (err=%d) and failed to restore the original directory entry (err=%d); inode %llu may be orphaned",
+				namelen, filename, err, rollback_err,
+				inode->i_ino);
+		goto out_unlock;
+	}
+
+	inode_set_ctime_current(dir);
+	inode_inc_iversion(dir);
+	ext4_mark_inode_dirty(handle, dir);
+
+out_unlock:
+	if (inode != dir)
+		inode_unlock(inode);
+out_brelse:
+	brelse(bh);
+out_journal:
+	ext4_journal_stop(handle);
+out_unlock_dir:
+	inode_unlock(dir);
+	iput(inode);
+out_free:
+	ext4_fname_free_filename(&fname);
+
+	return err;
+}
+
 /*
  * directories can handle most operations...
  */
diff --git a/fs/ext4/sysfs.c b/fs/ext4/sysfs.c
index 923b375e017faa..80074fb15ee9b7 100644
--- a/fs/ext4/sysfs.c
+++ b/fs/ext4/sysfs.c
@@ -362,6 +362,7 @@ EXT4_ATTR_FEATURE(verity);
 #endif
 EXT4_ATTR_FEATURE(metadata_csum_seed);
 EXT4_ATTR_FEATURE(fast_commit);
+EXT4_ATTR_FEATURE(dirdata);
 #if IS_ENABLED(CONFIG_UNICODE) && defined(CONFIG_FS_ENCRYPTION)
 EXT4_ATTR_FEATURE(encrypted_casefold);
 #endif
@@ -385,6 +386,7 @@ static struct attribute *ext4_feat_attrs[] = {
 #endif
 	ATTR_LIST(metadata_csum_seed),
 	ATTR_LIST(fast_commit),
+	ATTR_LIST(dirdata),
 #if IS_ENABLED(CONFIG_UNICODE) && defined(CONFIG_FS_ENCRYPTION)
 	ATTR_LIST(encrypted_casefold),
 #endif
diff --git a/include/uapi/linux/ext4.h b/include/uapi/linux/ext4.h
index 9c683991c32f96..9134fe64947a93 100644
--- a/include/uapi/linux/ext4.h
+++ b/include/uapi/linux/ext4.h
@@ -35,6 +35,7 @@
 #define EXT4_IOC_SETFSUUID		_IOW('f', 44, struct fsuuid)
 #define EXT4_IOC_GET_TUNE_SB_PARAM	_IOR('f', 45, struct ext4_tune_sb_params)
 #define EXT4_IOC_SET_TUNE_SB_PARAM	_IOW('f', 46, struct ext4_tune_sb_params)
+#define EXT4_IOC_SET_LUFID		_IOW('f', 47, struct ext4_set_lufid)
 
 #define EXT4_IOC_SHUTDOWN _IOR('X', 125, __u32)
 
@@ -92,6 +93,19 @@ struct move_extent {
 	__u64 moved_len;	/* moved block length */
 };
 
+/*
+ * Structure for EXT4_IOC_SET_LUFID
+ * Sets LUFID on a directory entry
+ * Called on parent directory with filename and LUFID data as arguments
+ */
+struct ext4_set_lufid {
+	__u8 esl_name_len;	/* length of filename, NOT including NUL terminator
+				 * (valid range: 1-255, matching EXT4_NAME_LEN) */
+	char  esl_name[255 + 1]; /* filename (NUL-terminated) */
+	__u8 esl_data_len;	/* length of LUFID data */
+	char  esl_data[255]; /* LUFID data (raw bytes) */
+};
+
 /*
  * Flags used by EXT4_IOC_SHUTDOWN
  */

3/2 2026/07/14 19:42 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 34319
							output: 29
							thoughts: 345
						

Thoughts:
**My Assessment of the `dirdata` Patch for ext4**

Okay, so I'm looking at this patch for ext4 that introduces `dirdata`, using `EXT4_FEATURE_INCOMPAT_DIRDATA` and the `EXT4_IOC_SET_LUFID` ioctl. Right off the bat, I see this is a *functional* change – we're talking about new ioctl handling, directory entry parsing changes, new structures, the works. Definitely fuzz-worthy. Let's pinpoint those key functions to target.

Looking over the code, it looks like these are the hotspots: `ext4_ioctl_set_lufid`, `ext4_dirdata_set_lufid`, `ext4_dirdata_get`, `ext4_dirdata_set`, `ext4_lufid_snapshot`, `ext4_insert_dentry_data`, and `ext4_init_new_dir_data`. My instincts tell me to focus on `ext4_ioctl_set_lufid` and `ext4_dirdata_set_lufid` as entry points. Let's see if those are actually reachable within the filesystem. I can use the usual tools, `codesearch-dir-index` or `grepper` should do the trick.

Alright, a quick check reveals they're sitting in `fs/ext4/ioctl.c` and `fs/ext4/namei.c`. Good, that narrows down the scope. Now, the big question is dependencies. What configurations need to be enabled for this feature? I see the patch mentions `EXT4_FEATURE_INCOMPAT_DIRDATA`, but is there a corresponding `CONFIG_EXT4_DIRDATA` or something similar? I'll run a quick grep for `CONFIG_EXT4` and `DIRDATA` to confirm. This is going to be my baseline for fuzzing and if it's not enabled, I have to ensure those configurations are in the fuzzing profile.



4/2 2026/07/14 19:42 tool grepper
0m Args:
{
  "Expression": "CONFIG_EXT4",
  "PathPrefix": "fs/ext4/"
}

Results:
{
  "Output": "fs/ext4/.kunitconfig=1=CONFIG_KUNIT=y\nfs/ext4/.kunitconfig:2:CONFIG_EXT4_FS=y\nfs/ext4/.kunitconfig:3:CONFIG_EXT4_KUNIT_TESTS=y\n--\nfs/ext4/Makefile-5-\nfs/ext4/Makefile:6:obj-$(CONFIG_EXT4_FS) += ext4.o\nfs/ext4/Makefile-7-\nfs/ext4/Makefile=8=ext4-y\t:= balloc.o bitmap.o block_validity.o dir.o ext4_jbd2.o extents.o \\\n--\nfs/ext4/Makefile-14-\nfs/ext4/Makefile:15:ext4-$(CONFIG_EXT4_FS_POSIX_ACL)\t+= acl.o\nfs/ext4/Makefile:16:ext4-$(CONFIG_EXT4_FS_SECURITY)\t\t+= xattr_security.o\nfs/ext4/Makefile-17-ext4-test-objs\t\t\t\t+= inode-test.o mballoc-test.o \\\nfs/ext4/Makefile-18-\t\t\t\t\t   extents-test.o hash-test.o\nfs/ext4/Makefile:19:obj-$(CONFIG_EXT4_KUNIT_TESTS)\t\t+= ext4-test.o\nfs/ext4/Makefile-20-ext4-$(CONFIG_FS_VERITY)\t\t+= verity.o\n--\nfs/ext4/acl.h=39=static inline int ext4_acl_count(size_t size)\n--\nfs/ext4/acl.h-54-\nfs/ext4/acl.h:55:#ifdef CONFIG_EXT4_FS_POSIX_ACL\nfs/ext4/acl.h-56-\n--\nfs/ext4/acl.h=61=extern int ext4_init_acl(handle_t *, struct inode *, struct inode *);\nfs/ext4/acl.h-62-\nfs/ext4/acl.h:63:#else  /* CONFIG_EXT4_FS_POSIX_ACL */\nfs/ext4/acl.h-64-#include \u003clinux/sched.h\u003e\n--\nfs/ext4/acl.h=69=ext4_init_acl(handle_t *handle, struct inode *inode, struct inode *dir)\n--\nfs/ext4/acl.h-72-}\nfs/ext4/acl.h:73:#endif  /* CONFIG_EXT4_FS_POSIX_ACL */\nfs/ext4/acl.h-74-\n--\nfs/ext4/ext4.h-91- */\nfs/ext4/ext4.h:92:#ifdef CONFIG_EXT4_DEBUG\nfs/ext4/ext4.h-93-#define ext_debug(ino, fmt, ...)\t\t\t\t\t\\\n--\nfs/ext4/ext4.h=1587=struct ext4_sb_info {\n--\nfs/ext4/ext4.h-1796-\tu64 s_dax_part_off;\nfs/ext4/ext4.h:1797:#ifdef CONFIG_EXT4_DEBUG\nfs/ext4/ext4.h-1798-\tunsigned long s_simulate_fail;\n--\nfs/ext4/ext4.h-1859-\ttid_t s_fc_ineligible_tid;\nfs/ext4/ext4.h:1860:#ifdef CONFIG_EXT4_DEBUG\nfs/ext4/ext4.h-1861-\tint s_fc_debug_max_replay;\n--\nfs/ext4/ext4.h=1984=static inline bool ext4_simulate_fail(struct super_block *sb,\n--\nfs/ext4/ext4.h-1986-{\nfs/ext4/ext4.h:1987:#ifdef CONFIG_EXT4_DEBUG\nfs/ext4/ext4.h-1988-\tstruct ext4_sb_info *sbi = EXT4_SB(sb);\n--\nfs/ext4/ext4.h=4227=extern int ext4_block_write_begin(handle_t *handle, struct folio *folio,\n--\nfs/ext4/ext4.h-4230-\nfs/ext4/ext4.h:4231:#if IS_ENABLED(CONFIG_EXT4_KUNIT_TESTS)\nfs/ext4/ext4.h-4232-#define EXPORT_SYMBOL_FOR_EXT4_TEST(sym) \\\n--\nfs/ext4/ext4_extents.h=270=extern int ext4_ext_zeroout(struct inode *inode, struct ext4_extent *ex);\nfs/ext4/ext4_extents.h:271:#if IS_ENABLED(CONFIG_EXT4_KUNIT_TESTS)\nfs/ext4/ext4_extents.h-272-extern int ext4_ext_space_root_idx_test(struct inode *inode, int check);\n--\nfs/ext4/extents.c=6227=int ext4_ext_clear_bb(struct inode *inode)\n--\nfs/ext4/extents.c-6278-\nfs/ext4/extents.c:6279:#if IS_ENABLED(CONFIG_EXT4_KUNIT_TESTS)\nfs/ext4/extents.c-6280-int ext4_ext_space_root_idx_test(struct inode *inode, int check)\n--\nfs/ext4/fast_commit.c=2581=static int ext4_fc_replay(journal_t *journal, struct buffer_head *bh,\n--\nfs/ext4/fast_commit.c-2606-\nfs/ext4/fast_commit.c:2607:#ifdef CONFIG_EXT4_DEBUG\nfs/ext4/fast_commit.c-2608-\tif (sbi-\u003es_fc_debug_max_replay \u0026\u0026 off \u003e= sbi-\u003es_fc_debug_max_replay) {\n--\nfs/ext4/hash.c=314=int ext4fs_dirhash(const struct inode *dir, const char *name, int len,\n--\nfs/ext4/hash.c-344-\nfs/ext4/hash.c:345:#if IS_ENABLED(CONFIG_EXT4_KUNIT_TESTS)\nfs/ext4/hash.c-346-EXPORT_SYMBOL_FOR_EXT4_TEST(ext4fs_dirhash);\n--\nfs/ext4/ialloc.c=876=static int ext4_xattr_credits_for_new_inode(struct inode *dir, mode_t mode,\n--\nfs/ext4/ialloc.c-880-\tint nblocks = 0;\nfs/ext4/ialloc.c:881:#ifdef CONFIG_EXT4_FS_POSIX_ACL\nfs/ext4/ialloc.c-882-\tstruct posix_acl *p = get_inode_acl(dir, ACL_TYPE_DEFAULT);\n--\nfs/ext4/inode.c=412=int ext4_issue_zeroout(struct inode *inode, ext4_lblk_t lblk, ext4_fsblk_t pblk,\n--\nfs/ext4/inode.c-436- */\nfs/ext4/inode.c:437:#ifdef CONFIG_EXT4_DEBUG\nfs/ext4/inode.c-438-void ext4_check_map_extents_env(struct inode *inode)\n--\nfs/ext4/mballoc.c=5728=static void ext4_mb_pa_put_free(struct ext4_allocation_context *ac)\n--\nfs/ext4/mballoc.c-5743-\nfs/ext4/mballoc.c:5744:#ifdef CONFIG_EXT4_DEBUG\nfs/ext4/mballoc.c-5745-static inline void ext4_mb_show_pa(struct super_block *sb)\n--\nfs/ext4/mballoc.c=7129=ext4_mballoc_query_range(\n--\nfs/ext4/mballoc.c-7184-\nfs/ext4/mballoc.c:7185:#if IS_ENABLED(CONFIG_EXT4_KUNIT_TESTS)\nfs/ext4/mballoc.c-7186-void mb_clear_bits_test(void *bm, int cur, int len)\n--\nfs/ext4/mballoc.h-28- */\nfs/ext4/mballoc.h:29:#ifdef CONFIG_EXT4_DEBUG\nfs/ext4/mballoc.h-30-#define mb_debug(sb, fmt, ...)\t\t\t\t\t\t\\\n--\nfs/ext4/mballoc.h=273=extern int ext4_mb_mark_context(handle_t *handle,\n--\nfs/ext4/mballoc.h-277-\t\text4_grpblk_t *ret_changed);\nfs/ext4/mballoc.h:278:#if IS_ENABLED(CONFIG_EXT4_KUNIT_TESTS)\nfs/ext4/mballoc.h-279-extern void mb_clear_bits_test(void *bm, int cur, int len);\n--\nfs/ext4/super.c=125=static const struct fs_context_operations ext4_context_ops = {\n--\nfs/ext4/super.c-132-\nfs/ext4/super.c:133:#if !defined(CONFIG_EXT2_FS) \u0026\u0026 !defined(CONFIG_EXT2_FS_MODULE) \u0026\u0026 defined(CONFIG_EXT4_USE_FOR_EXT2)\nfs/ext4/super.c-134-static struct file_system_type ext2_fs_type = {\n--\nfs/ext4/super.c=1692=enum {\n--\nfs/ext4/super.c-1715-\tOpt_errors, Opt_data, Opt_data_err, Opt_jqfmt, Opt_dax_type,\nfs/ext4/super.c:1716:#ifdef CONFIG_EXT4_DEBUG\nfs/ext4/super.c-1717-\tOpt_fc_debug_max_replay, Opt_fc_debug_force\n--\nfs/ext4/super.c=1762=static const struct fs_parameter_spec ext4_param_specs[] = {\n--\nfs/ext4/super.c-1835-\tfsparam_flag\t(\"noinit_itable\",\tOpt_noinit_itable),\nfs/ext4/super.c:1836:#ifdef CONFIG_EXT4_DEBUG\nfs/ext4/super.c-1837-\tfsparam_flag\t(\"fc_debug_force\",\tOpt_fc_debug_force),\n--\nfs/ext4/super.c=1879=static const struct mount_opts {\n--\nfs/ext4/super.c-1922-\t{Opt_user_xattr, EXT4_MOUNT_XATTR_USER, MOPT_SET},\nfs/ext4/super.c:1923:#ifdef CONFIG_EXT4_FS_POSIX_ACL\nfs/ext4/super.c-1924-\t{Opt_acl, EXT4_MOUNT_POSIX_ACL, MOPT_SET},\n--\nfs/ext4/super.c-1945-\t MOPT_SET},\nfs/ext4/super.c:1946:#ifdef CONFIG_EXT4_DEBUG\nfs/ext4/super.c-1947-\t{Opt_fc_debug_force, EXT4_MOUNT2_JOURNAL_FAST_COMMIT,\n--\nfs/ext4/super.c=1997=struct ext4_fs_context {\n--\nfs/ext4/super.c-2000-\tint\t\ts_jquota_fmt;\t/* Format of quota to use */\nfs/ext4/super.c:2001:#ifdef CONFIG_EXT4_DEBUG\nfs/ext4/super.c-2002-\tint s_fc_debug_max_replay;\n--\nfs/ext4/super.c=2171=static int ext4_parse_param(struct fs_context *fc, struct fs_parameter *param)\n--\nfs/ext4/super.c-2306-\t\treturn 0;\nfs/ext4/super.c:2307:#ifdef CONFIG_EXT4_DEBUG\nfs/ext4/super.c-2308-\tcase Opt_fc_debug_max_replay:\n--\nfs/ext4/super.c=2855=static void ext4_apply_options(struct fs_context *fc, struct super_block *sb)\n--\nfs/ext4/super.c-2878-\nfs/ext4/super.c:2879:#ifdef CONFIG_EXT4_DEBUG\nfs/ext4/super.c-2880-\tAPPLY(s_fc_debug_max_replay);\n--\nfs/ext4/super.c=4388=static void ext4_set_def_opts(struct super_block *sb,\n--\nfs/ext4/super.c-4403-\tset_opt(sb, XATTR_USER);\nfs/ext4/super.c:4404:#ifdef CONFIG_EXT4_FS_POSIX_ACL\nfs/ext4/super.c-4405-\tset_opt(sb, POSIX_ACL);\n--\nfs/ext4/super.c=7377=static ssize_t ext4_quota_write(struct super_block *sb, int type,\n--\nfs/ext4/super.c-7437-\nfs/ext4/super.c:7438:#if !defined(CONFIG_EXT2_FS) \u0026\u0026 !defined(CONFIG_EXT2_FS_MODULE) \u0026\u0026 defined(CONFIG_EXT4_USE_FOR_EXT2)\nfs/ext4/super.c-7439-static inline void register_as_ext2(void)\n--\nfs/ext4/sysfs.c=266=EXT4_RW_ATTR_SBI_PI(msg_ratelimit_burst, s_msg_ratelimit_state.burst);\nfs/ext4/sysfs.c:267:#ifdef CONFIG_EXT4_DEBUG\nfs/ext4/sysfs.c-268-EXT4_RW_ATTR_SBI_UL(simulate_fail, s_simulate_fail);\n--\nfs/ext4/sysfs.c=295=static struct attribute *ext4_attrs[] = {\n--\nfs/ext4/sysfs.c-335-\tATTR_LIST(journal_task),\nfs/ext4/sysfs.c:336:#ifdef CONFIG_EXT4_DEBUG\nfs/ext4/sysfs.c-337-\tATTR_LIST(simulate_fail),\n--\nfs/ext4/xattr.c=88=static const struct xattr_handler * const ext4_xattr_handler_map[] = {\nfs/ext4/xattr.c-89-\t[EXT4_XATTR_INDEX_USER]\t\t     = \u0026ext4_xattr_user_handler,\nfs/ext4/xattr.c:90:#ifdef CONFIG_EXT4_FS_POSIX_ACL\nfs/ext4/xattr.c-91-\t[EXT4_XATTR_INDEX_POSIX_ACL_ACCESS]  = \u0026nop_posix_acl_access,\n--\nfs/ext4/xattr.c-94-\t[EXT4_XATTR_INDEX_TRUSTED]\t     = \u0026ext4_xattr_trusted_handler,\nfs/ext4/xattr.c:95:#ifdef CONFIG_EXT4_FS_SECURITY\nfs/ext4/xattr.c-96-\t[EXT4_XATTR_INDEX_SECURITY]\t     = \u0026ext4_xattr_security_handler,\n--\nfs/ext4/xattr.c=101=const struct xattr_handler * const ext4_xattr_handlers[] = {\n--\nfs/ext4/xattr.c-103-\t\u0026ext4_xattr_trusted_handler,\nfs/ext4/xattr.c:104:#ifdef CONFIG_EXT4_FS_SECURITY\nfs/ext4/xattr.c-105-\t\u0026ext4_xattr_security_handler,\n--\nfs/ext4/xattr.h=213=__xattr_check_inode(struct inode *inode, struct ext4_xattr_ibody_header *header,\n--\nfs/ext4/xattr.h-218-\nfs/ext4/xattr.h:219:#ifdef CONFIG_EXT4_FS_SECURITY\nfs/ext4/xattr.h-220-extern int ext4_init_security(handle_t *handle, struct inode *inode,\n"
}

5/2 2026/07/14 19:42 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 39281
							output: 38
							thoughts: 310
						

Thoughts:
**Analyzing the Ext4 Directory Data Feature**

Okay, so there isn't a dedicated Kconfig option like `CONFIG_EXT4_DIRDATA` that I was expecting. It's just a feature flag within ext4. The relevant patch introduces `EXT4_FEATURE_INCOMPAT_DIRDATA`, which is important to note. And I see the configuration to enable it is simply `EXT4_FS`. That makes sense.

Wait, this patch also touches `fs/ext4/ioctl.c`, specifically adding `EXT4_IOC_SET_LUFID`. That's intriguing. I need to figure out how that is utilized. Okay, *yes*, it's an ioctl operation on ext4 directories. Good to know.

Alright, I need to list the functions related to this new functionality for my analysis. Let's make a note of `ext4_ioctl_set_lufid` and `ext4_dirdata_set_lufid` within `FocusSymbols`. Then, I'll need to add `ext4_dirdata_get`, `ext4_dirdata_set`, `ext4_lufid_snapshot`, `ext4_insert_dentry_data`, and `ext4_init_new_dir_data`.

Let's dig a little deeper. Hmm, what else is relevant? `ext4_dirent_get_data_len`, and `ext4_dir_entry_len`. Oh, hang on, `ext4_dirent_get_data_len` is an inline function defined in `ext4.h`. So it might be a bit simpler to follow.

`ext4_dirdata_get` is in `fs/ext4/namei.c`. Makes sense. Let's thoroughly check `fs/ext4/namei.c` and make sure I haven't missed any other new functions.



6/2 2026/07/14 19:42 tool grepper
0m Args:
{
  "Expression": "^int ext4_dirdata_set_lufid",
  "PathPrefix": "fs/ext4/"
}

Results:
{
  "Output": "fs/ext4/namei.c=4586=static int ext4_rename2(struct mnt_idmap *idmap,\n--\nfs/ext4/namei.c-4624- */\nfs/ext4/namei.c:4625:int ext4_dirdata_set_lufid(struct inode *dir, const char *filename,\nfs/ext4/namei.c-4626-\t\t\t    int namelen, struct ext4_dentry_param *edp)\n"
}

7/2 2026/07/14 19:42 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 40176
							output: 38
							thoughts: 16
						

Thoughts:
Okay, let's dive into this. Here's my understanding, presented as my own mental processing:

**Analyzing `ext4_ioctl_set_lufid`**

Right, let's start by looking at `ext4_ioctl_set_lufid`. I need to figure out what this function does. Given the name, I'm guessing it relates to an ioctl call, and it likely deals with setting something associated with a LUFID, which probably stands for something like "Logical Unit File ID" or a similar block device concept.

I'll need to figure out what the kernel's ext4 filesystem implementation does with block devices. Specifically, how does it interact with the device's ioctls? I'll probably want to look for the function's definition, any related structures, and the calling contexts to fully grasp its function. I'm especially interested in how it affects the metadata or the direct-i/o path. Does it allocate new structures? Does it influence any cache behavior? Perhaps there's a security implication too; a thorough review is a must. Knowing the specifics of what `lufid` controls will be key, and any edge cases.



8/2 2026/07/14 19:42 tool grepper
0m Args:
{
  "Expression": "^static long ext4_ioctl_set_lufid",
  "PathPrefix": "fs/ext4/"
}

Results:
{
  "Output": "fs/ext4/ioctl.c=1390=static int ext4_ioctl_set_tune_sb(struct file *filp,\n--\nfs/ext4/ioctl.c-1546- */\nfs/ext4/ioctl.c:1547:static long ext4_ioctl_set_lufid(struct file *filp, unsigned long arg)\nfs/ext4/ioctl.c-1548-{\n"
}

9/2 2026/07/14 19:42 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 40601
							output: 35
							thoughts: 55
						

Thoughts:
Okay, here's my take on those code additions, framed as my own thought process:

**Refining My Focus for Deep Dive**

Right, so I'm honing in on the key areas within this ext4 file system analysis. I need to tighten the scope of my investigation, and I've decided to explicitly add `ext4_ioctl_set_lufid` and `ext4_dirdata_set_lufid` to the `FocusSymbols` list. That's crucial. I'm treating these as the most important functions right now for the investigation.

Furthermore, I recognize the importance of the related data access functions. Therefore I will also include `ext4_dirdata_get` and `ext4_dirdata_set` in the `FocusSymbols`. That will give me more context for the first two functions and help me grasp the flow of data.



10/2 2026/07/14 19:42 tool grepper
0m Args:
{
  "Expression": "^unsigned char ext4_dirdata_get",
  "PathPrefix": "fs/ext4/"
}

Results:
{
  "Output": "fs/ext4/namei.c=1313=static inline int search_dirblock(struct buffer_head *bh,\n--\nfs/ext4/namei.c-1353- */\nfs/ext4/namei.c:1354:unsigned char ext4_dirdata_get(struct ext4_dir_entry_2 *de, struct inode *dir,\nfs/ext4/namei.c-1355-\t\t\t       int buf_size,\n"
}

11/2 2026/07/14 19:42 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 41007
							output: 35
							thoughts: 10
						

Thoughts:
Okay, here's my interpretation of that thought process, tailored for an expert audience, and written in the first person:

**Deep Dive into `ext4_dirdata_set`**

Alright, so I'm thinking about `ext4_dirdata_set`. I know this is a core function, central to how ext4 manages directory entries and the associated metadata. Essentially, it's responsible for setting or updating directory data within the inode. We're talking about the low-level details of how a file's name, its inode number, and various other bits of crucial information are stored within the directory's blocks. I'm visualizing the block layout, the offsets, the lengths... and the all the potential issues that might arise during the intricate dance of writing this data correctly.

The critical pieces are all there: the directory entry, the name length, the inode number, the data type... I'm immediately concerned about the potential for fragmentation if the directory grows and contracts frequently. How does it handle potential collisions when updating an entry? Does it account for possible partial writes, or power failures in the middle of this crucial process? I'm wondering what error handling is in place, specifically for situations where a data write is partially successful, which would inevitably corrupt the filesystem.

I'm also curious about the locking mechanisms employed to ensure thread-safety when multiple processes are attempting to modify the same directory concurrently. Is it using per-inode locks? Global locks? A combination? What's the granularity of the locking and how does that affect overall performance? I'm going to want to check into that soon. And how are we sure that the journal is correctly updated to ensure we're never in an inconsistent state? It's essential that these operations be atomic or that we have the proper journal entries to recover.

Furthermore, how does it interact with the block allocator? How does the block allocator ensure it has the correct amount of space for this data set? Are there optimizations for commonly occurring scenarios? And what about the impact on performance with SSDs versus HDDs? This function must be designed to perform well on a variety of underlying storage types.



12/2 2026/07/14 19:42 tool grepper
0m Args:
{
  "Expression": "^static void ext4_dirdata_set",
  "PathPrefix": "fs/ext4/"
}

Results:
{
  "Output": "fs/ext4/namei.c=1354=unsigned char ext4_dirdata_get(struct ext4_dir_entry_2 *de, struct inode *dir,\n--\nfs/ext4/namei.c-1467- */\nfs/ext4/namei.c:1468:static void ext4_dirdata_set(struct ext4_dir_entry_2 *de, struct inode *dir,\nfs/ext4/namei.c-1469-\t\t\t     struct ext4_dirent_fid *dfid,\n"
}

13/2 2026/07/14 19:42 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 41647
							output: 45
							thoughts: 11
						

Thoughts:
Okay, here's my summary, taking on the persona you've described:

**Ext4 LUFID Snapshot Considerations**

Alright, let's break this down. Thinking about `ext4_lufid_snapshot`, I'm immediately focusing on the implications within an ext4 filesystem. The term "LUFID" suggests *Logical Unit File ID*, meaning we're dealing with how snapshots are implemented, specifically at a file-system level.

My primary concern is the efficiency of such a snapshot mechanism, and any associated overhead. How is this snapshot "created" – is it a full copy, a copy-on-write operation, or perhaps something more clever that leverages the block group structure? Performance is key, especially during snapshot creation and subsequent reads/writes to the "parent" and "snapshot" volumes. I need to understand the impact on inode management and block allocation. Are there significant performance penalties? Are metadata updates atomic and consistent?

Next, I need to consider the granularity. Does this LUFID-based approach offer snapshots of entire volumes, individual files, or a more granular selection of data? The flexibility of selecting the data included would be valuable in order to avoid a "snapshot" which is as large as the original data.

Error handling is critical too. What happens if the snapshot operation fails mid-way? Is data corruption a possibility, and if so, how is it mitigated? Recovery mechanisms are a must, as is ensuring data integrity under all circumstances. I need to see the potential failure modes.

Finally, compatibility and integration are paramount. Does this `ext4_lufid_snapshot` play nicely with existing tools and utilities, such as `fsck`, `e2fsck`, and other volume management technologies? Any integration issues could make this difficult to implement in many real-world use cases.



14/2 2026/07/14 19:42 tool grepper
0m Error:
no matches

Args:
{
  "Expression": "^static struct ext4_dirent_fid \\*.*ext4_lufid_snapshot",
  "PathPrefix": "fs/ext4/"
}

Results:
{
  "error": "no matches"
}

15/2 2026/07/14 19:42 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 42098
							output: 33
							thoughts: 18
						

Thoughts:
Okay, here's my attempt at summarizing that thought process, tailored for an expert audience:

**Grep, Then Digest**

Right, let's cut straight to the chase. That's a good starting point. I'm going to reach for `grep` and aim for `ext4_lufid_snapshot`. That likely targets the function responsible for managing snapshots within the ext4 filesystem. By examining the source code, I can quickly narrow down the areas involved in snapshot creation and management within the ext4 implementation. That's usually the most efficient way to understand its nuances.



16/2 2026/07/14 19:43 tool grepper
0m Args:
{
  "Expression": "ext4_lufid_snapshot",
  "PathPrefix": "fs/ext4/"
}

Results:
{
  "Output": "fs/ext4/namei.c=4092=static struct inode *ext4_whiteout_for_rename(struct mnt_idmap *idmap,\n--\nfs/ext4/namei.c-4127-/*\nfs/ext4/namei.c:4128: * ext4_lufid_snapshot - copy the LUFID record from a directory entry\nfs/ext4/namei.c-4129- *\n--\nfs/ext4/namei.c=4134=static struct ext4_dirent_fid *\nfs/ext4/namei.c:4135:ext4_lufid_snapshot(struct ext4_dir_entry_2 *de, unsigned int blocksize)\nfs/ext4/namei.c-4136-{\n--\nfs/ext4/namei.c=4161=static int ext4_rename(struct mnt_idmap *idmap, struct inode *old_dir,\n--\nfs/ext4/namei.c-4306-\t\tif (ext4_has_feature_dirdata(old.dir-\u003ei_sb)) {\nfs/ext4/namei.c:4307:\t\t\told_fid = ext4_lufid_snapshot(old.de,\nfs/ext4/namei.c-4308-\t\t\t\t\t\t      old.dir-\u003ei_sb-\u003es_blocksize);\n--\nfs/ext4/namei.c=4427=static int ext4_cross_rename(struct inode *old_dir, struct dentry *old_dentry,\n--\nfs/ext4/namei.c-4522-\tif (ext4_has_feature_dirdata(old.dir-\u003ei_sb)) {\nfs/ext4/namei.c:4523:\t\told_fid = ext4_lufid_snapshot(old.de, old.dir-\u003ei_sb-\u003es_blocksize);\nfs/ext4/namei.c:4524:\t\tnew_fid = ext4_lufid_snapshot(new.de, new.dir-\u003ei_sb-\u003es_blocksize);\nfs/ext4/namei.c-4525-\t\tif (old_fid || new_fid)\n"
}

17/2 2026/07/14 19:43 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 42834
							output: 34
							thoughts: 47
						

Thoughts:
Here's my take on those thoughts:

**Analyzing Relevant Static Functions for File System Operations**

Alright, so I'm focusing on these static functions specifically. We're looking at `ext4_lufid_snapshot`, which is crucial for our snapshot capabilities within the ext4 filesystem. I need to fully understand its interactions. To get a holistic view, I also need to bring in `ext4_insert_dentry_data` and `ext4_init_new_dir_data`. Considering these three together should give me the insights I'm looking for. I need to see how they interrelate in the context of file creation, deletion and especially snapshot implementation. It's time to dig in.



18/2 2026/07/14 19:43 tool grepper
0m Args:
{
  "Expression": "ext4_insert_dentry_data",
  "PathPrefix": "fs/ext4/"
}

Results:
{
  "Output": "fs/ext4/ext4.h=3055=extern int ext4_find_dest_de(struct inode *dir, struct buffer_head *bh,\n--\nfs/ext4/ext4.h-3059-\t\t\t     int dlen);\nfs/ext4/ext4.h:3060:void ext4_insert_dentry_data(struct inode *dir, struct inode *inode,\nfs/ext4/ext4.h-3061-\t\t\t     struct ext4_dir_entry_2 *de,\n--\nfs/ext4/ext4.h=3065=static inline void ext4_insert_dentry(struct inode *dir, struct inode *inode,\n--\nfs/ext4/ext4.h-3069-{\nfs/ext4/ext4.h:3070:\text4_insert_dentry_data(dir, inode, de, buf_size, fname, NULL);\nfs/ext4/ext4.h-3071-}\n--\nfs/ext4/inline.c=969=static int ext4_add_dirent_to_inline(handle_t *handle,\n--\nfs/ext4/inline.c-997-\t\treturn err;\nfs/ext4/inline.c:998:\text4_insert_dentry_data(dir, inode, de, inline_size, fname, dfid);\nfs/ext4/inline.c-999-\n--\nfs/ext4/namei.c=2287=int ext4_find_dest_de(struct inode *dir, struct buffer_head *bh,\n--\nfs/ext4/namei.c-2324-\nfs/ext4/namei.c:2325:void ext4_insert_dentry_data(struct inode *dir, struct inode *inode,\nfs/ext4/namei.c-2326-\t\t\t     struct ext4_dir_entry_2 *de, int buf_size,\n--\nfs/ext4/namei.c=2356=static int add_dirent_to_buf(handle_t *handle, struct ext4_filename *fname,\n--\nfs/ext4/namei.c-2396-\t/* By now the buffer is marked for journaling */\nfs/ext4/namei.c:2397:\text4_insert_dentry_data(dir, inode, de, blocksize, fname, dfid);\nfs/ext4/namei.c-2398-\n"
}

19/2 2026/07/14 19:43 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 43692
							output: 35
							thoughts: 13
						

Thoughts:
Okay, here's my interpretation of that line of thought, tailored for an expert audience, and presented as if it were my own internal monologue:

**Diving Deep into ext4 Directory Initialization**

Right, so we're looking at `ext4_init_new_dir_data`. This function, as I recall, is where the rubber really meets the road in the creation of a new directory within an ext4 filesystem. I'm thinking about the intricate dance it performs to lay down the initial bits of metadata and data blocks. We're setting up that directory structure, and there's a lot to consider.

First, I'm mentally parsing the arguments: the inode, the block group, the parent directory's inode, the new directory's name, and the link count. The key here is efficiency – we don't want to over-allocate or fragment things right off the bat. It has to ensure the new directory is laid out contiguously whenever possible.

I'm picturing the process: allocating an inode for the new directory, zeroing out the block, initializing the directory entry. But I'm also considering the implications of the block group policy. Is it using the default strategy, or are there specific allocation hints in play? That makes a significant difference in terms of fragmentation and subsequent performance.

Then there's the `ext4_set_inode_flags` call to consider. We're talking about setting flags like `EXT4_DIRTY` and others. I'm mentally reviewing my knowledge of the potential problems that could happen with an un-set flag.
 I'm also scrutinizing the `ext4_init_new_dir_data` function's role in constructing the "." and ".." entries. Are we being careful with the link counts? It's essential those entries are set up correctly, or the filesystem will have a catastrophic failure.

Finally, I'm mentally checking for any optimization opportunities. Can we streamline block allocation further? Is there a better way to handle the initial directory entry size calculations? I'm always looking for ways to improve this process to avoid deadlocks. This is core filesystem functionality, so it needs to be bulletproof and efficient.

It's all about the details.



20/2 2026/07/14 19:43 tool grepper
0m Args:
{
  "Expression": "ext4_init_new_dir_data",
  "PathPrefix": "fs/ext4/"
}

Results:
{
  "Output": "fs/ext4/ext4.h=3312=extern int ext4_ind_migrate(struct inode *inode);\n--\nfs/ext4/ext4.h-3314-/* namei.c */\nfs/ext4/ext4.h:3315:extern int ext4_init_new_dir_data(handle_t *handle, struct inode *dir,\nfs/ext4/ext4.h-3316-\t\t\t\t  struct inode *inode,\n--\nfs/ext4/ext4.h=3318=static inline int ext4_init_new_dir(handle_t *handle, struct inode *dir,\n--\nfs/ext4/ext4.h-3320-{\nfs/ext4/ext4.h:3321:\treturn ext4_init_new_dir_data(handle, dir, inode, NULL, NULL);\nfs/ext4/ext4.h-3322-}\n--\nfs/ext4/namei.c=3204=int ext4_init_dirblock(handle_t *handle, struct inode *inode,\n--\nfs/ext4/namei.c-3249-\nfs/ext4/namei.c:3250:int ext4_init_new_dir_data(handle_t *handle, struct inode *dir,\nfs/ext4/namei.c-3251-\t\t\t   struct inode *inode,\n"
}

21/2 2026/07/14 19:43 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 44574
							output: 36
							thoughts: 29
						

Thoughts:
Okay, here's my take on those thoughts, tailored for an expert audience:

**Analyzing Directory Entry Length Calculations**

Right, let's circle back and make absolutely sure we haven't overlooked any subtle interactions related to directory entry lengths, particularly as they relate to `ext4_dirent_get_data_len` and `ext4_dir_entry_len`. I've got to ensure the code correctly calculates the length of each entry in an ext4 directory. I need to cross-reference those two functions and confirm their assumptions are consistent. We're dealing with potentially vulnerable areas here, so I need to see how they interact. Are there corner cases where these calculations could be off, leading to buffer overflows or incorrect interpretation of the directory structure? Have any of the newer filesystem enhancements affected these calculations? Time to dig into the details and reconfirm my understanding of how these functions relate and the potential risks if those calculations are wrong.



22/2 2026/07/14 19:43 tool grepper
0m Args:
{
  "Expression": "ext4_dirent_get_data_len",
  "PathPrefix": "fs/ext4/"
}

Results:
{
  "Output": "fs/ext4/dir.c=469=int ext4_htree_store_dirent(struct file *dir_file, __u32 hash,\n--\nfs/ext4/dir.c-485-\t\t\text4_rec_len_from_disk(dirent-\u003erec_len, buf_size);\nfs/ext4/dir.c:486:\t\textra_data = ext4_dirent_get_data_len(dirent, rec_len);\nfs/ext4/dir.c-487-\t}\n--\nfs/ext4/ext4.h=2637=static inline unsigned int ext4_dirent_rec_len(unsigned int name_len,\n--\nfs/ext4/ext4.h-2645-\t * hash is stored as a CFHASH extension and is already counted in the\nfs/ext4/ext4.h:2646:\t * name_len argument (via ext4_dirent_get_data_len), so adding it again\nfs/ext4/ext4.h-2647-\t * would double-count.\n--\nfs/ext4/ext4.h=4112=static inline bool ext4_dir_entry_is_tail(struct ext4_dir_entry_2 *de)\n--\nfs/ext4/ext4.h-4122-/*\nfs/ext4/ext4.h:4123: * ext4_dirent_get_data_len() - Compute the total dirdata length for an entry.\nfs/ext4/ext4.h-4124- * @de: directory entry\n--\nfs/ext4/ext4.h-4133- */\nfs/ext4/ext4.h:4134:static inline int ext4_dirent_get_data_len(struct ext4_dir_entry_2 *de,\nfs/ext4/ext4.h-4135-\t\t\t\t\t   unsigned int rec_len)\n--\nfs/ext4/ext4.h=4193=static inline unsigned int ext4_dir_entry_len(struct ext4_dir_entry_2 *de,\n--\nfs/ext4/ext4.h-4197-\tunsigned int rec_len = ext4_rec_len_from_disk(de-\u003erec_len, blocksize);\nfs/ext4/ext4.h:4198:\tunsigned int dirdata = ext4_dirent_get_data_len(de, rec_len);\nfs/ext4/ext4.h-4199-\n"
}

23/2 2026/07/14 19:43 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 45470
							output: 32
							thoughts: 279
						

Thoughts:
**Analyzing ext4 Directory Structure Code**

Okay, so I'm looking at this `ext4` directory code. I see that `ext4_dirent_get_data_len` is an inline function, which makes sense. I need to add several functions to my `FocusSymbols` list. Let's see... I'll include `ext4_ioctl_set_lufid`, `ext4_dirdata_set_lufid`, `ext4_dirdata_get`, `ext4_dirdata_set`, `ext4_lufid_snapshot`, `ext4_insert_dentry_data`, and `ext4_init_new_dir_data`. Hold on, looks like `ext4_dirdata_set` is actually static, as is `ext4_lufid_snapshot` and `ext4_ioctl_set_lufid`. However, `ext4_dirdata_set_lufid` is exported, as is `ext4_dirdata_get`. Also global are `ext4_insert_dentry_data` and `ext4_init_new_dir_data`.

Now, before I move on, let's just make sure I haven't missed anything interesting. Hmmm, `ext4_dirent_rec_len` is also inline, as is `ext4_dir_entry_len`. Ah, I see, they've modified `ext4_check_dx_root`, and they've added `dx_get_dx_info`. Alright, that looks comprehensive.



24/2 2026/07/14 19:43 tool grepper
0m Args:
{
  "Expression": "dx_get_dx_info",
  "PathPrefix": "fs/ext4/"
}

Results:
{
  "Output": "fs/ext4/namei.c=528=ext4_next_entry(struct ext4_dir_entry_2 *p, unsigned long blocksize)\n--\nfs/ext4/namei.c-537- */\nfs/ext4/namei.c:538:static struct dx_root_info *dx_get_dx_info(struct inode *dir, void *de_buf)\nfs/ext4/namei.c-539-{\n--\nfs/ext4/namei.c=603=static inline unsigned dx_root_limit(struct inode *dir,\n--\nfs/ext4/namei.c-608-\nfs/ext4/namei.c:609:\tinfo = dx_get_dx_info(dir, dot_de);\nfs/ext4/namei.c-610-\tif (IS_ERR(info))\n--\nfs/ext4/namei.c=814=dx_probe(struct ext4_filename *fname, struct inode *dir,\n--\nfs/ext4/namei.c-830-\nfs/ext4/namei.c:831:\tinfo = dx_get_dx_info(dir, (struct ext4_dir_entry_2 *)frame-\u003ebh-\u003eb_data);\nfs/ext4/namei.c-832-\tif (IS_ERR(info))\n--\nfs/ext4/namei.c=983=static void dx_release(struct inode *dir, struct dx_frame *frames)\n--\nfs/ext4/namei.c-991-\nfs/ext4/namei.c:992:\tinfo = dx_get_dx_info(dir, (struct ext4_dir_entry_2 *)frames[0].bh-\u003eb_data);\nfs/ext4/namei.c-993-\tif (IS_ERR(info))\n--\nfs/ext4/namei.c=2465=static int make_indexed_dir(handle_t *handle, struct ext4_filename *fname,\n--\nfs/ext4/namei.c-2543-\t/* initialize hashing info */\nfs/ext4/namei.c:2544:\tdx_info = dx_get_dx_info(dir, dot_de);\nfs/ext4/namei.c-2545-\tif (IS_ERR(dx_info)) {\n--\nfs/ext4/namei.c=2748=static int ext4_dx_add_entry(handle_t *handle, struct ext4_filename *fname,\n--\nfs/ext4/namei.c-2890-\t\t\tdx_set_block(entries + 0, newblock);\nfs/ext4/namei.c:2891:\t\t\tinfo = dx_get_dx_info(dir, (struct ext4_dir_entry_2 *)\nfs/ext4/namei.c-2892-\t\t\t\t\t      frames[0].bh-\u003eb_data);\n"
}

25/2 2026/07/14 19:43 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 46870
							output: 33
							thoughts: 281
						

Thoughts:
**Adding `dx_get_dx_info` to the Focus Symbols and Refining Configuration**

Okay, so I'm integrating `dx_get_dx_info` into the `FocusSymbols` list. Good, that's straightforward. Now, let's just double-check the configuration dependencies. The initial thought was `EXT4_FS`, which seems right, but is there anything else I need to consider? Ah, the patch mentions `EXT4_FEATURE_INCOMPAT_DIRDATA`. Right, that's an *ext4 feature*, not a kernel config itself. So, confirming that `EXT4_FS` is indeed sufficient is the correct approach.

Now, a quick review of the existing `FocusSymbols` is in order. Let's make sure I haven't missed anything obvious. I have:

*   `ext4_ioctl_set_lufid`
*   `ext4_dirdata_set_lufid`
*   `ext4_dirdata_get`
*   `ext4_dirdata_set`
*   `ext4_lufid_snapshot`
*   `ext4_insert_dentry_data`
*   `ext4_init_new_dir_data`
*   `dx_get_dx_info`

Looks good, covering a decent range of ext4's dirdata functionalities.

Let's do one last scan to see if anything else jumps out as relevant. I'll need to remember to check for functions that might have been impacted or related. `ext4_dirent_get_data_len` is inline, so it won't be in the symbol table, but it's worth noting here, to keep it in mind. Similarly, `ext4_dir_entry_len` is also inline. Hmm, `ext4_check_dx_root` is modified, that one is worth some additional scrutiny.



26/2 2026/07/14 19:43 tool grepper
0m Args:
{
  "Expression": "ext4_check_dx_root",
  "PathPrefix": "fs/ext4/"
}

Results:
{
  "Output": "fs/ext4/namei.c=2356=static int add_dirent_to_buf(handle_t *handle, struct ext4_filename *fname,\n--\nfs/ext4/namei.c-2420-\nfs/ext4/namei.c:2421:static bool ext4_check_dx_root(struct inode *dir,\nfs/ext4/namei.c-2422-\t\t\t       struct ext4_dir_entry_2 *dot_de,\n--\nfs/ext4/namei.c=2465=static int make_indexed_dir(handle_t *handle, struct ext4_filename *fname,\n--\nfs/ext4/namei.c-2501-\tdotdot_de = ext4_next_entry(dot_de, blocksize);\nfs/ext4/namei.c:2502:\tif (!ext4_check_dx_root(dir, dot_de, dotdot_de, \u0026de)) {\nfs/ext4/namei.c-2503-\t\tbrelse(bh);\n"
}

27/2 2026/07/14 19:43 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 47880
							output: 30
							thoughts: 30
						

Thoughts:
Okay, here's my take on those thoughts, tailored for an expert audience:

**Adding `ext4_check_dx_root` to the Focus List & Considering `ext4_setent`**

Alright, I'm going to add `ext4_check_dx_root` to my `FocusSymbols` list. I need to keep a close eye on that function in the context of my current investigation. I anticipate it's likely to be relevant given the area I'm probing.

Now, let me shift gears slightly and revisit `ext4_setent`. I want to quickly re-evaluate it and see if it interacts with the first focus. It will be useful to understand its role in this context, and any potential interactions, before diving in further. It's good to keep multiple angles in view for more effective debugging.



28/2 2026/07/14 19:43 tool grepper
0m Args:
{
  "Expression": "ext4_setent",
  "PathPrefix": "fs/ext4/"
}

Results:
{
  "Output": "fs/ext4/namei.c=3900=static int ext4_rename_dir_finish(handle_t *handle, struct ext4_renament *ent,\n--\nfs/ext4/namei.c-3928-\nfs/ext4/namei.c:3929:static int ext4_setent(handle_t *handle, struct ext4_renament *ent,\nfs/ext4/namei.c-3930-\t\t       unsigned ino, unsigned file_type,\n--\nfs/ext4/namei.c=4006=static void ext4_resetent(handle_t *handle, struct ext4_renament *ent,\n--\nfs/ext4/namei.c-4027-\nfs/ext4/namei.c:4028:\text4_setent(handle, \u0026old, ino, file_type, NULL);\nfs/ext4/namei.c-4029-\tbrelse(old.bh);\n--\nfs/ext4/namei.c=4161=static int ext4_rename(struct mnt_idmap *idmap, struct inode *old_dir,\n--\nfs/ext4/namei.c-4291-\t\t */\nfs/ext4/namei.c:4292:\t\tretval = ext4_setent(handle, \u0026old, whiteout-\u003ei_ino,\nfs/ext4/namei.c-4293-\t\t\t\t     EXT4_FT_CHRDEV, NULL);\n--\nfs/ext4/namei.c-4315-\t\t}\nfs/ext4/namei.c:4316:\t\tretval = ext4_setent(handle, \u0026new,\nfs/ext4/namei.c-4317-\t\t\t\t     old.inode-\u003ei_ino, old_file_type, old_fid);\n--\nfs/ext4/namei.c=4427=static int ext4_cross_rename(struct inode *old_dir, struct dentry *old_dentry,\n--\nfs/ext4/namei.c-4535-\t\tnew_file_type \u0026= ~EXT4_DIRENT_LUFID;\nfs/ext4/namei.c:4536:\tretval = ext4_setent(handle, \u0026new, old.inode-\u003ei_ino, old_de_file_type,\nfs/ext4/namei.c-4537-\t\t\t     old_fid);\n--\nfs/ext4/namei.c-4540-\nfs/ext4/namei.c:4541:\tretval = ext4_setent(handle, \u0026old, new.inode-\u003ei_ino, new_file_type,\nfs/ext4/namei.c-4542-\t\t\t     new_fid);\n"
}

29/2 2026/07/14 19:43 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 48819
							output: 32
							thoughts: 35
						

Thoughts:
Okay, here's my take on those thoughts, tailored for an expert audience, and written as if they were my own:

**Integrating src_fid into ext4_setent**

Right, so I need to modify `ext4_setent`. The goal is to incorporate the handling of `src_fid`. This feels like a straightforward, though potentially delicate, change. Best to be meticulous here. Now, before I jump in, I want to confirm how `__ext4_add_entry` is handling entries, as that is likely the function that will get updated. I'll need to double check the interactions. I'm going to start with a sanity check there, making sure to consider the implications on the inode structure and directory entry layout. It's a key entry point, and that's the area I need to understand most clearly.



30/2 2026/07/14 19:43 tool grepper
0m Args:
{
  "Expression": "__ext4_add_entry",
  "PathPrefix": "fs/ext4/"
}

Results:
{
  "Output": "fs/ext4/namei.c=2465=static int make_indexed_dir(handle_t *handle, struct ext4_filename *fname,\n--\nfs/ext4/namei.c-2621- */\nfs/ext4/namei.c:2622:static int __ext4_add_entry(handle_t *handle, struct inode *dir,\nfs/ext4/namei.c-2623-\t\t\t  const struct qstr *d_name,\n--\nfs/ext4/namei.c=2734=static int ext4_add_entry(handle_t *handle, struct dentry *dentry,\n--\nfs/ext4/namei.c-2740-\t\treturn -ENOKEY;\nfs/ext4/namei.c:2741:\treturn __ext4_add_entry(handle, dir, \u0026dentry-\u003ed_name, inode,\nfs/ext4/namei.c-2742-\t\t\t\tdentry-\u003ed_fsdata);\n--\nfs/ext4/namei.c=3737=int __ext4_link(struct inode *dir, struct inode *inode,\n--\nfs/ext4/namei.c-3759-\nfs/ext4/namei.c:3760:\terr = __ext4_add_entry(handle, dir, d_name, inode,\nfs/ext4/namei.c-3761-\t\t\t       dentry ? dentry-\u003ed_fsdata : NULL);\n"
}

31/2 2026/07/14 19:43 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 49450
							output: 252
							thoughts: 337
						

Thoughts:
**My Assessment of the EXT4 Patch**

Okay, I've reviewed the code. My immediate thought is, let's include `__ext4_add_entry` in the list of functions involved, just to ensure comprehensive coverage. So that's the final list:

*   `ext4_ioctl_set_lufid`
*   `ext4_dirdata_set_lufid`
*   `ext4_dirdata_get`
*   `ext4_dirdata_set`
*   `ext4_lufid_snapshot`
*   `ext4_insert_dentry_data`
*   `ext4_init_new_dir_data`
*   `dx_get_dx_info`
*   `ext4_check_dx_root`
*   `ext4_setent`
*   `__ext4_add_entry`

Now, regarding configuration, my understanding is that `EXT4_FS` is the relevant config. However, I always like to double-check these assumptions. I see the patch touches `fs/ext4/sysfs.c`, adding a `dirdata` feature. Also, it modifies `fs/ext4/fast_commit.c` with the inclusion of `EXT4_FC_REASON_DIRDATA`, and it handles dirdata in `fs/ext4/inline.c`. There's the addition of `EXT4_IOC_SET_LUFID` in `fs/ext4/ioctl.c`. Then, significant changes are present in `fs/ext4/namei.c`, and `ext4_htree_store_dirent` gets modified in `fs/ext4/dir.c`.

Based on this analysis, I'm confident that `EXT4_FS` is, in fact, the only configuration dependency for this change. I'm ready to submit my findings and conclusions.



32/2 2026/07/14 19:44 tool set-results
0m Args:
{
  "EnableConfigs": [
    "EXT4_FS"
  ],
  "FocusSymbols": [
    "ext4_ioctl_set_lufid",
    "ext4_dirdata_set_lufid",
    "ext4_dirdata_get",
    "ext4_dirdata_set",
    "ext4_lufid_snapshot",
    "ext4_insert_dentry_data",
    "ext4_init_new_dir_data",
    "dx_get_dx_info",
    "ext4_check_dx_root",
    "ext4_setent",
    "__ext4_add_entry"
  ],
  "Reasoning": "The patch introduces a new ext4 feature `dirdata` (EXT4_FEATURE_INCOMPAT_DIRDATA) and a new ioctl `EXT4_IOC_SET_LUFID` to attach LUFID data to directory entries. It modifies core directory entry parsing, insertion, splitting, and renaming logic to handle variable-length data appended to directory entries. This is a significant functional change to the ext4 filesystem that should be fuzzed.",
  "WorthFuzzing": true
}

Results:
{
  "EnableConfigs": [
    "EXT4_FS"
  ],
  "FocusSymbols": [
    "ext4_ioctl_set_lufid",
    "ext4_dirdata_set_lufid",
    "ext4_dirdata_get",
    "ext4_dirdata_set",
    "ext4_lufid_snapshot",
    "ext4_insert_dentry_data",
    "ext4_init_new_dir_data",
    "dx_get_dx_info",
    "ext4_check_dx_root",
    "ext4_setent",
    "__ext4_add_entry"
  ],
  "Reasoning": "The patch introduces a new ext4 feature `dirdata` (EXT4_FEATURE_INCOMPAT_DIRDATA) and a new ioctl `EXT4_IOC_SET_LUFID` to attach LUFID data to directory entries. It modifies core directory entry parsing, insertion, splitting, and renaming logic to handle variable-length data appended to directory entries. This is a significant functional change to the ext4 filesystem that should be fuzzed.",
  "WorthFuzzing": true
}

LLM Calls Summary:
Total Calls Total Tokens Avg Tokens Total Duration (Seconds) Avg Duration (Seconds)
Tool Calls Summary:
Total Calls Total Duration (Seconds) Avg Duration (Seconds)