Merge remote-tracking branches 'spi/fix/qup' and 'spi/fix/topcliff-pch' into spi...
[firefly-linux-kernel-4.4.55.git] / fs / ext4 / xattr.c
index 1f5cf5880718d28c8ca7893f7165807f78101c6b..4eec399ec807bc6733d1a90b8c3d0d205eb795c1 100644 (file)
@@ -520,8 +520,8 @@ static void ext4_xattr_update_super_block(handle_t *handle,
 }
 
 /*
- * Release the xattr block BH: If the reference count is > 1, decrement
- * it; otherwise free the block.
+ * Release the xattr block BH: If the reference count is > 1, decrement it;
+ * otherwise free the block.
  */
 static void
 ext4_xattr_release_block(handle_t *handle, struct inode *inode,
@@ -542,16 +542,31 @@ ext4_xattr_release_block(handle_t *handle, struct inode *inode,
                if (ce)
                        mb_cache_entry_free(ce);
                get_bh(bh);
+               unlock_buffer(bh);
                ext4_free_blocks(handle, inode, bh, 0, 1,
                                 EXT4_FREE_BLOCKS_METADATA |
                                 EXT4_FREE_BLOCKS_FORGET);
-               unlock_buffer(bh);
        } else {
                le32_add_cpu(&BHDR(bh)->h_refcount, -1);
                if (ce)
                        mb_cache_entry_release(ce);
+               /*
+                * Beware of this ugliness: Releasing of xattr block references
+                * from different inodes can race and so we have to protect
+                * from a race where someone else frees the block (and releases
+                * its journal_head) before we are done dirtying the buffer. In
+                * nojournal mode this race is harmless and we actually cannot
+                * call ext4_handle_dirty_xattr_block() with locked buffer as
+                * that function can call sync_dirty_buffer() so for that case
+                * we handle the dirtying after unlocking the buffer.
+                */
+               if (ext4_handle_valid(handle))
+                       error = ext4_handle_dirty_xattr_block(handle, inode,
+                                                             bh);
                unlock_buffer(bh);
-               error = ext4_handle_dirty_xattr_block(handle, inode, bh);
+               if (!ext4_handle_valid(handle))
+                       error = ext4_handle_dirty_xattr_block(handle, inode,
+                                                             bh);
                if (IS_SYNC(inode))
                        ext4_handle_sync(handle);
                dquot_free_block(inode, EXT4_C2B(EXT4_SB(inode->i_sb), 1));