123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205 |
- // SPDX-License-Identifier: GPL-2.0
- /*
- * Copyright (C) 2017 Red Hat, Inc.
- * Copyright (c) 2018 Christoph Hellwig.
- */
- #include <linux/module.h>
- #include <linux/compiler.h>
- #include <linux/fs.h>
- #include <linux/iomap.h>
- #include <linux/pagemap.h>
- #include <linux/pagevec.h>
- /*
- * Seek for SEEK_DATA / SEEK_HOLE within @page, starting at @lastoff.
- * Returns true if found and updates @lastoff to the offset in file.
- */
- static bool
- page_seek_hole_data(struct inode *inode, struct page *page, loff_t *lastoff,
- int whence)
- {
- const struct address_space_operations *ops = inode->i_mapping->a_ops;
- unsigned int bsize = i_blocksize(inode), off;
- bool seek_data = whence == SEEK_DATA;
- loff_t poff = page_offset(page);
- if (WARN_ON_ONCE(*lastoff >= poff + PAGE_SIZE))
- return false;
- if (*lastoff < poff) {
- /*
- * Last offset smaller than the start of the page means we found
- * a hole:
- */
- if (whence == SEEK_HOLE)
- return true;
- *lastoff = poff;
- }
- /*
- * Just check the page unless we can and should check block ranges:
- */
- if (bsize == PAGE_SIZE || !ops->is_partially_uptodate)
- return PageUptodate(page) == seek_data;
- lock_page(page);
- if (unlikely(page->mapping != inode->i_mapping))
- goto out_unlock_not_found;
- for (off = 0; off < PAGE_SIZE; off += bsize) {
- if (offset_in_page(*lastoff) >= off + bsize)
- continue;
- if (ops->is_partially_uptodate(page, off, bsize) == seek_data) {
- unlock_page(page);
- return true;
- }
- *lastoff = poff + off + bsize;
- }
- out_unlock_not_found:
- unlock_page(page);
- return false;
- }
- /*
- * 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: uptodate buffer heads count as data; everything else
- * counts as a hole.
- *
- * Returns the resulting offset on successs, and -ENOENT otherwise.
- */
- static 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 nr_pages, i;
- nr_pages = pagevec_lookup_range(&pvec, inode->i_mapping, &index,
- end - 1);
- if (nr_pages == 0)
- break;
- for (i = 0; i < nr_pages; i++) {
- struct page *page = pvec.pages[i];
- if (page_seek_hole_data(inode, page, &lastoff, whence))
- goto check_range;
- lastoff = page_offset(page) + PAGE_SIZE;
- }
- 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;
- }
- static loff_t
- iomap_seek_hole_actor(struct inode *inode, loff_t offset, loff_t length,
- void *data, struct iomap *iomap, struct iomap *srcmap)
- {
- switch (iomap->type) {
- case IOMAP_UNWRITTEN:
- offset = page_cache_seek_hole_data(inode, offset, length,
- SEEK_HOLE);
- if (offset < 0)
- return length;
- fallthrough;
- case IOMAP_HOLE:
- *(loff_t *)data = offset;
- return 0;
- default:
- return length;
- }
- }
- loff_t
- iomap_seek_hole(struct inode *inode, loff_t offset, const struct iomap_ops *ops)
- {
- loff_t size = i_size_read(inode);
- loff_t ret;
- /* Nothing to be found before or beyond the end of the file. */
- if (offset < 0 || offset >= size)
- return -ENXIO;
- while (offset < size) {
- ret = iomap_apply(inode, offset, size - offset, IOMAP_REPORT,
- ops, &offset, iomap_seek_hole_actor);
- if (ret < 0)
- return ret;
- if (ret == 0)
- break;
- offset += ret;
- }
- return offset;
- }
- EXPORT_SYMBOL_GPL(iomap_seek_hole);
- static loff_t
- iomap_seek_data_actor(struct inode *inode, loff_t offset, loff_t length,
- void *data, struct iomap *iomap, struct iomap *srcmap)
- {
- switch (iomap->type) {
- case IOMAP_HOLE:
- return length;
- case IOMAP_UNWRITTEN:
- offset = page_cache_seek_hole_data(inode, offset, length,
- SEEK_DATA);
- if (offset < 0)
- return length;
- fallthrough;
- default:
- *(loff_t *)data = offset;
- return 0;
- }
- }
- loff_t
- iomap_seek_data(struct inode *inode, loff_t offset, const struct iomap_ops *ops)
- {
- loff_t size = i_size_read(inode);
- loff_t ret;
- /* Nothing to be found before or beyond the end of the file. */
- if (offset < 0 || offset >= size)
- return -ENXIO;
- while (offset < size) {
- ret = iomap_apply(inode, offset, size - offset, IOMAP_REPORT,
- ops, &offset, iomap_seek_data_actor);
- if (ret < 0)
- return ret;
- if (ret == 0)
- return offset;
- offset += ret;
- }
- /* We've reached the end of the file without finding data */
- return -ENXIO;
- }
- EXPORT_SYMBOL_GPL(iomap_seek_data);
|