Home Home > GIT Browse
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAnthony Iliopoulos <ailiopoulos@suse.com>2019-03-20 01:00:59 +0100
committerAnthony Iliopoulos <ailiopoulos@suse.com>2019-03-20 01:00:59 +0100
commit05d288553914536e889de886b778b87ae0d9998f (patch)
treedd9e266557e9b2ccb5a5468b1f16f5d20fad4eea
parentd08b330e6f38153cb9b325c89c4bc744df456018 (diff)
vfs: Add page_cache_seek_hole_data helper (bsc#1070995).
-rw-r--r--patches.fixes/vfs-Add-page_cache_seek_hole_data-helper.patch190
-rw-r--r--series.conf1
2 files changed, 191 insertions, 0 deletions
diff --git a/patches.fixes/vfs-Add-page_cache_seek_hole_data-helper.patch b/patches.fixes/vfs-Add-page_cache_seek_hole_data-helper.patch
new file mode 100644
index 0000000000..534151753a
--- /dev/null
+++ b/patches.fixes/vfs-Add-page_cache_seek_hole_data-helper.patch
@@ -0,0 +1,190 @@
+From 334fd34d76f237c0ee58dfc400d2c4e34d660544 Mon Sep 17 00:00:00 2001
+From: Andreas Gruenbacher <agruenba@redhat.com>
+Date: Thu, 29 Jun 2017 11:43:20 -0700
+Subject: [PATCH] vfs: Add page_cache_seek_hole_data helper
+Git-commit: 334fd34d76f237c0ee58dfc400d2c4e34d660544
+Patch-mainline: v4.13-rc1
+References: bsc#1070995
+
+Both ext4 and xfs implement seeking for the next hole or piece of data
+in unwritten extents by scanning the page cache, and both versions share
+the same bug when iterating the buffers of a page: the start offset into
+the page isn't taken into account, so when a page fits more than two
+filesystem blocks, things will go wrong. For example, on a filesystem
+with a block size of 1k, the following command will fail:
+
+ xfs_io -f -c "falloc 0 4k" \
+ -c "pwrite 1k 1k" \
+ -c "pwrite 3k 1k" \
+ -c "seek -a -r 0" foo
+
+In this example, neither lseek(fd, 1024, SEEK_HOLE) nor lseek(fd, 2048,
+SEEK_DATA) will return the correct result.
+
+Introduce a generic vfs helper for seeking in the page cache that gets
+this right. The next commits will replace the filesystem specific
+implementations.
+
+Signed-off-by: Andreas Gruenbacher <agruenba@redhat.com>
+[hch: dropped the export]
+Signed-off-by: Christoph Hellwig <hch@lst.de>
+Reviewed-by: Darrick J. Wong <darrick.wong@oracle.com>
+Signed-off-by: Darrick J. Wong <darrick.wong@oracle.com>
+Acked-by: Anthony Iliopoulos <ailiopoulos@suse.com>
+
+---
+ fs/buffer.c | 124 ++++++++++++++++++++++++++++++++++++++++++++
+ include/linux/buffer_head.h | 2 +
+ 2 files changed, 126 insertions(+)
+
+diff --git a/fs/buffer.c b/fs/buffer.c
+index 161be58c5cb0..b3674eb7c9c0 100644
+--- a/fs/buffer.c
++++ b/fs/buffer.c
+@@ -3492,6 +3492,130 @@ int bh_submit_read(struct buffer_head *bh)
+ }
+ EXPORT_SYMBOL(bh_submit_read);
+
++/*
++ * Seek for SEEK_DATA / SEEK_HOLE within @page, starting at @lastoff.
++ *
++ * Returns the offset within the file on success, and -ENOENT otherwise.
++ */
++static loff_t
++page_seek_hole_data(struct page *page, loff_t lastoff, int whence)
++{
++ loff_t offset = page_offset(page);
++ struct buffer_head *bh, *head;
++ bool seek_data = whence == SEEK_DATA;
++
++ if (lastoff < offset)
++ lastoff = offset;
++
++ bh = head = page_buffers(page);
++ do {
++ offset += bh->b_size;
++ if (lastoff >= offset)
++ continue;
++
++ /*
++ * Unwritten extents that have data in the page cache covering
++ * them can be identified by the BH_Unwritten state flag.
++ * Pages with multiple buffers might have a mix of holes, data
++ * and unwritten extents - any buffer with valid data in it
++ * should have BH_Uptodate flag set on it.
++ */
++
++ if ((buffer_unwritten(bh) || buffer_uptodate(bh)) == seek_data)
++ return lastoff;
++
++ lastoff = offset;
++ } while ((bh = bh->b_this_page) != head);
++ return -ENOENT;
++}
++
++/*
++ * Seek for SEEK_DATA / SEEK_HOLE in the page cache.
++ *
++ * Within unwritten extents, the page cache determines which parts are holes
++ * and which are data: unwritten and uptodate buffer heads count as data;
++ * everything else counts as a hole.
++ *
++ * Returns the resulting offset on successs, and -ENOENT otherwise.
++ */
++loff_t
++page_cache_seek_hole_data(struct inode *inode, loff_t offset, loff_t length,
++ int whence)
++{
++ pgoff_t index = offset >> PAGE_SHIFT;
++ pgoff_t end = DIV_ROUND_UP(offset + length, PAGE_SIZE);
++ loff_t lastoff = offset;
++ struct pagevec pvec;
++
++ if (length <= 0)
++ return -ENOENT;
++
++ pagevec_init(&pvec);
++
++ do {
++ unsigned want, nr_pages, i;
++
++ want = min_t(unsigned, end - index, PAGEVEC_SIZE);
++ nr_pages = pagevec_lookup(&pvec, inode->i_mapping, index, want);
++ if (nr_pages == 0)
++ break;
++
++ for (i = 0; i < nr_pages; i++) {
++ struct page *page = pvec.pages[i];
++
++ /*
++ * At this point, the page may be truncated or
++ * invalidated (changing page->mapping to NULL), or
++ * even swizzled back from swapper_space to tmpfs file
++ * mapping. However, page->index will not change
++ * because we have a reference on the page.
++ *
++ * If current page offset is beyond where we've ended,
++ * we've found a hole.
++ */
++ if (whence == SEEK_HOLE &&
++ lastoff < page_offset(page))
++ goto check_range;
++
++ /* Searching done if the page index is out of range. */
++ if (page->index >= end)
++ goto not_found;
++
++ lock_page(page);
++ if (likely(page->mapping == inode->i_mapping) &&
++ page_has_buffers(page)) {
++ lastoff = page_seek_hole_data(page, lastoff, whence);
++ if (lastoff >= 0) {
++ unlock_page(page);
++ goto check_range;
++ }
++ }
++ unlock_page(page);
++ lastoff = page_offset(page) + PAGE_SIZE;
++ }
++
++ /* Searching done if fewer pages returned than wanted. */
++ if (nr_pages < want)
++ break;
++
++ index = pvec.pages[i - 1]->index + 1;
++ pagevec_release(&pvec);
++ } while (index < end);
++
++ /* When no page at lastoff and we are not done, we found a hole. */
++ if (whence != SEEK_HOLE)
++ goto not_found;
++
++check_range:
++ if (lastoff < offset + length)
++ goto out;
++not_found:
++ lastoff = -ENOENT;
++out:
++ pagevec_release(&pvec);
++ return lastoff;
++}
++
+ void __init buffer_init(void)
+ {
+ unsigned long nrpages;
+diff --git a/include/linux/buffer_head.h b/include/linux/buffer_head.h
+index bd029e52ef5e..ad4e024ce17e 100644
+--- a/include/linux/buffer_head.h
++++ b/include/linux/buffer_head.h
+@@ -201,6 +201,8 @@ void write_boundary_block(struct block_device *bdev,
+ sector_t bblock, unsigned blocksize);
+ int bh_uptodate_or_lock(struct buffer_head *bh);
+ int bh_submit_read(struct buffer_head *bh);
++loff_t page_cache_seek_hole_data(struct inode *inode, loff_t offset,
++ loff_t length, int whence);
+
+ extern int buffer_heads_over_limit;
+
+--
+2.16.4
+
diff --git a/series.conf b/series.conf
index 1c00cecb24..9d2becc586 100644
--- a/series.conf
+++ b/series.conf
@@ -3559,6 +3559,7 @@
patches.fixes/xfs-remove-bli-from-AIL-before-release-on-transactio.patch
patches.fixes/xfs-remove-double-underscore-integer-types.patch
patches.fixes/xfs-check-if-an-inode-is-cached-and-allocated.patch
+ patches.fixes/vfs-Add-page_cache_seek_hole_data-helper.patch
patches.drivers/ipmi_ssif-unlock-on-allocation-failure
patches.drivers/0007-ipmi_ssif-remove-redundant-null-check-on-array-clien.patch
patches.drivers/0008-ipmi-Use-the-proper-default-value-for-register-size-.patch