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@xxxxxxxxxx> --- lib/ext2fs/ext2_err.et.in | 3 - resize/resize2fs.c | 167 +++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 157 insertions(+), 13 deletions(-) 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: -- 2.13.2.932.g7449e964c-goog