Currently ext4 will delete ea entry from ibody and recreate ea entry which store the same value when expanding inode. The main performance issue is caused by the fact that ext4 will destroy and recreate the ea inode, and such behavior is redundant. The patch is a bit ugly, because ext4_xattr_set_entry() contains the creating,deleting,replacing of xattr without external intervention, this looks good. But the movement of ea entry from ibody to block breaks this, so add an argument for ext4_xattr_set_entry() for this break, and then ext4_xattr_block_set() will reuse the ea_inode instead of recreating an ea_inode which store the same value. Signed-off-by: JunChao Sun <sunjunchao2870@xxxxxxxxx> --- fs/ext4/xattr.c | 99 ++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 81 insertions(+), 18 deletions(-) diff --git a/fs/ext4/xattr.c b/fs/ext4/xattr.c index 767454d74cd6..439581e630d4 100644 --- a/fs/ext4/xattr.c +++ b/fs/ext4/xattr.c @@ -1634,7 +1634,7 @@ static int ext4_xattr_inode_lookup_create(handle_t *handle, struct inode *inode, static int ext4_xattr_set_entry(struct ext4_xattr_info *i, struct ext4_xattr_search *s, handle_t *handle, struct inode *inode, - bool is_block) + bool is_block, struct inode *mv_ea_inode) { struct ext4_xattr_entry *last, *next; struct ext4_xattr_entry *here = s->here; @@ -1727,7 +1727,7 @@ static int ext4_xattr_set_entry(struct ext4_xattr_info *i, goto out; } } - if (i->value && in_inode) { + if (i->value && in_inode && !mv_ea_inode) { WARN_ON_ONCE(!i->value_len); ret = ext4_xattr_inode_alloc_quota(inode, i->value_len); @@ -1819,7 +1819,9 @@ static int ext4_xattr_set_entry(struct ext4_xattr_info *i, if (i->value) { /* Insert new value. */ - if (in_inode) { + if (in_inode && mv_ea_inode) { + here->e_value_inum = cpu_to_le32(mv_ea_inode->i_ino); + } else if (in_inode) { here->e_value_inum = cpu_to_le32(new_ea_inode->i_ino); } else if (i->value_len) { void *val = s->base + min_offs - new_size; @@ -1838,7 +1840,7 @@ static int ext4_xattr_set_entry(struct ext4_xattr_info *i, } update_hash: - if (i->value) { + if (i->value && !mv_ea_inode) { __le32 hash = 0; /* Entry hash calculation. */ @@ -1922,7 +1924,7 @@ ext4_xattr_block_find(struct inode *inode, struct ext4_xattr_info *i, static int ext4_xattr_block_set(handle_t *handle, struct inode *inode, struct ext4_xattr_info *i, - struct ext4_xattr_block_find *bs) + struct ext4_xattr_block_find *bs, struct inode *mv_ea_inode) { struct super_block *sb = inode->i_sb; struct buffer_head *new_bh = NULL; @@ -1972,7 +1974,7 @@ ext4_xattr_block_set(handle_t *handle, struct inode *inode, } ea_bdebug(bs->bh, "modifying in-place"); error = ext4_xattr_set_entry(i, s, handle, inode, - true /* is_block */); + true /* is_block */, NULL); ext4_xattr_block_csum_set(inode, bs->bh); unlock_buffer(bs->bh); if (error == -EFSCORRUPTED) @@ -2040,7 +2042,7 @@ ext4_xattr_block_set(handle_t *handle, struct inode *inode, s->end = s->base + sb->s_blocksize; } - error = ext4_xattr_set_entry(i, s, handle, inode, true /* is_block */); + error = ext4_xattr_set_entry(i, s, handle, inode, true /* is_block */, mv_ea_inode); if (error == -EFSCORRUPTED) goto bad_block; if (error) @@ -2286,7 +2288,7 @@ int ext4_xattr_ibody_set(handle_t *handle, struct inode *inode, if (!EXT4_INODE_HAS_XATTR_SPACE(inode)) return -ENOSPC; - error = ext4_xattr_set_entry(i, s, handle, inode, false /* is_block */); + error = ext4_xattr_set_entry(i, s, handle, inode, false /* is_block */, NULL); if (error) return error; header = IHDR(inode, ext4_raw_inode(&is->iloc)); @@ -2429,7 +2431,7 @@ ext4_xattr_set_handle(handle_t *handle, struct inode *inode, int name_index, if (!is.s.not_found) error = ext4_xattr_ibody_set(handle, inode, &i, &is); else if (!bs.s.not_found) - error = ext4_xattr_block_set(handle, inode, &i, &bs); + error = ext4_xattr_block_set(handle, inode, &i, &bs, NULL); } else { error = 0; /* Xattr value did not change? Save us some work and bail out */ @@ -2446,7 +2448,7 @@ ext4_xattr_set_handle(handle_t *handle, struct inode *inode, int name_index, error = ext4_xattr_ibody_set(handle, inode, &i, &is); if (!error && !bs.s.not_found) { i.value = NULL; - error = ext4_xattr_block_set(handle, inode, &i, &bs); + error = ext4_xattr_block_set(handle, inode, &i, &bs, NULL); } else if (error == -ENOSPC) { if (EXT4_I(inode)->i_file_acl && !bs.s.base) { brelse(bs.bh); @@ -2455,7 +2457,7 @@ ext4_xattr_set_handle(handle_t *handle, struct inode *inode, int name_index, if (error) goto cleanup; } - error = ext4_xattr_block_set(handle, inode, &i, &bs); + error = ext4_xattr_block_set(handle, inode, &i, &bs, NULL); if (!error && !is.s.not_found) { i.value = NULL; error = ext4_xattr_ibody_set(handle, inode, &i, @@ -2615,6 +2617,10 @@ static int ext4_xattr_move_to_block(handle_t *handle, struct inode *inode, .in_inode = !!entry->e_value_inum, }; struct ext4_xattr_ibody_header *header = IHDR(inode, raw_inode); + struct ext4_xattr_entry *here = NULL, *last = NULL, *next = NULL; + struct inode *old_ea_inode = NULL; + size_t name_size = EXT4_XATTR_LEN(entry->e_name_len); + size_t min_offs; int error; is = kzalloc(sizeof(struct ext4_xattr_ibody_find), GFP_NOFS); @@ -2660,20 +2666,76 @@ static int ext4_xattr_move_to_block(handle_t *handle, struct inode *inode, i.value = buffer; i.value_len = value_size; + here = is->s.here; + last = is->s.first; + min_offs = is->s.end - is->s.base; + /* Compute min_offs and last entry */ + for (; !IS_LAST_ENTRY(last); last = next) { + next = EXT4_XATTR_NEXT(last); + if ((void *)next >= is->s.end) { + EXT4_ERROR_INODE(inode, "corrupted xattr entries"); + error = -EFSCORRUPTED; + goto out; + } + if (!last->e_value_inum && last->e_value_size) { + size_t offs = le16_to_cpu(last->e_value_offs); + + if (offs < min_offs) + min_offs = offs; + } + } + + /* Remove the name in ibody */ + last = ENTRY((void *)last - name_size); + memmove(here, (void *)here + name_size, + (void *)last - (void *)here + sizeof(__u32)); + memset(last, 0, name_size); + + /* Get the ea_inode which store the old value */ + if (here->e_value_inum) { + error = ext4_xattr_inode_iget(inode, + le32_to_cpu(here->e_value_inum), + le32_to_cpu(here->e_hash), + &old_ea_inode); + if (error) { + old_ea_inode = NULL; + goto out; + } + } else if (here->e_value_size) { + /* Remove the old value in ibody */ + void *first_val = is->s.base + min_offs; + void *rm_val = is->s.base + le16_to_cpu(here->e_value_offs); + size_t rm_size = EXT4_XATTR_SIZE(le32_to_cpu(here->e_value_size)); + size_t offs = le16_to_cpu(here->e_value_offs); + + memmove(first_val + rm_size, first_val, rm_val - first_val); + memset(first_val, 0, rm_size); + min_offs += rm_size; + + /* Adjust all value offsets */ + last = is->s.first; + while (!IS_LAST_ENTRY(last)) { + size_t o = le16_to_cpu(last->e_value_offs); + + if (!last->e_value_inum && + last->e_value_size && o < offs) + last->e_value_offs = cpu_to_le16(o + rm_size); + last = EXT4_XATTR_NEXT(last); + } + } + error = ext4_xattr_block_find(inode, &i, bs); if (error) goto out; - /* Move ea entry from the inode into the block */ - error = ext4_xattr_block_set(handle, inode, &i, bs); + /* + * Move ea entry from the inode into the block, and do not need to + * recreate an ea_inode that store the same value. + */ + error = ext4_xattr_block_set(handle, inode, &i, bs, old_ea_inode); if (error) goto out; - /* Remove the chosen entry from the inode */ - i.value = NULL; - i.value_len = 0; - error = ext4_xattr_ibody_set(handle, inode, &i, is); - out: kfree(b_entry_name); if (entry->e_value_inum && buffer) @@ -2684,6 +2746,7 @@ static int ext4_xattr_move_to_block(handle_t *handle, struct inode *inode, brelse(bs->bh); kfree(is); kfree(bs); + iput(old_ea_inode); return error; } -- 2.17.1