netfs, fscache: Add cache write preparation
diff --git a/fs/afs/file.c b/fs/afs/file.c
index 58363d1..6e7f8a6 100644
--- a/fs/afs/file.c
+++ b/fs/afs/file.c
@@ -367,7 +367,13 @@ static void afs_free_dirty_region(struct netfs_dirty_region *region)
 
 static void afs_init_wreq(struct netfs_write_request *wreq)
 {
+	struct afs_vnode *vnode = AFS_FS_I(wreq->inode);
+
 	//wreq->netfs_priv = key_get(afs_file_key(file));
+	if (test_bit(NETFS_WREQ_WRITE_TO_CACHE, &wreq->flags) &&
+	    fscache_prepare_write_operation(wreq, afs_vnode_cache(vnode)) < 0)
+		__clear_bit(NETFS_WREQ_WRITE_TO_CACHE, &wreq->flags);
+
 }
 
 static void afs_update_i_size(struct file *file, loff_t new_i_size)
diff --git a/fs/cachefiles/interface.c b/fs/cachefiles/interface.c
index da3948fd..fb609ef 100644
--- a/fs/cachefiles/interface.c
+++ b/fs/cachefiles/interface.c
@@ -569,4 +569,5 @@ const struct fscache_cache_ops cachefiles_cache_ops = {
 	.dissociate_pages	= cachefiles_dissociate_pages,
 	.check_consistency	= cachefiles_check_consistency,
 	.begin_read_operation	= cachefiles_begin_read_operation,
+	.prepare_write_operation = cachefiles_prepare_write_operation,
 };
diff --git a/fs/cachefiles/internal.h b/fs/cachefiles/internal.h
index 4ed83aa..9350282 100644
--- a/fs/cachefiles/internal.h
+++ b/fs/cachefiles/internal.h
@@ -154,6 +154,14 @@ void cachefiles_put_object(struct fscache_object *_object,
 			   enum fscache_obj_ref_trace why);
 
 /*
+ * io.c
+ */
+extern int cachefiles_begin_read_operation(struct netfs_read_request *,
+					   struct fscache_retrieval *);
+extern int cachefiles_prepare_write_operation(struct netfs_write_request *wreq,
+					      struct fscache_operation *op);
+
+/*
  * key.c
  */
 extern char *cachefiles_cook_key(const u8 *raw, int keylen, uint8_t type);
@@ -221,12 +229,6 @@ extern int cachefiles_write_page(struct fscache_storage *, struct page *);
 extern void cachefiles_uncache_page(struct fscache_object *, struct page *);
 
 /*
- * rdwr2.c
- */
-extern int cachefiles_begin_read_operation(struct netfs_read_request *,
-					   struct fscache_retrieval *);
-
-/*
  * security.c
  */
 extern int cachefiles_get_security_ID(struct cachefiles_cache *cache);
diff --git a/fs/cachefiles/io.c b/fs/cachefiles/io.c
index ca68bb9..e350660 100644
--- a/fs/cachefiles/io.c
+++ b/fs/cachefiles/io.c
@@ -351,7 +351,7 @@ static int cachefiles_prepare_write(struct netfs_cache_resources *cres,
  */
 static void cachefiles_end_operation(struct netfs_cache_resources *cres)
 {
-	struct fscache_retrieval *op = cres->cache_priv;
+	struct fscache_operation *op = cres->cache_priv;
 	struct file *file = cres->cache_priv2;
 
 	_enter("");
@@ -359,8 +359,8 @@ static void cachefiles_end_operation(struct netfs_cache_resources *cres)
 	if (file)
 		fput(file);
 	if (op) {
-		fscache_op_complete(&op->op, false);
-		fscache_put_retrieval(op);
+		fscache_op_complete(op, false);
+		fscache_put_operation(op);
 	}
 
 	_leave("");
@@ -407,7 +407,7 @@ int cachefiles_begin_read_operation(struct netfs_read_request *rreq,
 	}
 
 	fscache_get_retrieval(op);
-	rreq->cache_resources.cache_priv = op;
+	rreq->cache_resources.cache_priv = &op->op;
 	rreq->cache_resources.cache_priv2 = file;
 	rreq->cache_resources.ops = &cachefiles_netfs_cache_ops;
 	rreq->cache_resources.debug_id = object->fscache.debug_id;
@@ -418,3 +418,47 @@ int cachefiles_begin_read_operation(struct netfs_read_request *rreq,
 	fput(file);
 	return -EIO;
 }
+
+/*
+ * Open the cache file when preparing a cache write operation.
+ */
+int cachefiles_prepare_write_operation(struct netfs_write_request *wreq,
+				       struct fscache_operation *op)
+{
+	struct cachefiles_object *object;
+	struct cachefiles_cache *cache;
+	struct path path;
+	struct file *file;
+
+	_enter("");
+
+	object = container_of(op->object, struct cachefiles_object, fscache);
+	cache = container_of(object->fscache.cache,
+			     struct cachefiles_cache, cache);
+
+	path.mnt = cache->mnt;
+	path.dentry = object->backer;
+	file = open_with_fake_path(&path, O_RDWR | O_LARGEFILE | O_DIRECT,
+				   d_inode(object->backer), cache->cache_cred);
+	if (IS_ERR(file))
+		return PTR_ERR(file);
+	if (!S_ISREG(file_inode(file)->i_mode))
+		goto error_file;
+	if (unlikely(!file->f_op->read_iter) ||
+	    unlikely(!file->f_op->write_iter)) {
+		pr_notice("Cache does not support read_iter and write_iter\n");
+		goto error_file;
+	}
+
+	atomic_inc(&op->usage);
+	wreq->cache_resources.cache_priv = op;
+	wreq->cache_resources.cache_priv2 = file;
+	wreq->cache_resources.ops = &cachefiles_netfs_cache_ops;
+	wreq->cache_resources.debug_id = object->fscache.debug_id;
+	_leave("");
+	return 0;
+
+error_file:
+	fput(file);
+	return -EIO;
+}
diff --git a/fs/fscache/internal.h b/fs/fscache/internal.h
index c483863..269f733 100644
--- a/fs/fscache/internal.h
+++ b/fs/fscache/internal.h
@@ -206,6 +206,8 @@ extern atomic_t fscache_n_stores_ok;
 extern atomic_t fscache_n_stores_again;
 extern atomic_t fscache_n_stores_nobufs;
 extern atomic_t fscache_n_stores_oom;
+extern atomic_t fscache_n_stores_object_dead;
+extern atomic_t fscache_n_stores_op_waits;
 extern atomic_t fscache_n_store_ops;
 extern atomic_t fscache_n_store_calls;
 extern atomic_t fscache_n_store_pages;
diff --git a/fs/fscache/io.c b/fs/fscache/io.c
index 8ecc114..6b019ad 100644
--- a/fs/fscache/io.c
+++ b/fs/fscache/io.c
@@ -114,3 +114,102 @@ int __fscache_begin_read_operation(struct netfs_read_request *rreq,
 	return -ENOBUFS;
 }
 EXPORT_SYMBOL(__fscache_begin_read_operation);
+
+/*
+ * Prepare for a cache write operation.
+ * - we return:
+ *   -ENOMEM	- Out of memory
+ *   -ERESTARTSYS - Interrupted
+ *   -ENOBUFS	- No backing object or space available in which to cache any
+ *                pages not being written
+ *   -ENODATA	- No data available in the backing object for some or all of
+ *                the pages
+ *   0		- Prepared a write on all pages
+ */
+int __fscache_prepare_write_operation(struct netfs_write_request *rreq,
+				      struct fscache_cookie *cookie)
+{
+	struct fscache_operation *op;
+	struct fscache_object *object;
+	bool wake_cookie = false;
+	int ret;
+
+	_enter("rr=%08x", rreq->debug_id);
+
+	fscache_stat(&fscache_n_stores);
+
+	if (hlist_empty(&cookie->backing_objects))
+		goto nobufs;
+
+	if (test_bit(FSCACHE_COOKIE_INVALIDATING, &cookie->flags)) {
+		_leave(" = -ENOBUFS [invalidating]");
+		return -ENOBUFS;
+	}
+
+	ASSERTCMP(cookie->def->type, !=, FSCACHE_COOKIE_TYPE_INDEX);
+
+	if (fscache_wait_for_deferred_lookup(cookie) < 0)
+		return -ERESTARTSYS;
+
+	op = kzalloc(sizeof(*op), GFP_NOIO | __GFP_NOMEMALLOC | __GFP_NORETRY);
+	if (!op)
+		return -ENOMEM;
+
+	fscache_operation_init(cookie, op, NULL, NULL, NULL);
+	op->flags = FSCACHE_OP_ASYNC |
+		(1 << FSCACHE_OP_WAITING) |
+		(1 << FSCACHE_OP_UNUSE_COOKIE);
+
+	spin_lock(&cookie->lock);
+
+	if (!fscache_cookie_enabled(cookie) ||
+	    hlist_empty(&cookie->backing_objects))
+		goto nobufs_unlock;
+	object = hlist_entry(cookie->backing_objects.first,
+			     struct fscache_object, cookie_link);
+
+	__fscache_use_cookie(cookie);
+
+	if (fscache_submit_op(object, op) < 0)
+		goto nobufs_unlock_dec;
+	spin_unlock(&cookie->lock);
+
+	fscache_stat(&fscache_n_retrieval_ops);
+
+	/* we wait for the operation to become active, and then process it
+	 * *here*, in this thwrite, and not in the thwrite pool */
+	ret = fscache_wait_for_operation_activation(
+		object, op,
+		__fscache_stat(&fscache_n_stores_op_waits),
+		__fscache_stat(&fscache_n_stores_object_dead));
+	if (ret < 0)
+		goto error;
+
+	/* ask the cache to honour the operation */
+	ret = object->cache->ops->prepare_write_operation(rreq, op);
+
+error:
+	if (ret == -ENOMEM)
+		fscache_stat(&fscache_n_stores_oom);
+	else if (ret < 0)
+		fscache_stat(&fscache_n_stores_nobufs);
+	else
+		fscache_stat(&fscache_n_stores_ok);
+
+	fscache_put_operation(op);
+	_leave(" = %d", ret);
+	return ret;
+
+nobufs_unlock_dec:
+	wake_cookie = __fscache_unuse_cookie(cookie);
+nobufs_unlock:
+	spin_unlock(&cookie->lock);
+	fscache_put_operation(op);
+	if (wake_cookie)
+		__fscache_wake_unused_cookie(cookie);
+nobufs:
+	fscache_stat(&fscache_n_stores_nobufs);
+	_leave(" = -ENOBUFS");
+	return -ENOBUFS;
+}
+EXPORT_SYMBOL(__fscache_prepare_write_operation);
diff --git a/fs/fscache/stats.c b/fs/fscache/stats.c
index a7c3ed8..264826d 100644
--- a/fs/fscache/stats.c
+++ b/fs/fscache/stats.c
@@ -55,6 +55,8 @@ atomic_t fscache_n_stores_ok;
 atomic_t fscache_n_stores_again;
 atomic_t fscache_n_stores_nobufs;
 atomic_t fscache_n_stores_oom;
+atomic_t fscache_n_stores_object_dead;
+atomic_t fscache_n_stores_op_waits;
 atomic_t fscache_n_store_ops;
 atomic_t fscache_n_store_calls;
 atomic_t fscache_n_store_pages;
@@ -233,6 +235,9 @@ int fscache_stats_show(struct seq_file *m, void *v)
 		   atomic_read(&fscache_n_store_pages),
 		   atomic_read(&fscache_n_store_radix_deletes),
 		   atomic_read(&fscache_n_store_pages_over_limit));
+	seq_printf(m, "Stores : owt=%u abt=%u\n",
+		   atomic_read(&fscache_n_stores_op_waits),
+		   atomic_read(&fscache_n_stores_object_dead));
 
 	seq_printf(m, "VmScan : nos=%u gon=%u bsy=%u can=%u wt=%u\n",
 		   atomic_read(&fscache_n_store_vmscan_not_storing),
diff --git a/fs/netfs/objects.c b/fs/netfs/objects.c
index 6f44a67..1407bb0 100644
--- a/fs/netfs/objects.c
+++ b/fs/netfs/objects.c
@@ -177,8 +177,10 @@ struct netfs_write_request *netfs_alloc_write_request(struct address_space *mapp
 		wreq->inode	= inode;
 		wreq->netfs_ops	= ctx->ops;
 		wreq->debug_id	= atomic_inc_return(&debug_ids);
-		if (cached)
+		if (cached) {
+			kdebug("cached W=%08x", wreq->debug_id);
 			__set_bit(NETFS_WREQ_WRITE_TO_CACHE, &wreq->flags);
+		}
 		xa_init(&wreq->buffer);
 		INIT_WORK(&wreq->work, netfs_writeback_worker);
 		INIT_LIST_HEAD(&wreq->proc_link);
diff --git a/fs/netfs/write_back.c b/fs/netfs/write_back.c
index 9f1c759..0ebe9ae 100644
--- a/fs/netfs/write_back.c
+++ b/fs/netfs/write_back.c
@@ -218,6 +218,11 @@ static void netfs_set_up_write_to_cache(struct netfs_write_request *wreq)
 {
 	struct netfs_write_operation *op;
 
+	if (!wreq->cache_resources.ops) {
+		clear_bit(NETFS_WREQ_WRITE_TO_CACHE, &wreq->flags);
+		return;
+	}
+
 	op = netfs_create_write_operation(wreq, NETFS_WRITE_TO_CACHE,
 					  wreq->coverage.start,
 					  wreq->coverage.end - wreq->coverage.start,
@@ -246,10 +251,9 @@ static void netfs_writeback(struct netfs_write_request *wreq)
 	    !netfs_wreq_encrypt(wreq))
 		goto out;
 
-	if (wreq->cache_resources.ops &&
-	    test_bit(NETFS_WREQ_WRITE_TO_CACHE, &wreq->flags))
+	if (test_bit(NETFS_WREQ_WRITE_TO_CACHE, &wreq->flags))
 		netfs_set_up_write_to_cache(wreq);
-	//if (wreq->region->type != NETFS_REGION_CACHE_COPY)
+	if (test_bit(NETFS_WREQ_WRITE_TO_SERVER, &wreq->flags))
 		ctx->ops->create_write_operations(wreq);
 
 out:
@@ -577,6 +581,8 @@ static long netfs_flush_dirty(struct netfs_i_context *ctx,
 		netfs_get_dirty_region(ctx, r, netfs_region_trace_get_wreq);
 		netfs_deduct_write_credit(r, r->dirty.end - r->dirty.start);
 		list_move_tail(&r->flush_link, &wreq->regions);
+		if (r->type != NETFS_REGION_CACHE_COPY)
+			__set_bit(NETFS_WREQ_WRITE_TO_SERVER, &wreq->flags);
 		if (r == tail)
 			break;
 	}
diff --git a/include/linux/fscache-cache.h b/include/linux/fscache-cache.h
index 3235ddb..d2273eb 100644
--- a/include/linux/fscache-cache.h
+++ b/include/linux/fscache-cache.h
@@ -308,6 +308,10 @@ struct fscache_cache_ops {
 	/* Begin a read operation for the netfs lib */
 	int (*begin_read_operation)(struct netfs_read_request *rreq,
 				    struct fscache_retrieval *op);
+
+	/* Prepare a write operation for the netfs lib */
+	int (*prepare_write_operation)(struct netfs_write_request *wreq,
+				       struct fscache_operation *op);
 };
 
 extern struct fscache_cookie fscache_fsdef_index;
diff --git a/include/linux/fscache.h b/include/linux/fscache.h
index ba58c42..0085dd3 100644
--- a/include/linux/fscache.h
+++ b/include/linux/fscache.h
@@ -196,6 +196,8 @@ extern void __fscache_wait_on_invalidate(struct fscache_cookie *);
 
 #ifdef FSCACHE_USE_NEW_IO_API
 extern int __fscache_begin_read_operation(struct netfs_read_request *, struct fscache_cookie *);
+extern int __fscache_prepare_write_operation(struct netfs_write_request *,
+					     struct fscache_cookie *);
 #else
 extern int __fscache_read_or_alloc_page(struct fscache_cookie *,
 					struct page *,
@@ -534,6 +536,32 @@ int fscache_begin_read_operation(struct netfs_read_request *rreq,
 	return -ENOBUFS;
 }
 
+/**
+ * fscache_prepare_write_operation - Prepare a write operation for the netfs lib
+ * @wreq: The write request being undertaken
+ * @cookie: The cookie representing the cache object
+ *
+ * Prepare a write operation on behalf of the netfs helper library.  @wreq
+ * indicates the write request to which the operation state should be attached;
+ * @cookie indicates the cache object that will be accessed.
+ *
+ * This is intended to be called from the ->init_wreq() netfs lib operation as
+ * implemented by the network filesystem.
+ *
+ * Returns:
+ * * 0		- Success
+ * * -ENOBUFS	- No caching available
+ * * Other error code from the cache, such as -ENOMEM.
+ */
+static inline
+int fscache_prepare_write_operation(struct netfs_write_request *wreq,
+				    struct fscache_cookie *cookie)
+{
+	if (fscache_cookie_valid(cookie) && fscache_cookie_enabled(cookie))
+		return __fscache_prepare_write_operation(wreq, cookie);
+	return -ENOBUFS;
+}
+
 #else /* FSCACHE_USE_NEW_IO_API */
 
 /**
diff --git a/include/linux/netfs.h b/include/linux/netfs.h
index 33dd886..b551ed3 100644
--- a/include/linux/netfs.h
+++ b/include/linux/netfs.h
@@ -352,6 +352,7 @@ struct netfs_write_request {
 	unsigned long		flags;
 #define NETFS_WREQ_WRITE_TO_CACHE	0	/* Need to write to the cache */
 #define NETFS_WREQ_BUFFERED		1	/* Data is held in ->buffer */
+#define NETFS_WREQ_WRITE_TO_SERVER	2	/* Need to write to the server */
 	const struct netfs_request_ops *netfs_ops;
 };