Re: [PATCH v5 09/11] ext4: add dirdata LUFID support for directory entry rename

Andreas Dilger <[email protected]>
Newsgroups org.kernel.vger.linux-ext4
Message-ID <[email protected]>
On Jul 14, 2026, at 06:51, Artem Blagodarenko <[email protected]> wrote:
> 
> ext4_setent() updates the inode number and file_type in an existing
> directory entry but did not handle the dirdata extension payload.
> Extend it with a src_fid parameter so that the source LUFID travels
> with the inode through rename.
> 
> When src_fid is non-NULL and the destination slot carries a LUFID of
> the same on-disk size, the source payload is copied into the destination
> before the file_type merge.  When src_fid is NULL (source has no LUFID),
> EXT4_DIRENT_LUFID is cleared from the destination entry.
> 
> CFHASH bytes are left in place: CFHASH is a property of the destination
> filename, not the inode, and it always precedes CFHASH in the extension
> layout so its offset is unaffected by the LUFID copy.
> 
> Both ext4_rename() and ext4_cross_rename() snapshot the source LUFID
> into a stack buffer before any setent() call, so that a cross-directory
> swap reads both sides before either is overwritten.
> 
> Signed-off-by: Artem Blagodarenko <[email protected]>

Rather than allocating one or two separate buffers for the lufid information,
it would seem more robust and efficient to just store them on the stack?
Perhaps a fixed buffer (e.g. 65 bytes is enough for 4x 16-byte FIDs + size)
would cover virtually all possible uses, and only in the very rare case that
the lufid field is larger would it need to allocate a buffer.

The maximum possible size would be 256 bytes, but that may be too large to
put the stack, especially two of them.

Cheers, Andreas

> ---
> fs/ext4/namei.c | 124 ++++++++++++++++++++++++++++++++++++++++++++----
> 1 file changed, 115 insertions(+), 9 deletions(-)
> 
> diff --git a/fs/ext4/namei.c b/fs/ext4/namei.c
> index 4a6f7ffa656f..168ffd3751cb 100644
> --- a/fs/ext4/namei.c
> +++ b/fs/ext4/namei.c
> @@ -3731,7 +3731,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;
> 
> @@ -3741,8 +3742,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);
> @@ -3779,7 +3829,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);
> }
> 
> @@ -3878,6 +3928,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.
> @@ -3906,6 +3982,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,
> @@ -4017,7 +4094,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);
> @@ -4030,8 +4107,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;
> }
> @@ -4118,6 +4205,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,
> @@ -4154,8 +4242,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,
> @@ -4234,12 +4323,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;
> 
> @@ -4272,6 +4376,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);
> -- 
> 2.43.7
> 


Cheers, Andreas
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.