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 --- 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