add patch fix-off-by-one-in-loop-termination-in-ext4_find_unwritten_pgoff
[ext4-patch-queue.git] / fix-seek_hole
blob577d8c1aa297d2a604127ce91b96c50c40702c5a
1 ext4: fix SEEK_HOLE
3 From: Jan Kara <jack@suse.cz>
5 Currently, SEEK_HOLE implementation in ext4 may both return that there's
6 a hole at some offset although that offset already has data and skip
7 some holes during a search for the next hole. The first problem is
8 demostrated by:
10 xfs_io -c "falloc 0 256k" -c "pwrite 0 56k" -c "seek -h 0" file
11 wrote 57344/57344 bytes at offset 0
12 56 KiB, 14 ops; 0.0000 sec (2.054 GiB/sec and 538461.5385 ops/sec)
13 Whence  Result
14 HOLE    0
16 Where we can see that SEEK_HOLE wrongly returned offset 0 as containing
17 a hole although we have written data there. The second problem can be
18 demonstrated by:
20 xfs_io -c "falloc 0 256k" -c "pwrite 0 56k" -c "pwrite 128k 8k"
21        -c "seek -h 0" file
23 wrote 57344/57344 bytes at offset 0
24 56 KiB, 14 ops; 0.0000 sec (1.978 GiB/sec and 518518.5185 ops/sec)
25 wrote 8192/8192 bytes at offset 131072
26 8 KiB, 2 ops; 0.0000 sec (2 GiB/sec and 500000.0000 ops/sec)
27 Whence  Result
28 HOLE    139264
30 Where we can see that hole at offsets 56k..128k has been ignored by the
31 SEEK_HOLE call.
33 The underlying problem is in the ext4_find_unwritten_pgoff() which is
34 just buggy. In some cases it fails to update returned offset when it
35 finds a hole (when no pages are found or when the first found page has
36 higher index than expected), in some cases conditions for detecting hole
37 are just missing (we fail to detect a situation where indices of
38 returned pages are not contiguous).
40 Fix ext4_find_unwritten_pgoff() to properly detect non-contiguous page
41 indices and also handle all cases where we got less pages then expected
42 in one place and handle it properly there.
44 CC: stable@vger.kernel.org
45 Fixes: c8c0df241cc2719b1262e627f999638411934f60
46 CC: Zheng Liu <wenqing.lz@taobao.com>
47 Signed-off-by: Jan Kara <jack@suse.cz>
48 Signed-off-by: Theodore Ts'o <tytso@mit.edu>
49 ---
50  fs/ext4/file.c | 50 ++++++++++++++------------------------------------
51  1 file changed, 14 insertions(+), 36 deletions(-)
53 diff --git a/fs/ext4/file.c b/fs/ext4/file.c
54 index 831fd6beebf0..bbea2dccd584 100644
55 --- a/fs/ext4/file.c
56 +++ b/fs/ext4/file.c
57 @@ -484,47 +484,27 @@ static int ext4_find_unwritten_pgoff(struct inode *inode,
58                 num = min_t(pgoff_t, end - index, PAGEVEC_SIZE);
59                 nr_pages = pagevec_lookup(&pvec, inode->i_mapping, index,
60                                           (pgoff_t)num);
61 -               if (nr_pages == 0) {
62 -                       if (whence == SEEK_DATA)
63 -                               break;
65 -                       BUG_ON(whence != SEEK_HOLE);
66 -                       /*
67 -                        * If this is the first time to go into the loop and
68 -                        * offset is not beyond the end offset, it will be a
69 -                        * hole at this offset
70 -                        */
71 -                       if (lastoff == startoff || lastoff < endoff)
72 -                               found = 1;
73 -                       break;
74 -               }
76 -               /*
77 -                * If this is the first time to go into the loop and
78 -                * offset is smaller than the first page offset, it will be a
79 -                * hole at this offset.
80 -                */
81 -               if (lastoff == startoff && whence == SEEK_HOLE &&
82 -                   lastoff < page_offset(pvec.pages[0])) {
83 -                       found = 1;
84 +               if (nr_pages == 0)
85                         break;
86 -               }
88                 for (i = 0; i < nr_pages; i++) {
89                         struct page *page = pvec.pages[i];
90                         struct buffer_head *bh, *head;
92                         /*
93 -                        * If the current offset is not beyond the end of given
94 -                        * range, it will be a hole.
95 +                        * If current offset is smaller than the page offset,
96 +                        * there is a hole at this offset.
97                          */
98 -                       if (lastoff < endoff && whence == SEEK_HOLE &&
99 -                           page->index > end) {
100 +                       if (whence == SEEK_HOLE && lastoff < endoff &&
101 +                           lastoff < page_offset(pvec.pages[i])) {
102                                 found = 1;
103                                 *offset = lastoff;
104                                 goto out;
105                         }
107 +                       if (page->index > end)
108 +                               goto out;
110                         lock_page(page);
112                         if (unlikely(page->mapping != inode->i_mapping)) {
113 @@ -564,20 +544,18 @@ static int ext4_find_unwritten_pgoff(struct inode *inode,
114                         unlock_page(page);
115                 }
117 -               /*
118 -                * The no. of pages is less than our desired, that would be a
119 -                * hole in there.
120 -                */
121 -               if (nr_pages < num && whence == SEEK_HOLE) {
122 -                       found = 1;
123 -                       *offset = lastoff;
124 +               /* The no. of pages is less than our desired, we are done. */
125 +               if (nr_pages < num)
126                         break;
127 -               }
129                 index = pvec.pages[i - 1]->index + 1;
130                 pagevec_release(&pvec);
131         } while (index <= end);
133 +       if (whence == SEEK_HOLE && lastoff < endoff) {
134 +               found = 1;
135 +               *offset = lastoff;
136 +       }
137  out:
138         pagevec_release(&pvec);
139         return found;
140 -- 
141 2.12.0