Commit 31651c60 authored by Ernesto A. Fernández's avatar Ernesto A. Fernández Committed by Linus Torvalds

hfsplus: avoid deadlock on file truncation

After an extent is removed from the extent tree, the corresponding bits
are also cleared from the block allocation file.  This is currently done
without releasing the tree lock.

The problem is that the allocation file has extents of its own; if it is
fragmented enough, some of them may be in the extent tree as well, and
hfsplus_get_block() will try to take the lock again.

To avoid deadlock, only hold the extent tree lock during the actual tree
operations.

Link: http://lkml.kernel.org/r/20180709202549.auxwkb6memlegb4a@eafSigned-off-by: default avatarErnesto A. Fernández <ernesto.mnd.fernandez@gmail.com>
Reported-by: default avatarAnatoly Trosinenko <anatoly.trosinenko@gmail.com>
Cc: Viacheslav Dubeyko <slava@dubeyko.com>
Signed-off-by: default avatarAndrew Morton <akpm@linux-foundation.org>
Signed-off-by: default avatarLinus Torvalds <torvalds@linux-foundation.org>
parent 7464726c
...@@ -336,6 +336,9 @@ static int hfsplus_free_extents(struct super_block *sb, ...@@ -336,6 +336,9 @@ static int hfsplus_free_extents(struct super_block *sb,
int i; int i;
int err = 0; int err = 0;
/* Mapping the allocation file may lock the extent tree */
WARN_ON(mutex_is_locked(&HFSPLUS_SB(sb)->ext_tree->tree_lock));
hfsplus_dump_extent(extent); hfsplus_dump_extent(extent);
for (i = 0; i < 8; extent++, i++) { for (i = 0; i < 8; extent++, i++) {
count = be32_to_cpu(extent->block_count); count = be32_to_cpu(extent->block_count);
...@@ -415,11 +418,13 @@ int hfsplus_free_fork(struct super_block *sb, u32 cnid, ...@@ -415,11 +418,13 @@ int hfsplus_free_fork(struct super_block *sb, u32 cnid,
if (res) if (res)
break; break;
start = be32_to_cpu(fd.key->ext.start_block); start = be32_to_cpu(fd.key->ext.start_block);
hfsplus_free_extents(sb, ext_entry,
total_blocks - start,
total_blocks);
hfs_brec_remove(&fd); hfs_brec_remove(&fd);
mutex_unlock(&fd.tree->tree_lock);
hfsplus_free_extents(sb, ext_entry, total_blocks - start,
total_blocks);
total_blocks = start; total_blocks = start;
mutex_lock(&fd.tree->tree_lock);
} while (total_blocks > blocks); } while (total_blocks > blocks);
hfs_find_exit(&fd); hfs_find_exit(&fd);
...@@ -576,15 +581,20 @@ void hfsplus_file_truncate(struct inode *inode) ...@@ -576,15 +581,20 @@ void hfsplus_file_truncate(struct inode *inode)
} }
while (1) { while (1) {
if (alloc_cnt == hip->first_blocks) { if (alloc_cnt == hip->first_blocks) {
mutex_unlock(&fd.tree->tree_lock);
hfsplus_free_extents(sb, hip->first_extents, hfsplus_free_extents(sb, hip->first_extents,
alloc_cnt, alloc_cnt - blk_cnt); alloc_cnt, alloc_cnt - blk_cnt);
hfsplus_dump_extent(hip->first_extents); hfsplus_dump_extent(hip->first_extents);
hip->first_blocks = blk_cnt; hip->first_blocks = blk_cnt;
mutex_lock(&fd.tree->tree_lock);
break; break;
} }
res = __hfsplus_ext_cache_extent(&fd, inode, alloc_cnt); res = __hfsplus_ext_cache_extent(&fd, inode, alloc_cnt);
if (res) if (res)
break; break;
hfs_brec_remove(&fd);
mutex_unlock(&fd.tree->tree_lock);
start = hip->cached_start; start = hip->cached_start;
hfsplus_free_extents(sb, hip->cached_extents, hfsplus_free_extents(sb, hip->cached_extents,
alloc_cnt - start, alloc_cnt - blk_cnt); alloc_cnt - start, alloc_cnt - blk_cnt);
...@@ -596,7 +606,7 @@ void hfsplus_file_truncate(struct inode *inode) ...@@ -596,7 +606,7 @@ void hfsplus_file_truncate(struct inode *inode)
alloc_cnt = start; alloc_cnt = start;
hip->cached_start = hip->cached_blocks = 0; hip->cached_start = hip->cached_blocks = 0;
hip->extent_state &= ~(HFSPLUS_EXT_DIRTY | HFSPLUS_EXT_NEW); hip->extent_state &= ~(HFSPLUS_EXT_DIRTY | HFSPLUS_EXT_NEW);
hfs_brec_remove(&fd); mutex_lock(&fd.tree->tree_lock);
} }
hfs_find_exit(&fd); hfs_find_exit(&fd);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment