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