resize2fs: add support for resizing filesystems with ea_inode feature

Submitted by Tahsin Erdogan on July 15, 2017, 12:38 a.m.

Details

Message ID 20170715003849.1982-1-tahsin@google.com
State Accepted
Headers show

Commit Message

Tahsin Erdogan July 15, 2017, 12:38 a.m.
Resizing filesystems with ea_inode feature was disallowed so far
because the code for updating the ea entries was missing. This patch
adds that support.

Signed-off-by: Tahsin Erdogan <tahsin@google.com>
---
 lib/ext2fs/ext2_err.et.in |   3 -
 resize/resize2fs.c        | 167 +++++++++++++++++++++++++++++++++++++++++++---
 2 files changed, 157 insertions(+), 13 deletions(-)

Comments

Theodore Ts'o July 24, 2017, 4:08 a.m.
On Fri, Jul 14, 2017 at 05:38:49PM -0700, Tahsin Erdogan wrote:
> Resizing filesystems with ea_inode feature was disallowed so far
> because the code for updating the ea entries was missing. This patch
> adds that support.
> 
> Signed-off-by: Tahsin Erdogan <tahsin@google.com>

Thanks, applied.  I do have a few comments; if you can look into
providing some test cases, that would be great.  Also, although this
isn't first change that we've added to resize2fs which has this
property, it used to be that if resize2fs was interrupted, the user
could usually recover by running e2fsck.  With ea_inode, there will be
cases where resize2fs getting interrupted could result in the file
system's extended attributes getting pretty badly scrambled.

Using an undo file is one way to solve the problem; so perhaps
something we should consider is making the undo file enabled by
default.  (Although then we need to figure out where to put it; the
current directory might not be the right place, especially if it
happens to be /tmp.)

					- Ted

Patch hide | download patch | download mbox

diff --git a/lib/ext2fs/ext2_err.et.in b/lib/ext2fs/ext2_err.et.in
index c5a9ffcc420c..ac96964d93d0 100644
--- a/lib/ext2fs/ext2_err.et.in
+++ b/lib/ext2fs/ext2_err.et.in
@@ -542,7 +542,4 @@  ec	EXT2_ET_CORRUPT_JOURNAL_SB,
 ec	EXT2_ET_INODE_CORRUPTED,
 	"Inode is corrupted"
 
-ec	EXT2_ET_CANNOT_MOVE_EA_INODE,
-	"Cannot move extended attribute inode"
-
 	end
diff --git a/resize/resize2fs.c b/resize/resize2fs.c
index a54564f08ae5..20a0c463e411 100644
--- a/resize/resize2fs.c
+++ b/resize/resize2fs.c
@@ -1986,6 +1986,145 @@  static void quiet_com_err_proc(const char *whoami EXT2FS_ATTR((unused)),
 {
 }
 
+static int fix_ea_entries(ext2_extent imap, struct ext2_ext_attr_entry *entry,
+			  struct ext2_ext_attr_entry *end, ext2_ino_t last_ino)
+{
+	int modified = 0;
+	ext2_ino_t new_ino;
+	errcode_t retval;
+
+	while (entry < end && !EXT2_EXT_IS_LAST_ENTRY(entry)) {
+		if (entry->e_value_inum > last_ino) {
+			new_ino = ext2fs_extent_translate(imap,
+							  entry->e_value_inum);
+			entry->e_value_inum = new_ino;
+			modified = 1;
+		}
+		entry = EXT2_EXT_ATTR_NEXT(entry);
+	}
+	return modified;
+}
+
+static int fix_ea_ibody_entries(ext2_extent imap,
+				struct ext2_inode_large *inode, int inode_size,
+				ext2_ino_t last_ino)
+{
+	struct ext2_ext_attr_entry *start, *end;
+	__u32 *ea_magic;
+
+	if (inode->i_extra_isize == 0)
+		return 0;
+
+	ea_magic = (__u32 *)((char *)inode + EXT2_GOOD_OLD_INODE_SIZE +
+				inode->i_extra_isize);
+	if (*ea_magic != EXT2_EXT_ATTR_MAGIC)
+		return 0;
+
+	start = (struct ext2_ext_attr_entry *)(ea_magic + 1);
+	end = (struct ext2_ext_attr_entry *)((char *)inode + inode_size);
+
+	return fix_ea_entries(imap, start, end, last_ino);
+}
+
+static int fix_ea_block_entries(ext2_extent imap, char *block_buf,
+				unsigned int blocksize, ext2_ino_t last_ino)
+{
+	struct ext2_ext_attr_header *header;
+	struct ext2_ext_attr_entry *start, *end;
+
+	header = (struct ext2_ext_attr_header *)block_buf;
+	start = (struct ext2_ext_attr_entry *)(header+1);
+	end = (struct ext2_ext_attr_entry *)(block_buf + blocksize);
+
+	return fix_ea_entries(imap, start, end, last_ino);
+}
+
+/* A simple LRU cache to check recently processed blocks. */
+struct blk_cache {
+	int cursor;
+	blk64_t blks[4];
+};
+
+#define BLK_IN_CACHE(b,c) ((b) == (c).blks[0] || (b) == (c).blks[1] || \
+			   (b) == (c).blks[2] || (b) == (c).blks[3])
+#define BLK_ADD_CACHE(b,c) { 			\
+	(c).blks[(c).cursor] = (b);		\
+	(c).cursor = ((c).cursor + 1) % 4;	\
+}
+
+static errcode_t fix_ea_inode_refs(ext2_resize_t rfs, struct ext2_inode *inode,
+				   char *block_buf, ext2_ino_t last_ino)
+{
+	ext2_filsys	fs = rfs->new_fs;
+	ext2_inode_scan	scan = NULL;
+	ext2_ino_t	ino;
+	int		inode_size = EXT2_INODE_SIZE(fs->super);
+	blk64_t		blk;
+	int		modified;
+	struct blk_cache blk_cache = { 0 };
+	struct ext2_ext_attr_header *header;
+	errcode_t		retval;
+
+	header = (struct ext2_ext_attr_header *)block_buf;
+
+	retval = ext2fs_open_inode_scan(fs, 0, &scan);
+	if (retval)
+		goto out;
+
+	while (1) {
+		retval = ext2fs_get_next_inode_full(scan, &ino, inode,
+						    inode_size);
+		if (retval)
+			goto out;
+		if (!ino)
+			break;
+
+		if (inode->i_links_count == 0 && ino != EXT2_RESIZE_INO)
+			continue; /* inode not in use */
+
+		if (inode_size != EXT2_GOOD_OLD_INODE_SIZE) {
+			modified = fix_ea_ibody_entries(rfs->imap,
+					(struct ext2_inode_large *)inode,
+					inode_size, last_ino);
+			if (modified) {
+				retval = ext2fs_write_inode_full(fs, ino, inode,
+								 inode_size);
+				if (retval)
+					goto out;
+			}
+		}
+
+		blk = ext2fs_file_acl_block(fs, inode);
+		if (blk && !BLK_IN_CACHE(blk, blk_cache)) {
+			retval = ext2fs_read_ext_attr3(fs, blk, block_buf, ino);
+			if (retval)
+				goto out;
+
+			modified = fix_ea_block_entries(rfs->imap, block_buf,
+							fs->blocksize,
+							last_ino);
+			if (modified) {
+				retval = ext2fs_write_ext_attr3(fs, blk,
+								block_buf, ino);
+				if (retval)
+					goto out;
+				/*
+				 * If refcount is greater than 1, we might see
+				 * the same block referenced by other inodes
+				 * later.
+				 */
+				if (header->h_refcount > 1)
+					BLK_ADD_CACHE(blk, blk_cache);
+			}
+		}
+	}
+	retval = 0;
+out:
+	if (scan)
+		ext2fs_close_inode_scan(scan);
+	return retval;
+
+}
 static errcode_t inode_scan_and_fix(ext2_resize_t rfs)
 {
 	struct process_block_struct	pb;
@@ -1996,6 +2135,7 @@  static errcode_t inode_scan_and_fix(ext2_resize_t rfs)
 	char			*block_buf = 0;
 	ext2_ino_t		start_to_move;
 	int			inode_size;
+	int			update_ea_inode_refs = 0;
 
 	if ((rfs->old_fs->group_desc_count <=
 	     rfs->new_fs->group_desc_count) &&
@@ -2057,15 +2197,6 @@  static errcode_t inode_scan_and_fix(ext2_resize_t rfs)
 		if (ino <= start_to_move)
 			goto remap_blocks; /* Don't need to move inode. */
 
-		/*
-		 * Moving an extended attribute inode requires updating all inodes
-		 * that reference it which is a lot more involved.
-		 */
-		if (inode->i_flags & EXT4_EA_INODE_FL) {
-			retval = EXT2_ET_CANNOT_MOVE_EA_INODE;
-			goto errout;
-		}
-
 		/*
 		 * Find a new inode.  Now that extents and directory blocks
 		 * are tied to the inode number through the checksum, we must
@@ -2077,7 +2208,15 @@  static errcode_t inode_scan_and_fix(ext2_resize_t rfs)
 
 		ext2fs_inode_alloc_stats2(rfs->new_fs, new_inode, +1,
 					  pb.is_dir);
-		inode->i_ctime = time(0);
+		/*
+		 * i_ctime field in xattr inodes contain a portion of the ref
+		 * count, do not overwrite.
+		 */
+		if (inode->i_flags & EXT4_EA_INODE_FL)
+			update_ea_inode_refs = 1;
+		else
+			inode->i_ctime = time(0);
+
 		retval = ext2fs_write_inode_full(rfs->old_fs, new_inode,
 						inode, inode_size);
 		if (retval)
@@ -2143,6 +2282,14 @@  remap_blocks:
 				goto errout;
 		}
 	}
+
+	if (update_ea_inode_refs &&
+	    ext2fs_has_feature_ea_inode(rfs->new_fs->super)) {
+		retval = fix_ea_inode_refs(rfs, inode, block_buf,
+					   start_to_move);
+		if (retval)
+			goto errout;
+	}
 	io_channel_flush(rfs->old_fs->io);
 
 errout: