aboutsummaryrefslogtreecommitdiffstats
path: root/fs
diff options
context:
space:
mode:
authorJeff Moyer <jmoyer@redhat.com>2012-07-12 09:43:14 -0400
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>2012-07-19 11:58:20 -0400
commit4ff1ddad40c57605cc33a78699e4559217a06a46 (patch)
tree4dea6826fde5c05a60e1a78bfaa735699919972a /fs
parentd53c2bc79a9044090cafc82530be55d4bb144905 (diff)
block: fix infinite loop in __getblk_slow
commit 91f68c89d8f35fe98ea04159b9a3b42d0149478f upstream. Commit 080399aaaf35 ("block: don't mark buffers beyond end of disk as mapped") exposed a bug in __getblk_slow that causes mount to hang as it loops infinitely waiting for a buffer that lies beyond the end of the disk to become uptodate. The problem was initially reported by Torsten Hilbrich here: https://lkml.org/lkml/2012/6/18/54 and also reported independently here: http://www.sysresccd.org/forums/viewtopic.php?f=13&t=4511 and then Richard W.M. Jones and Marcos Mello noted a few separate bugzillas also associated with the same issue. This patch has been confirmed to fix: https://bugzilla.redhat.com/show_bug.cgi?id=835019 The main problem is here, in __getblk_slow: for (;;) { struct buffer_head * bh; int ret; bh = __find_get_block(bdev, block, size); if (bh) return bh; ret = grow_buffers(bdev, block, size); if (ret < 0) return NULL; if (ret == 0) free_more_memory(); } __find_get_block does not find the block, since it will not be marked as mapped, and so grow_buffers is called to fill in the buffers for the associated page. I believe the for (;;) loop is there primarily to retry in the case of memory pressure keeping grow_buffers from succeeding. However, we also continue to loop for other cases, like the block lying beond the end of the disk. So, the fix I came up with is to only loop when grow_buffers fails due to memory allocation issues (return value of 0). The attached patch was tested by myself, Torsten, and Rich, and was found to resolve the problem in call cases. Signed-off-by: Jeff Moyer <jmoyer@redhat.com> Reported-and-Tested-by: Torsten Hilbrich <torsten.hilbrich@secunet.com> Tested-by: Richard W.M. Jones <rjones@redhat.com> Reviewed-by: Josh Boyer <jwboyer@redhat.com> [ Jens is on vacation, taking this directly - Linus ] Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Diffstat (limited to 'fs')
-rw-r--r--fs/buffer.c22
1 files changed, 13 insertions, 9 deletions
diff --git a/fs/buffer.c b/fs/buffer.c
index 330cbce1141..d42162672c7 100644
--- a/fs/buffer.c
+++ b/fs/buffer.c
@@ -1084,6 +1084,9 @@ grow_buffers(struct block_device *bdev, sector_t block, int size)
1084static struct buffer_head * 1084static struct buffer_head *
1085__getblk_slow(struct block_device *bdev, sector_t block, int size) 1085__getblk_slow(struct block_device *bdev, sector_t block, int size)
1086{ 1086{
1087 int ret;
1088 struct buffer_head *bh;
1089
1087 /* Size must be multiple of hard sectorsize */ 1090 /* Size must be multiple of hard sectorsize */
1088 if (unlikely(size & (bdev_logical_block_size(bdev)-1) || 1091 if (unlikely(size & (bdev_logical_block_size(bdev)-1) ||
1089 (size < 512 || size > PAGE_SIZE))) { 1092 (size < 512 || size > PAGE_SIZE))) {
@@ -1096,20 +1099,21 @@ __getblk_slow(struct block_device *bdev, sector_t block, int size)
1096 return NULL; 1099 return NULL;
1097 } 1100 }
1098 1101
1099 for (;;) { 1102retry:
1100 struct buffer_head * bh; 1103 bh = __find_get_block(bdev, block, size);
1101 int ret; 1104 if (bh)
1105 return bh;
1102 1106
1107 ret = grow_buffers(bdev, block, size);
1108 if (ret == 0) {
1109 free_more_memory();
1110 goto retry;
1111 } else if (ret > 0) {
1103 bh = __find_get_block(bdev, block, size); 1112 bh = __find_get_block(bdev, block, size);
1104 if (bh) 1113 if (bh)
1105 return bh; 1114 return bh;
1106
1107 ret = grow_buffers(bdev, block, size);
1108 if (ret < 0)
1109 return NULL;
1110 if (ret == 0)
1111 free_more_memory();
1112 } 1115 }
1116 return NULL;
1113} 1117}
1114 1118
1115/* 1119/*