Commit 631a86fc authored by Jeff Moyer's avatar Jeff Moyer Committed by Ben Hutchings

block: fix infinite loop in __getblk_slow

commit 91f68c89 upstream.

Commit 080399aa ("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: default avatarJeff Moyer <jmoyer@redhat.com>
Reported-and-Tested-by: default avatarTorsten Hilbrich <torsten.hilbrich@secunet.com>
Tested-by: default avatarRichard W.M. Jones <rjones@redhat.com>
Reviewed-by: default avatarJosh Boyer <jwboyer@redhat.com>
[ Jens is on vacation, taking this directly  - Linus ]
--
Stable Notes: this patch requires backport to 3.0, 3.2 and 3.3.
Signed-off-by: default avatarLinus Torvalds <torvalds@linux-foundation.org>
Signed-off-by: default avatarBen Hutchings <ben@decadent.org.uk>
parent 3bbc9e19
...@@ -1087,6 +1087,9 @@ grow_buffers(struct block_device *bdev, sector_t block, int size) ...@@ -1087,6 +1087,9 @@ grow_buffers(struct block_device *bdev, sector_t block, int size)
static struct buffer_head * static struct buffer_head *
__getblk_slow(struct block_device *bdev, sector_t block, int size) __getblk_slow(struct block_device *bdev, sector_t block, int size)
{ {
int ret;
struct buffer_head *bh;
/* Size must be multiple of hard sectorsize */ /* Size must be multiple of hard sectorsize */
if (unlikely(size & (bdev_logical_block_size(bdev)-1) || if (unlikely(size & (bdev_logical_block_size(bdev)-1) ||
(size < 512 || size > PAGE_SIZE))) { (size < 512 || size > PAGE_SIZE))) {
...@@ -1099,20 +1102,21 @@ __getblk_slow(struct block_device *bdev, sector_t block, int size) ...@@ -1099,20 +1102,21 @@ __getblk_slow(struct block_device *bdev, sector_t block, int size)
return NULL; return NULL;
} }
for (;;) { retry:
struct buffer_head * bh;
int ret;
bh = __find_get_block(bdev, block, size); bh = __find_get_block(bdev, block, size);
if (bh) if (bh)
return bh; return bh;
ret = grow_buffers(bdev, block, size); ret = grow_buffers(bdev, block, size);
if (ret < 0) if (ret == 0) {
return NULL;
if (ret == 0)
free_more_memory(); free_more_memory();
goto retry;
} else if (ret > 0) {
bh = __find_get_block(bdev, block, size);
if (bh)
return bh;
} }
return NULL;
} }
/* /*
......
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