Add a pair of helpers for use by a netfs to write data to the cache. Signed-off-by: David Howells <dhowells@xxxxxxxxxx> --- fs/cachefiles/content-map.c | 38 +++++++++++++ fs/cachefiles/interface.c | 1 fs/cachefiles/internal.h | 2 + fs/fscache/io.c | 128 +++++++++++++++++++++++++++++++++++++++++++ include/linux/fscache.h | 34 +++++++++++ 5 files changed, 203 insertions(+) diff --git a/fs/cachefiles/content-map.c b/fs/cachefiles/content-map.c index da0a81e3f751..d1e8a509a8cc 100644 --- a/fs/cachefiles/content-map.c +++ b/fs/cachefiles/content-map.c @@ -204,6 +204,44 @@ enum netfs_read_source cachefiles_prepare_read(struct netfs_read_subrequest *sub return NETFS_READ_FROM_CACHE; } +/* + * Prepare for a write to occur. + */ +int cachefiles_prepare_write(struct fscache_op_resources *opr, + loff_t *_start, size_t *_len, loff_t i_size) +{ + struct cachefiles_object *object = + container_of(opr->object, struct cachefiles_object, fscache); + loff_t start = *_start, map_limit; + size_t len = *_len, down; + long granule = start / CACHEFILES_GRAN_SIZE; + + if (start >= CACHEFILES_SIZE_LIMIT) + return -ENOBUFS; + + if (granule / 8 >= object->content_map_size) { + cachefiles_expand_content_map(object, i_size); + if (granule / 8 >= object->content_map_size) + return -ENOBUFS; + } + + map_limit = object->content_map_size * 8 * CACHEFILES_GRAN_SIZE; + if (start >= map_limit) + return -ENOBUFS; + if (len > map_limit - start) + len = map_limit - start; + + /* Assume that the preparation to write involved preloading any + * bits of the cache that weren't to be written and filling any + * gaps that didn't end up being written. + */ + + down = start - round_down(start, CACHEFILES_DIO_BLOCK_SIZE); + *_start = start - down; + *_len = round_up(down + len, CACHEFILES_DIO_BLOCK_SIZE); + return 0; +} + /* * Allocate a new content map. */ diff --git a/fs/cachefiles/interface.c b/fs/cachefiles/interface.c index 3609ff2fb491..a9725ca72ad5 100644 --- a/fs/cachefiles/interface.c +++ b/fs/cachefiles/interface.c @@ -609,6 +609,7 @@ static const struct fscache_op_ops cachefiles_io_ops = { .write = cachefiles_write, .expand_readahead = cachefiles_expand_readahead, .prepare_read = cachefiles_prepare_read, + .prepare_write = cachefiles_prepare_write, }; static void cachefiles_begin_operation(struct fscache_op_resources *opr) diff --git a/fs/cachefiles/internal.h b/fs/cachefiles/internal.h index e2d06c0860a2..38149868c331 100644 --- a/fs/cachefiles/internal.h +++ b/fs/cachefiles/internal.h @@ -137,6 +137,8 @@ extern void cachefiles_mark_content_map(struct cachefiles_object *object, loff_t start, loff_t len, unsigned int inval_counter); extern void cachefiles_expand_content_map(struct cachefiles_object *object, loff_t size); extern void cachefiles_shorten_content_map(struct cachefiles_object *object, loff_t new_size); +extern int cachefiles_prepare_write(struct fscache_op_resources *opr, + loff_t *_start, size_t *_len, loff_t i_size); extern bool cachefiles_load_content_map(struct cachefiles_object *object); extern void cachefiles_save_content_map(struct cachefiles_object *object); extern int cachefiles_display_object(struct seq_file *m, struct fscache_object *object); diff --git a/fs/fscache/io.c b/fs/fscache/io.c index 5401c9ed347b..295a89af4269 100644 --- a/fs/fscache/io.c +++ b/fs/fscache/io.c @@ -10,6 +10,7 @@ #include <linux/fscache-cache.h> #include <linux/slab.h> #include <linux/netfs.h> +#include <linux/uio.h> #include "internal.h" /* @@ -260,3 +261,130 @@ void __fscache_resize_cookie(struct fscache_cookie *cookie, loff_t new_size) } } EXPORT_SYMBOL(__fscache_resize_cookie); + +struct fscache_write_request { + struct fscache_op_resources cache_resources; + struct address_space *mapping; + loff_t start; + size_t len; + fscache_io_terminated_t term_func; + void *term_func_priv; +}; + +/** + * fscache_clear_page_bits - Clear the PG_fscache bits from a set of pages + * @mapping: The netfs inode to use as the source + * @start: The start position in @mapping + * @len: The amount of data to unlock + * + * Clear the PG_fscache flag from a sequence of pages and wake up anyone who's + * waiting. + */ +void __fscache_clear_page_bits(struct address_space *mapping, + loff_t start, size_t len) +{ + pgoff_t first = start / PAGE_SIZE; + pgoff_t last = (start + len - 1) / PAGE_SIZE; + struct page *page; + + if (len) { + XA_STATE(xas, &mapping->i_pages, first); + + rcu_read_lock(); + xas_for_each(&xas, page, last) { + unlock_page_fscache(page); + } + rcu_read_unlock(); + } +} +EXPORT_SYMBOL(__fscache_clear_page_bits); + +/* + * Deal with the completion of writing the data to the cache. + */ +static void fscache_wreq_done(void *priv, ssize_t transferred_or_error) +{ + struct fscache_write_request *wreq = priv; + + fscache_clear_page_bits(wreq->mapping, wreq->start, wreq->len); + + if (wreq->term_func) + wreq->term_func(wreq->term_func_priv, transferred_or_error); + fscache_end_operation(&wreq->cache_resources); + kfree(wreq); +} + +/** + * fscache_write_to_cache - Save a write to the cache and clear PG_fscache + * @cookie: The cookie representing the cache object + * @mapping: The netfs inode to use as the source + * @start: The start position in @mapping + * @len: The amount of data to write back + * @i_size: The new size of the inode + * @term_func: The function to call upon completion + * @term_func_priv: The private data for @term_func + * + * Helper function for a netfs to write dirty data from an inode into the cache + * object that's backing it. + * + * @start and @len describe the range of the data. This does not need to be + * page-aligned, but to satisfy DIO requirements, the cache may expand it up to + * the page boundaries on either end. All the pages covering the range must be + * marked with PG_fscache. + * + * If given, @term_func will be called upon completion and supplied with + * @term_func_priv. Note that the PG_fscache flags will have been cleared by + * this point, so the netfs must retain its own pin on the mapping. + */ +void __fscache_write_to_cache(struct fscache_cookie *cookie, + struct address_space *mapping, + loff_t start, size_t len, loff_t i_size, + fscache_io_terminated_t term_func, + void *term_func_priv) +{ + struct fscache_write_request *wreq; + struct fscache_op_resources *opr; + struct iov_iter iter; + int ret = -ENOBUFS; + + if (!fscache_cookie_valid(cookie) || len == 0) + goto abandon; + + _enter("%llx,%zx", start, len); + + wreq = kzalloc(sizeof(struct fscache_write_request), GFP_NOFS); + if (!wreq) + goto abandon; + wreq->mapping = mapping; + wreq->start = start; + wreq->len = len; + wreq->term_func = term_func; + wreq->term_func_priv = term_func_priv; + + opr = &wreq->cache_resources; + if (fscache_begin_operation(cookie, opr, FSCACHE_WANT_WRITE) < 0) + goto abandon_free; + + ret = opr->ops->prepare_write(opr, &start, &len, i_size); + if (ret < 0) + goto abandon_end; + + /* TODO: Consider clearing page bits now for space the write isn't + * covering. This is more complicated than it appears when THPs are + * taken into account. + */ + + iov_iter_xarray(&iter, WRITE, &mapping->i_pages, start, len); + fscache_write(opr, start, &iter, fscache_wreq_done, wreq); + return; + +abandon_end: + return fscache_wreq_done(wreq, ret); +abandon_free: + kfree(wreq); +abandon: + fscache_clear_page_bits(mapping, start, len); + if (term_func) + term_func(term_func_priv, ret); +} +EXPORT_SYMBOL(__fscache_write_to_cache); diff --git a/include/linux/fscache.h b/include/linux/fscache.h index 1c1ea3558421..0613ccea88c1 100644 --- a/include/linux/fscache.h +++ b/include/linux/fscache.h @@ -192,6 +192,12 @@ struct fscache_op_ops { */ enum netfs_read_source (*prepare_read)(struct netfs_read_subrequest *subreq, loff_t i_size); + + /* Prepare a write operation, working out what part of the write we can + * actually do. + */ + int (*prepare_write)(struct fscache_op_resources *opr, + loff_t *_start, size_t *_len, loff_t i_size); }; /* @@ -226,6 +232,10 @@ extern void __fscache_invalidate(struct fscache_cookie *, const void *, loff_t, extern void fscache_put_super(struct super_block *, struct fscache_cookie *(*get_cookie)(struct inode *)); +extern void __fscache_write_to_cache(struct fscache_cookie *, struct address_space *, + loff_t, size_t, loff_t, fscache_io_terminated_t, void *); +extern void __fscache_clear_page_bits(struct address_space *, loff_t, size_t); + /** * fscache_register_netfs - Register a filesystem as desiring caching services * @netfs: The description of the filesystem @@ -627,6 +637,30 @@ int fscache_write(struct fscache_op_resources *opr, return ops->write(opr, start_pos, iter, term_func, term_func_priv); } +static inline void fscache_clear_page_bits(struct address_space *mapping, + loff_t start, size_t len) +{ + if (fscache_available()) + __fscache_clear_page_bits(mapping, start, len); +} + +static inline void fscache_write_to_cache(struct fscache_cookie *cookie, + struct address_space *mapping, + loff_t start, size_t len, loff_t i_size, + fscache_io_terminated_t term_func, + void *term_func_priv) +{ + if (fscache_available()) { + __fscache_write_to_cache(cookie, mapping, start, len, i_size, + term_func, term_func_priv); + } else { + fscache_clear_page_bits(mapping, start, len); + if (term_func) + term_func(term_func_priv, -ENOBUFS); + } + +} + #if __fscache_available extern int fscache_set_page_dirty(struct page *page, struct fscache_cookie *cookie); #else