Currently, SEEK_HOLE implementation in ext4 may both return that there'sa hole at some offset although that offset already has data and skipsome holes during a search for the next hole. The first problem isdemostrated by:

Where we can see that hole at offsets 56k..128k has been ignored by theSEEK_HOLE call.

The underlying problem is in the ext4_find_unwritten_pgoff() which isjust buggy. In some cases it fails to update returned offset when itfinds a hole (when no pages are found or when the first found page hashigher index than expected), in some cases conditions for detecting holeare just missing (we fail to detect a situation where indices ofreturned pages are not contiguous).

Fix ext4_find_unwritten_pgoff() to properly detect non-contiguous pageindices and also handle all cases where we got less pages then expectedin one place and handle it properly there.

--- a/fs/ext4/file.c+++ b/fs/ext4/file.c@@ -307,47 +307,27 @@ static int ext4_find_unwritten_pgoff(str num = min_t(pgoff_t, end - index, PAGEVEC_SIZE); nr_pages = pagevec_lookup(&pvec, inode->i_mapping, index, (pgoff_t)num);- if (nr_pages == 0) {- if (whence == SEEK_DATA)- break;-- BUG_ON(whence != SEEK_HOLE);- /*- * If this is the first time to go into the loop and- * offset is not beyond the end offset, it will be a- * hole at this offset- */- if (lastoff == startoff || lastoff < endoff)- found = 1;+ if (nr_pages == 0) break;- }-- /*- * If this is the first time to go into the loop and- * offset is smaller than the first page offset, it will be a- * hole at this offset.- */- if (lastoff == startoff && whence == SEEK_HOLE &&- lastoff < page_offset(pvec.pages[0])) {- found = 1;- break;- }

/*- * If the current offset is not beyond the end of given- * range, it will be a hole.+ * If current offset is smaller than the page offset,+ * there is a hole at this offset. */- if (lastoff < endoff && whence == SEEK_HOLE &&- page->index > end) {+ if (whence == SEEK_HOLE && lastoff < endoff &&+ lastoff < page_offset(pvec.pages[i])) { found = 1; *offset = lastoff; goto out; }

- /*- * The no. of pages is less than our desired, that would be a- * hole in there.- */- if (nr_pages < num && whence == SEEK_HOLE) {- found = 1;- *offset = lastoff;+ /* The no. of pages is less than our desired, we are done. */+ if (nr_pages < num) break;- }