This is a helper function for filesystems which support file tails. Signed-off-by: Matthew Wilcox (Oracle) <willy@xxxxxxxxxxxxx> --- include/linux/pagemap.h | 1 + mm/filemap.c | 57 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 58 insertions(+) diff --git a/include/linux/pagemap.h b/include/linux/pagemap.h index c21b3ad1068c..618fe184c248 100644 --- a/include/linux/pagemap.h +++ b/include/linux/pagemap.h @@ -764,6 +764,7 @@ static inline struct page *grab_cache_page(struct address_space *mapping, return find_or_create_page(mapping, index, mapping_gfp_mask(mapping)); } +void folio_copy_tail(struct folio *, loff_t pos, void *src, size_t max); struct folio *read_cache_folio(struct address_space *, pgoff_t index, filler_t *filler, struct file *file); struct folio *mapping_read_folio_gfp(struct address_space *, pgoff_t index, diff --git a/mm/filemap.c b/mm/filemap.c index 40be33b5ee46..b02d9c390d3c 100644 --- a/mm/filemap.c +++ b/mm/filemap.c @@ -4145,3 +4145,60 @@ bool filemap_release_folio(struct folio *folio, gfp_t gfp) return try_to_free_buffers(folio); } EXPORT_SYMBOL(filemap_release_folio); + +/** + * folio_copy_tail - Copy an in-memory file tail into a page cache folio. + * @folio: The folio to copy into. + * @pos: The file position of the first byte of data in the tail. + * @src: The address of the tail data. + * @max: The size of the buffer used for the tail data. + * + * Supports file tails starting at @pos that are a maximum of @max + * bytes in size. Zeroes the remainder of the folio. + */ +void folio_copy_tail(struct folio *folio, loff_t pos, void *src, size_t max) +{ + loff_t isize = i_size_read(folio->mapping->host); + size_t offset, len = isize - pos; + char *dst; + + if (folio_pos(folio) > isize) { + len = 0; + } else if (folio_pos(folio) > pos) { + len -= folio_pos(folio) - pos; + src += folio_pos(folio) - pos; + max -= folio_pos(folio) - pos; + pos = folio_pos(folio); + } + /* + * i_size is larger than the number of bytes stored in the tail? + * Assume the remainder is zero-padded. + */ + if (WARN_ON_ONCE(len > max)) + len = max; + offset = offset_in_folio(folio, pos); + dst = kmap_local_folio(folio, offset); + if (folio_test_highmem(folio) && folio_test_large(folio)) { + size_t poff = offset_in_page(offset); + size_t plen = min(poff + len, PAGE_SIZE) - poff; + + for (;;) { + memcpy(dst, src, plen); + memset(dst + plen, 0, PAGE_SIZE - poff - plen); + offset += PAGE_SIZE - poff; + if (offset == folio_size(folio)) + break; + kunmap_local(dst); + dst = kmap_local_folio(folio, offset); + len -= plen; + poff = 0; + plen = min(len, PAGE_SIZE); + } + } else { + memcpy(dst, src, len); + memset(dst + len, 0, folio_size(folio) - len - offset); + } + kunmap_local(dst); + flush_dcache_folio(folio); +} +EXPORT_SYMBOL_GPL(folio_copy_tail); -- 2.39.1