diff options
Diffstat (limited to 'fs/buffer.c')
| -rw-r--r-- | fs/buffer.c | 124 | 
1 files changed, 124 insertions, 0 deletions
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, 0); + +	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;  | 
