[RFC PATCH 42/76] cachefiles: Shape requests from the fscache read helper
From: David Howells
Date: Fri Nov 20 2020 - 10:14:42 EST
Implement the function that shapes read requests to map onto the granules
in a cache file.
When preparing to fetch data from the server to be cached, the request will
be expanded to align with granule size and cut down so that it doesn't
cross the boundary between a non-present granule and a present granule.
When preparing to read data from the cache, the extent will be cut down so
that it doesn't cross the boundary between a present granule and a
non-present granule.
If no caching is taking place, whatever was requested goes.
Signed-off-by: David Howells <dhowells@xxxxxxxxxx>
---
fs/cachefiles/content-map.c | 164 +++++++++++++++++++++++++++++++++++++++++++
fs/cachefiles/interface.c | 2 +
fs/cachefiles/internal.h | 4 +
3 files changed, 170 insertions(+)
diff --git a/fs/cachefiles/content-map.c b/fs/cachefiles/content-map.c
index 68fcab313361..4ff8c645817c 100644
--- a/fs/cachefiles/content-map.c
+++ b/fs/cachefiles/content-map.c
@@ -10,6 +10,7 @@
#include <linux/file.h>
#include <linux/swap.h>
#include <linux/xattr.h>
+#include <linux/netfs.h>
#include "internal.h"
static const char cachefiles_xattr_content_map[] =
@@ -40,6 +41,169 @@ static size_t cachefiles_map_size(loff_t i_size)
return map_size;
}
+static bool cachefiles_granule_is_present(struct cachefiles_object *object,
+ long granule)
+{
+ bool res;
+
+ if (granule / 8 >= object->content_map_size)
+ return false;
+ read_lock_bh(&object->content_map_lock);
+ res = test_bit_le(granule, object->content_map);
+ read_unlock_bh(&object->content_map_lock);
+ return res;
+}
+
+static long cachefiles_find_next_granule(struct cachefiles_object *object,
+ long start_from, long *_limit)
+{
+ long result, limit;
+
+ read_lock_bh(&object->content_map_lock);
+ *_limit = limit = object->content_map_size * 8;
+ result = find_next_bit_le(object->content_map, limit, start_from);
+ read_unlock_bh(&object->content_map_lock);
+ return result;
+}
+
+static long cachefiles_find_next_hole(struct cachefiles_object *object,
+ long start_from, long *_limit)
+{
+ long result, limit;
+
+ read_lock_bh(&object->content_map_lock);
+ *_limit = limit = object->content_map_size * 8;
+ result = find_next_zero_bit_le(object->content_map, limit, start_from);
+ read_unlock_bh(&object->content_map_lock);
+ return result;
+}
+
+/*
+ * Expand a readahead proposal from the VM to align with cache limits
+ * and granularity.
+ */
+void cachefiles_expand_readahead(struct fscache_op_resources *opr,
+ loff_t *_start, size_t *_len, loff_t i_size)
+{
+ loff_t start = *_start, delta;
+ size_t len = *_len;
+
+ if (start >= CACHEFILES_SIZE_LIMIT)
+ return;
+
+ if (len > CACHEFILES_SIZE_LIMIT - start)
+ len = *_len = CACHEFILES_SIZE_LIMIT - start;
+
+ delta = start & (CACHEFILES_GRAN_SIZE - 1);
+ if (start - delta < i_size) {
+ start -= delta;
+ len = round_up(len + delta, CACHEFILES_GRAN_SIZE);
+ if (len > i_size - start) {
+ _debug("overshot eof");
+ len = i_size - start;
+ }
+ }
+
+ *_start = start;
+ *_len = len;
+}
+
+/*
+ * Prepare a I/O subrequest of a read request. We're asked to retrieve all the
+ * remaining data in the read request, but we are allowed to shrink that and we
+ * set flags to indicate where we want it read from.
+ */
+enum netfs_read_source cachefiles_prepare_read(struct netfs_read_subrequest *subreq,
+ loff_t i_size)
+{
+ struct cachefiles_object *object =
+ container_of(subreq->rreq->cache_resources.object,
+ struct cachefiles_object, fscache);
+ loff_t start = subreq->start, len = subreq->len, boundary;
+ long granule, next, limit;
+
+ _enter("%llx,%llx", start, len);
+
+ if (start >= CACHEFILES_SIZE_LIMIT) {
+ if (start >= i_size)
+ goto zero_pages_nocache;
+ goto on_server_nocache;
+ }
+ if (len > CACHEFILES_SIZE_LIMIT - start)
+ len = CACHEFILES_SIZE_LIMIT - start;
+
+ granule = start / CACHEFILES_GRAN_SIZE;
+ if (granule / 8 >= object->content_map_size) {
+ cachefiles_expand_content_map(object, i_size);
+ if (granule / 8 >= object->content_map_size)
+ goto maybe_on_server_nocache;
+ }
+
+ if (start >= i_size)
+ goto zero_pages;
+
+ if (cachefiles_granule_is_present(object, granule)) {
+ /* The start of the request is present in the cache - restrict
+ * the length to what's available.
+ */
+ if (start & (CACHEFILES_DIO_BLOCK_SIZE - 1)) {
+ /* We should never see DIO-unaligned requests here. */
+ WARN_ON_ONCE(1);
+ len &= CACHEFILES_DIO_BLOCK_SIZE - 1;
+ goto maybe_on_server;
+ }
+
+ next = cachefiles_find_next_hole(object, granule + 1, &limit);
+ _debug("present %lx %lx", granule, limit);
+ if (granule >= limit)
+ goto maybe_on_server;
+ boundary = next * CACHEFILES_GRAN_SIZE;
+ if (len > boundary - start)
+ len = boundary - start;
+ goto in_cache;
+ } else {
+ /* The start of the request is not present in the cache -
+ * restrict the length to the size of the hole.
+ */
+ next = cachefiles_find_next_granule(object, granule + 1, &limit);
+ _debug("hole %lx %lx", granule, limit);
+ if (granule >= limit)
+ goto maybe_on_server;
+ boundary = next * CACHEFILES_GRAN_SIZE;
+ if (len > boundary - start)
+ len = boundary - start;
+ goto maybe_on_server;
+ }
+
+maybe_on_server:
+ /* If the start of the request is beyond the original EOF of the file
+ * on the server then it's not going to be found on the server.
+ */
+ if (start >= object->fscache.cookie->zero_point)
+ goto zero_pages;
+ goto on_server;
+maybe_on_server_nocache:
+ if (start >= object->fscache.cookie->zero_point)
+ goto zero_pages_nocache;
+ goto on_server_nocache;
+on_server:
+ __set_bit(NETFS_SREQ_WRITE_TO_CACHE, &subreq->flags);
+on_server_nocache:
+ subreq->len = len;
+ _leave(" = down %llx", len);
+ return NETFS_DOWNLOAD_FROM_SERVER;
+zero_pages:
+ __set_bit(NETFS_SREQ_WRITE_TO_CACHE, &subreq->flags);
+zero_pages_nocache:
+ subreq->len = len;
+ _leave(" = zero %llx", len);
+ return NETFS_FILL_WITH_ZEROES;
+in_cache:
+ subreq->len = len;
+ _leave(" = read %llx", len);
+ return NETFS_READ_FROM_CACHE;
+}
+
/*
* Mark the content map to indicate stored granule.
*/
diff --git a/fs/cachefiles/interface.c b/fs/cachefiles/interface.c
index 1d06cf72eeb0..a20917cb4667 100644
--- a/fs/cachefiles/interface.c
+++ b/fs/cachefiles/interface.c
@@ -462,6 +462,8 @@ static const struct fscache_op_ops cachefiles_io_ops = {
.end_operation = __fscache_end_operation,
.read = cachefiles_read,
.write = cachefiles_write,
+ .expand_readahead = cachefiles_expand_readahead,
+ .prepare_read = cachefiles_prepare_read,
};
static void cachefiles_begin_operation(struct fscache_op_resources *opr)
diff --git a/fs/cachefiles/internal.h b/fs/cachefiles/internal.h
index e065550a4bc0..e9f45d5053b1 100644
--- a/fs/cachefiles/internal.h
+++ b/fs/cachefiles/internal.h
@@ -125,6 +125,10 @@ extern void cachefiles_daemon_unbind(struct cachefiles_cache *cache);
/*
* content-map.c
*/
+extern void cachefiles_expand_readahead(struct fscache_op_resources *opr,
+ loff_t *_start, size_t *_len, loff_t i_size);
+extern enum netfs_read_source cachefiles_prepare_read(struct netfs_read_subrequest *subreq,
+ loff_t i_size);
extern void cachefiles_mark_content_map(struct cachefiles_object *object,
loff_t start, loff_t len);
extern void cachefiles_expand_content_map(struct cachefiles_object *object, loff_t size);