浏览代码

ext4: fixup free space calculations when expanding inodes

Conditions checking whether there is enough free space in an xattr block
and when xattr is large enough to make enough space in the inode forgot
to account for the fact that inode need not be completely filled up with
xattrs. Thus we could move unnecessarily many xattrs out of inode or
even falsely claim there is not enough space to expand the inode. We
also forgot to update the amount of free space in xattr block when moving
more xattrs and thus could decide to move too big xattr resulting in
unexpected failure.

Fix these problems by properly updating free space in the inode and
xattr block as we move xattrs. To simplify the math, avoid shifting
xattrs after removing each one xattr and instead just shift xattrs only
once there is enough free space in the inode.

Signed-off-by: Jan Kara <jack@suse.cz>
Signed-off-by: Theodore Ts'o <tytso@mit.edu>
Jan Kara 9 年之前
父节点
当前提交
e3014d14a8
共有 1 个文件被更改,包括 24 次插入34 次删除
  1. 24 34
      fs/ext4/xattr.c

+ 24 - 34
fs/ext4/xattr.c

@@ -1350,7 +1350,8 @@ int ext4_expand_extra_isize_ea(struct inode *inode, int new_extra_isize,
 	struct ext4_xattr_ibody_find *is = NULL;
 	struct ext4_xattr_ibody_find *is = NULL;
 	struct ext4_xattr_block_find *bs = NULL;
 	struct ext4_xattr_block_find *bs = NULL;
 	char *buffer = NULL, *b_entry_name = NULL;
 	char *buffer = NULL, *b_entry_name = NULL;
-	size_t min_offs, free;
+	size_t min_offs;
+	size_t ifree, bfree;
 	int total_ino;
 	int total_ino;
 	void *base, *start, *end;
 	void *base, *start, *end;
 	int error = 0, tried_min_extra_isize = 0;
 	int error = 0, tried_min_extra_isize = 0;
@@ -1385,17 +1386,9 @@ retry:
 	if (error)
 	if (error)
 		goto cleanup;
 		goto cleanup;
 
 
-	free = ext4_xattr_free_space(last, &min_offs, base, &total_ino);
-	if (free >= isize_diff) {
-		entry = IFIRST(header);
-		ext4_xattr_shift_entries(entry,	EXT4_I(inode)->i_extra_isize
-				- new_extra_isize, (void *)raw_inode +
-				EXT4_GOOD_OLD_INODE_SIZE + new_extra_isize,
-				(void *)header, total_ino,
-				inode->i_sb->s_blocksize);
-		EXT4_I(inode)->i_extra_isize = new_extra_isize;
-		goto out;
-	}
+	ifree = ext4_xattr_free_space(last, &min_offs, base, &total_ino);
+	if (ifree >= isize_diff)
+		goto shift;
 
 
 	/*
 	/*
 	 * Enough free space isn't available in the inode, check if
 	 * Enough free space isn't available in the inode, check if
@@ -1416,8 +1409,8 @@ retry:
 		first = BFIRST(bh);
 		first = BFIRST(bh);
 		end = bh->b_data + bh->b_size;
 		end = bh->b_data + bh->b_size;
 		min_offs = end - base;
 		min_offs = end - base;
-		free = ext4_xattr_free_space(first, &min_offs, base, NULL);
-		if (free < isize_diff) {
+		bfree = ext4_xattr_free_space(first, &min_offs, base, NULL);
+		if (bfree + ifree < isize_diff) {
 			if (!tried_min_extra_isize && s_min_extra_isize) {
 			if (!tried_min_extra_isize && s_min_extra_isize) {
 				tried_min_extra_isize++;
 				tried_min_extra_isize++;
 				new_extra_isize = s_min_extra_isize;
 				new_extra_isize = s_min_extra_isize;
@@ -1428,10 +1421,10 @@ retry:
 			goto cleanup;
 			goto cleanup;
 		}
 		}
 	} else {
 	} else {
-		free = inode->i_sb->s_blocksize;
+		bfree = inode->i_sb->s_blocksize;
 	}
 	}
 
 
-	while (isize_diff > 0) {
+	while (isize_diff > ifree) {
 		size_t offs, size, entry_size;
 		size_t offs, size, entry_size;
 		struct ext4_xattr_entry *small_entry = NULL;
 		struct ext4_xattr_entry *small_entry = NULL;
 		struct ext4_xattr_info i = {
 		struct ext4_xattr_info i = {
@@ -1439,7 +1432,6 @@ retry:
 			.value_len = 0,
 			.value_len = 0,
 		};
 		};
 		unsigned int total_size;  /* EA entry size + value size */
 		unsigned int total_size;  /* EA entry size + value size */
-		unsigned int shift_bytes; /* No. of bytes to shift EAs by? */
 		unsigned int min_total_size = ~0U;
 		unsigned int min_total_size = ~0U;
 
 
 		is = kzalloc(sizeof(struct ext4_xattr_ibody_find), GFP_NOFS);
 		is = kzalloc(sizeof(struct ext4_xattr_ibody_find), GFP_NOFS);
@@ -1461,8 +1453,9 @@ retry:
 			total_size =
 			total_size =
 			EXT4_XATTR_SIZE(le32_to_cpu(last->e_value_size)) +
 			EXT4_XATTR_SIZE(le32_to_cpu(last->e_value_size)) +
 					EXT4_XATTR_LEN(last->e_name_len);
 					EXT4_XATTR_LEN(last->e_name_len);
-			if (total_size <= free && total_size < min_total_size) {
-				if (total_size < isize_diff) {
+			if (total_size <= bfree &&
+			    total_size < min_total_size) {
+				if (total_size + ifree < isize_diff) {
 					small_entry = last;
 					small_entry = last;
 				} else {
 				} else {
 					entry = last;
 					entry = last;
@@ -1491,6 +1484,7 @@ retry:
 		offs = le16_to_cpu(entry->e_value_offs);
 		offs = le16_to_cpu(entry->e_value_offs);
 		size = le32_to_cpu(entry->e_value_size);
 		size = le32_to_cpu(entry->e_value_size);
 		entry_size = EXT4_XATTR_LEN(entry->e_name_len);
 		entry_size = EXT4_XATTR_LEN(entry->e_name_len);
+		total_size = entry_size + EXT4_XATTR_SIZE(size);
 		i.name_index = entry->e_name_index,
 		i.name_index = entry->e_name_index,
 		buffer = kmalloc(EXT4_XATTR_SIZE(size), GFP_NOFS);
 		buffer = kmalloc(EXT4_XATTR_SIZE(size), GFP_NOFS);
 		b_entry_name = kmalloc(entry->e_name_len + 1, GFP_NOFS);
 		b_entry_name = kmalloc(entry->e_name_len + 1, GFP_NOFS);
@@ -1518,21 +1512,8 @@ retry:
 		if (error)
 		if (error)
 			goto cleanup;
 			goto cleanup;
 		total_ino -= entry_size;
 		total_ino -= entry_size;
-
-		entry = IFIRST(header);
-		if (entry_size + EXT4_XATTR_SIZE(size) >= isize_diff)
-			shift_bytes = isize_diff;
-		else
-			shift_bytes = entry_size + EXT4_XATTR_SIZE(size);
-		/* Adjust the offsets and shift the remaining entries ahead */
-		ext4_xattr_shift_entries(entry, -shift_bytes,
-			(void *)raw_inode + EXT4_GOOD_OLD_INODE_SIZE +
-			EXT4_I(inode)->i_extra_isize + shift_bytes,
-			(void *)header, total_ino, inode->i_sb->s_blocksize);
-
-		isize_diff -= shift_bytes;
-		EXT4_I(inode)->i_extra_isize += shift_bytes;
-		header = IHDR(inode, raw_inode);
+		ifree += total_size;
+		bfree -= total_size;
 
 
 		i.name = b_entry_name;
 		i.name = b_entry_name;
 		i.value = buffer;
 		i.value = buffer;
@@ -1553,6 +1534,15 @@ retry:
 		kfree(is);
 		kfree(is);
 		kfree(bs);
 		kfree(bs);
 	}
 	}
+
+shift:
+	/* Adjust the offsets and shift the remaining entries ahead */
+	entry = IFIRST(header);
+	ext4_xattr_shift_entries(entry,	EXT4_I(inode)->i_extra_isize
+			- new_extra_isize, (void *)raw_inode +
+			EXT4_GOOD_OLD_INODE_SIZE + new_extra_isize,
+			(void *)header, total_ino, inode->i_sb->s_blocksize);
+	EXT4_I(inode)->i_extra_isize = new_extra_isize;
 	brelse(bh);
 	brelse(bh);
 out:
 out:
 	ext4_clear_inode_state(inode, EXT4_STATE_NO_EXPAND);
 	ext4_clear_inode_state(inode, EXT4_STATE_NO_EXPAND);