Viewing: ext4-hash-indexed-dir-dotdot-update.patch

commit 0536b2a26992f5d2ef9e3537a196afac81281f60
Author:     Li Dongyang <dongyangli@ddn.com>
AuthorDate: Wed Apr 17 15:36:55 2024 +1000

LU-17711 osd-ldiskfs: do not delete dotdot during rename

Since upstream kernel commit v5.12-rc4-32-g6c0912739699
ext4_dir_entry_2 after rec_len will be wiped when deleting
the entry.

This creates a problem with rename, when we delete dotdot
first and if it's a dx dir, kernel will wipe entire dx_root
in the block after dotdot entry.

We can just update the dotdot entry in-place without deleting.

For dx dirs, ext4_update_dotdot() takes care of dotdot and
inserting dotdot is an update, use it for linear dirs also.

Rewrite ext4_update_dotdot() to get a few fixes:
*use ext4_read_dirblock to get the first block.
*do not assert on data read from disk, we check the dot and
dotdot entry and if anything looks wrong, we return -EFSCORRUPTED.
*make sure the change is journalled.
*set metadata_csum correctly for dx dirs.

Update ext4-data-in-dirent.patch, if dotdot entry has no space
for dirdata, try to expand the dotdot entry by moving the
entries behind it, or move the dx_root for dx dirs.

Add conf-sanity/154 to verify that the ".." entry was updated
properly after restore, including with an htree split directory
with dx_root entry.

Signed-off-by: Li Dongyang <dongyangli@ddn.com>
Reviewed-by: Andreas Dilger <adilger@whamcloud.com>
Reviewed-by: Shaun Tancheff <shaun.tancheff@hpe.com>
Reviewed-by: Jian Yu <yujian@whamcloud.com>
Reviewed-by: Oleg Drokin <green@whamcloud.com>
Change-Id: I33e862739fa44f583aaa4369190d6d80271db13b
Reviewed-on: https://review.whamcloud.com/54723

---
 fs/ext4/namei.c | 71 +++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 71 insertions(+)

diff --git a/fs/ext4/namei.c b/fs/ext4/namei.c
index f54e868..14ff68e 100644
--- a/fs/ext4/namei.c
+++ b/fs/ext4/namei.c
@@ -2174,6 +2174,67 @@ out_frames:
 	return retval;
 }
 
+/* update ".." entry */
+static int ext4_update_dotdot(handle_t *handle, struct dentry *dentry,
+			      struct inode *inode)
+{
+	struct inode *dir = dentry->d_parent->d_inode;
+	struct buffer_head *bh;
+	struct ext4_dir_entry_2 *dot_de, *dotdot_de;
+	unsigned int offset;
+	int retval = 0;
+
+	if (IS_ERR(handle))
+		return PTR_ERR(handle);
+
+	if (IS_DIRSYNC(dir))
+		handle->h_sync = 1;
+
+	bh = ext4_read_dirblock(dir, 0, DIRENT_HTREE);
+	if (IS_ERR(bh))
+		return PTR_ERR(bh);
+
+	dot_de = (struct ext4_dir_entry_2 *) bh->b_data;
+	if (ext4_check_dir_entry(dir, NULL, dot_de, bh, bh->b_data,
+				 bh->b_size, 0) ||
+	    le32_to_cpu(dot_de->inode) != dir->i_ino ||
+	    strcmp(".", dot_de->name)) {
+		EXT4_ERROR_INODE(dir, "directory missing '.'");
+		retval = -EFSCORRUPTED;
+		goto out;
+	}
+	offset = ext4_rec_len_from_disk(dot_de->rec_len,
+					dir->i_sb->s_blocksize);
+	dotdot_de = ext4_next_entry(dot_de, dir->i_sb->s_blocksize);
+	if (ext4_check_dir_entry(dir, NULL, dotdot_de, bh, bh->b_data,
+				 bh->b_size, offset) ||
+	    le32_to_cpu(dotdot_de->inode) == 0 ||
+	    strcmp("..", dotdot_de->name)) {
+		EXT4_ERROR_INODE(dir, "directory missing '..'");
+		retval = -EFSCORRUPTED;
+		goto out;
+	}
+
+	BUFFER_TRACE(dir_block, "get_write_access");
+	retval = ext4_journal_get_write_access(handle, bh);
+	if (retval)
+		goto out;
+
+	dotdot_de->inode = cpu_to_le32(inode->i_ino);
+
+	ext4_mark_inode_dirty(handle, dir);
+	BUFFER_TRACE(dir_block, "call ext4_handle_dirty_metadata");
+	if (is_dx(dir)) {
+		retval = ext4_handle_dirty_dx_node(handle, dir, bh);
+	} else {
+		retval = ext4_handle_dirty_dirblock(handle, dir, bh);
+	}
+
+out:
+	brelse(bh);
+	return retval;
+}
+
 /*
  *	ext4_add_entry()
  *
@@ -2228,6 +2296,10 @@ static int ext4_add_entry(handle_t *handle, struct dentry *dentry,
 		}
 	}
 
+	if (dentry->d_name.len == 2 &&
+			memcmp(dentry->d_name.name, "..", 2) == 0)
+		return ext4_update_dotdot(handle, dentry, inode);
+
 	if (is_dx(dir)) {
 		retval = ext4_dx_add_entry(handle, &fname, dir, inode);
 		if (!retval || (retval != ERR_BAD_DX_DIR))
-- 
2.20.1