--- /dev/null
+From e3014d14a81edde488d9a6758eea8afc41752d2d Mon Sep 17 00:00:00 2001
+From: Jan Kara <jack@suse.cz>
+Date: Mon, 29 Aug 2016 15:38:11 -0400
+Subject: [PATCH] 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>
+---
+ fs/ext4/xattr.c | 58 ++++++++++++++++++++++++---------------------------------
+ 1 file changed, 24 insertions(+), 34 deletions(-)
+
+Index: linux-4.4.49-92.14_lustre-vanilla/fs/ext4/xattr.c
+===================================================================
+--- linux-4.4.49-92.14_lustre-vanilla.orig/fs/ext4/xattr.c
++++ linux-4.4.49-92.14_lustre-vanilla/fs/ext4/xattr.c
+@@ -1619,18 +1619,19 @@ retry:
+ */
+ static void ext4_xattr_shift_entries(struct ext4_xattr_entry *entry,
+ int value_offs_shift, void *to,
+- void *from, size_t n, int blocksize)
++ void *from, size_t n)
+ {
+ struct ext4_xattr_entry *last = entry;
+ int new_offs;
+
++ /* We always shift xattr headers further thus offsets get lower */
++ BUG_ON(value_offs_shift > 0);
++
+ /* Adjust the value offsets of the entries */
+ for (; !IS_LAST_ENTRY(last); last = EXT4_XATTR_NEXT(last)) {
+ if (!last->e_value_inum && last->e_value_size) {
+ new_offs = le16_to_cpu(last->e_value_offs) +
+ value_offs_shift;
+- BUG_ON(new_offs + le32_to_cpu(last->e_value_size)
+- > blocksize);
+ last->e_value_offs = cpu_to_le16(new_offs);
+ }
+ }
+@@ -1651,7 +1652,8 @@ int ext4_expand_extra_isize_ea(struct in
+ struct ext4_xattr_ibody_find *is = NULL;
+ struct ext4_xattr_block_find *bs = NULL;
+ char *buffer = NULL, *b_entry_name = NULL;
+- size_t min_offs, free;
++ size_t min_offs;
++ size_t ifree, bfree;
+ int total_ino;
+ void *base, *start, *end;
+ int error = 0, tried_min_extra_isize = 0;
+@@ -1682,17 +1684,9 @@ retry:
+ last = entry;
+ total_ino = sizeof(struct ext4_xattr_ibody_header);
+
+- 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
+@@ -1713,8 +1707,8 @@ retry:
+ first = BFIRST(bh);
+ end = bh->b_data + bh->b_size;
+ 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) {
+ tried_min_extra_isize++;
+ new_extra_isize = s_min_extra_isize;
+@@ -1725,10 +1719,10 @@ retry:
+ goto cleanup;
+ }
+ } 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;
+ struct ext4_xattr_entry *small_entry = NULL;
+ struct ext4_xattr_info i = {
+@@ -1736,7 +1730,6 @@ retry:
+ .value_len = 0,
+ };
+ 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;
+
+ is = kzalloc(sizeof(struct ext4_xattr_ibody_find), GFP_NOFS);
+@@ -1758,8 +1751,9 @@ retry:
+ total_size =
+ EXT4_XATTR_SIZE(le32_to_cpu(last->e_value_size)) +
+ 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;
+ } else {
+ entry = last;
+@@ -1788,6 +1782,7 @@ retry:
+ offs = le16_to_cpu(entry->e_value_offs);
+ size = le32_to_cpu(entry->e_value_size);
+ entry_size = EXT4_XATTR_LEN(entry->e_name_len);
++ total_size = entry_size + EXT4_XATTR_SIZE(size);
+ i.name_index = entry->e_name_index,
+ buffer = kmalloc(EXT4_XATTR_SIZE(size), GFP_NOFS);
+ b_entry_name = kmalloc(entry->e_name_len + 1, GFP_NOFS);
+@@ -1815,21 +1810,8 @@ retry:
+ if (error)
+ goto cleanup;
+ 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.value = buffer;
+@@ -1850,6 +1832,15 @@ retry:
+ kfree(is);
+ 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);
++ EXT4_I(inode)->i_extra_isize = new_extra_isize;
+ brelse(bh);
+ out:
+ ext4_clear_inode_state(inode, EXT4_STATE_NO_EXPAND);
+From 887a9730614727c4fff7cb756711b190593fc1df Mon Sep 17 00:00:00 2001
+From: Konstantin Khlebnikov <khlebnikov@yandex-team.ru>
+Date: Sun, 21 May 2017 22:36:23 -0400
+Subject: [PATCH] ext4: keep existing extra fields when inode expands
+
+ext4_expand_extra_isize() should clear only space between old and new
+size.
+
+Fixes: 6dd4ee7cab7e # v2.6.23
+Cc: stable@vger.kernel.org
+Signed-off-by: Konstantin Khlebnikov <khlebnikov@yandex-team.ru>
+Signed-off-by: Theodore Ts'o <tytso@mit.edu>
+---
+ fs/ext4/inode.c | 5 +++--
+ 1 file changed, 3 insertions(+), 2 deletions(-)
+
+diff --git a/fs/ext4/inode.c b/fs/ext4/inode.c
+index 1bd0bfa..7cd99de 100644
+--- a/fs/ext4/inode.c
++++ b/fs/ext4/inode.c
+@@ -5637,8 +5637,9 @@ static int ext4_expand_extra_isize(struct inode *inode,
+ /* No extended attributes present */
+ if (!ext4_test_inode_state(inode, EXT4_STATE_XATTR) ||
+ header->h_magic != cpu_to_le32(EXT4_XATTR_MAGIC)) {
+- memset((void *)raw_inode + EXT4_GOOD_OLD_INODE_SIZE, 0,
+- new_extra_isize);
++ memset((void *)raw_inode + EXT4_GOOD_OLD_INODE_SIZE +
++ EXT4_I(inode)->i_extra_isize, 0,
++ new_extra_isize - EXT4_I(inode)->i_extra_isize);
+ EXT4_I(inode)->i_extra_isize = new_extra_isize;
+ return 0;
+ }
+--
+2.9.3
+