aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJan Kara <jack@suse.cz>2017-05-21 22:33:23 -0400
committerTheodore Ts'o <tytso@mit.edu>2017-05-21 22:33:23 -0400
commit7d95eddf313c88b24f99d4ca9c2411a4b82fef33 (patch)
treea4863c14b30a544b46f8b76a053199ccd1f8bf38
parentb4709067ac0944e4a5b94eabdc26155c6f2efbd7 (diff)
ext4: fix SEEK_HOLE
Currently, SEEK_HOLE implementation in ext4 may both return that there's a hole at some offset although that offset already has data and skip some holes during a search for the next hole. The first problem is demostrated by: xfs_io -c "falloc 0 256k" -c "pwrite 0 56k" -c "seek -h 0" file wrote 57344/57344 bytes at offset 0 56 KiB, 14 ops; 0.0000 sec (2.054 GiB/sec and 538461.5385 ops/sec) Whence Result HOLE 0 Where we can see that SEEK_HOLE wrongly returned offset 0 as containing a hole although we have written data there. The second problem can be demonstrated by: xfs_io -c "falloc 0 256k" -c "pwrite 0 56k" -c "pwrite 128k 8k" -c "seek -h 0" file wrote 57344/57344 bytes at offset 0 56 KiB, 14 ops; 0.0000 sec (1.978 GiB/sec and 518518.5185 ops/sec) wrote 8192/8192 bytes at offset 131072 8 KiB, 2 ops; 0.0000 sec (2 GiB/sec and 500000.0000 ops/sec) Whence Result HOLE 139264 Where we can see that hole at offsets 56k..128k has been ignored by the SEEK_HOLE call. The underlying problem is in the ext4_find_unwritten_pgoff() which is just buggy. In some cases it fails to update returned offset when it finds a hole (when no pages are found or when the first found page has higher index than expected), in some cases conditions for detecting hole are just missing (we fail to detect a situation where indices of returned pages are not contiguous). Fix ext4_find_unwritten_pgoff() to properly detect non-contiguous page indices and also handle all cases where we got less pages then expected in one place and handle it properly there. CC: stable@vger.kernel.org Fixes: c8c0df241cc2719b1262e627f999638411934f60 CC: Zheng Liu <wenqing.lz@taobao.com> Signed-off-by: Jan Kara <jack@suse.cz> Signed-off-by: Theodore Ts'o <tytso@mit.edu>
-rw-r--r--fs/ext4/file.c50
1 files changed, 14 insertions, 36 deletions
diff --git a/fs/ext4/file.c b/fs/ext4/file.c
index 831fd6beebf0..bbea2dccd584 100644
--- a/fs/ext4/file.c
+++ b/fs/ext4/file.c
@@ -484,47 +484,27 @@ static int ext4_find_unwritten_pgoff(struct inode *inode,
484 num = min_t(pgoff_t, end - index, PAGEVEC_SIZE); 484 num = min_t(pgoff_t, end - index, PAGEVEC_SIZE);
485 nr_pages = pagevec_lookup(&pvec, inode->i_mapping, index, 485 nr_pages = pagevec_lookup(&pvec, inode->i_mapping, index,
486 (pgoff_t)num); 486 (pgoff_t)num);
487 if (nr_pages == 0) { 487 if (nr_pages == 0)
488 if (whence == SEEK_DATA)
489 break;
490
491 BUG_ON(whence != SEEK_HOLE);
492 /*
493 * If this is the first time to go into the loop and
494 * offset is not beyond the end offset, it will be a
495 * hole at this offset
496 */
497 if (lastoff == startoff || lastoff < endoff)
498 found = 1;
499 break;
500 }
501
502 /*
503 * If this is the first time to go into the loop and
504 * offset is smaller than the first page offset, it will be a
505 * hole at this offset.
506 */
507 if (lastoff == startoff && whence == SEEK_HOLE &&
508 lastoff < page_offset(pvec.pages[0])) {
509 found = 1;
510 break; 488 break;
511 }
512 489
513 for (i = 0; i < nr_pages; i++) { 490 for (i = 0; i < nr_pages; i++) {
514 struct page *page = pvec.pages[i]; 491 struct page *page = pvec.pages[i];
515 struct buffer_head *bh, *head; 492 struct buffer_head *bh, *head;
516 493
517 /* 494 /*
518 * If the current offset is not beyond the end of given 495 * If current offset is smaller than the page offset,
519 * range, it will be a hole. 496 * there is a hole at this offset.
520 */ 497 */
521 if (lastoff < endoff && whence == SEEK_HOLE && 498 if (whence == SEEK_HOLE && lastoff < endoff &&
522 page->index > end) { 499 lastoff < page_offset(pvec.pages[i])) {
523 found = 1; 500 found = 1;
524 *offset = lastoff; 501 *offset = lastoff;
525 goto out; 502 goto out;
526 } 503 }
527 504
505 if (page->index > end)
506 goto out;
507
528 lock_page(page); 508 lock_page(page);
529 509
530 if (unlikely(page->mapping != inode->i_mapping)) { 510 if (unlikely(page->mapping != inode->i_mapping)) {
@@ -564,20 +544,18 @@ static int ext4_find_unwritten_pgoff(struct inode *inode,
564 unlock_page(page); 544 unlock_page(page);
565 } 545 }
566 546
567 /* 547 /* The no. of pages is less than our desired, we are done. */
568 * The no. of pages is less than our desired, that would be a 548 if (nr_pages < num)
569 * hole in there.
570 */
571 if (nr_pages < num && whence == SEEK_HOLE) {
572 found = 1;
573 *offset = lastoff;
574 break; 549 break;
575 }
576 550
577 index = pvec.pages[i - 1]->index + 1; 551 index = pvec.pages[i - 1]->index + 1;
578 pagevec_release(&pvec); 552 pagevec_release(&pvec);
579 } while (index <= end); 553 } while (index <= end);
580 554
555 if (whence == SEEK_HOLE && lastoff < endoff) {
556 found = 1;
557 *offset = lastoff;
558 }
581out: 559out:
582 pagevec_release(&pvec); 560 pagevec_release(&pvec);
583 return found; 561 return found;