mirror of
				https://github.com/torvalds/linux.git
				synced 2025-11-04 10:40:15 +02:00 
			
		
		
		
	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@eaf Signed-off-by: Ernesto A. Fernández <ernesto.mnd.fernandez@gmail.com> Reported-by: Anatoly Trosinenko <anatoly.trosinenko@gmail.com> Cc: Viacheslav Dubeyko <slava@dubeyko.com> Signed-off-by: Andrew Morton <akpm@linux-foundation.org> Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
This commit is contained in:
		
							parent
							
								
									7464726cb5
								
							
						
					
					
						commit
						31651c6071
					
				
					 1 changed files with 14 additions and 4 deletions
				
			
		| 
						 | 
					@ -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,
 | 
				
			||||||
		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)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	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)
 | 
				
			||||||
		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);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in a new issue