162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later
262306a36Sopenharmony_ci/* Cache data I/O routines
362306a36Sopenharmony_ci *
462306a36Sopenharmony_ci * Copyright (C) 2021 Red Hat, Inc. All Rights Reserved.
562306a36Sopenharmony_ci * Written by David Howells (dhowells@redhat.com)
662306a36Sopenharmony_ci */
762306a36Sopenharmony_ci#define FSCACHE_DEBUG_LEVEL OPERATION
862306a36Sopenharmony_ci#include <linux/fscache-cache.h>
962306a36Sopenharmony_ci#include <linux/uio.h>
1062306a36Sopenharmony_ci#include <linux/bvec.h>
1162306a36Sopenharmony_ci#include <linux/slab.h>
1262306a36Sopenharmony_ci#include <linux/uio.h>
1362306a36Sopenharmony_ci#include "internal.h"
1462306a36Sopenharmony_ci
1562306a36Sopenharmony_ci/**
1662306a36Sopenharmony_ci * fscache_wait_for_operation - Wait for an object become accessible
1762306a36Sopenharmony_ci * @cres: The cache resources for the operation being performed
1862306a36Sopenharmony_ci * @want_state: The minimum state the object must be at
1962306a36Sopenharmony_ci *
2062306a36Sopenharmony_ci * See if the target cache object is at the specified minimum state of
2162306a36Sopenharmony_ci * accessibility yet, and if not, wait for it.
2262306a36Sopenharmony_ci */
2362306a36Sopenharmony_cibool fscache_wait_for_operation(struct netfs_cache_resources *cres,
2462306a36Sopenharmony_ci				enum fscache_want_state want_state)
2562306a36Sopenharmony_ci{
2662306a36Sopenharmony_ci	struct fscache_cookie *cookie = fscache_cres_cookie(cres);
2762306a36Sopenharmony_ci	enum fscache_cookie_state state;
2862306a36Sopenharmony_ci
2962306a36Sopenharmony_ciagain:
3062306a36Sopenharmony_ci	if (!fscache_cache_is_live(cookie->volume->cache)) {
3162306a36Sopenharmony_ci		_leave(" [broken]");
3262306a36Sopenharmony_ci		return false;
3362306a36Sopenharmony_ci	}
3462306a36Sopenharmony_ci
3562306a36Sopenharmony_ci	state = fscache_cookie_state(cookie);
3662306a36Sopenharmony_ci	_enter("c=%08x{%u},%x", cookie->debug_id, state, want_state);
3762306a36Sopenharmony_ci
3862306a36Sopenharmony_ci	switch (state) {
3962306a36Sopenharmony_ci	case FSCACHE_COOKIE_STATE_CREATING:
4062306a36Sopenharmony_ci	case FSCACHE_COOKIE_STATE_INVALIDATING:
4162306a36Sopenharmony_ci		if (want_state == FSCACHE_WANT_PARAMS)
4262306a36Sopenharmony_ci			goto ready; /* There can be no content */
4362306a36Sopenharmony_ci		fallthrough;
4462306a36Sopenharmony_ci	case FSCACHE_COOKIE_STATE_LOOKING_UP:
4562306a36Sopenharmony_ci	case FSCACHE_COOKIE_STATE_LRU_DISCARDING:
4662306a36Sopenharmony_ci		wait_var_event(&cookie->state,
4762306a36Sopenharmony_ci			       fscache_cookie_state(cookie) != state);
4862306a36Sopenharmony_ci		goto again;
4962306a36Sopenharmony_ci
5062306a36Sopenharmony_ci	case FSCACHE_COOKIE_STATE_ACTIVE:
5162306a36Sopenharmony_ci		goto ready;
5262306a36Sopenharmony_ci	case FSCACHE_COOKIE_STATE_DROPPED:
5362306a36Sopenharmony_ci	case FSCACHE_COOKIE_STATE_RELINQUISHING:
5462306a36Sopenharmony_ci	default:
5562306a36Sopenharmony_ci		_leave(" [not live]");
5662306a36Sopenharmony_ci		return false;
5762306a36Sopenharmony_ci	}
5862306a36Sopenharmony_ci
5962306a36Sopenharmony_ciready:
6062306a36Sopenharmony_ci	if (!cres->cache_priv2)
6162306a36Sopenharmony_ci		return cookie->volume->cache->ops->begin_operation(cres, want_state);
6262306a36Sopenharmony_ci	return true;
6362306a36Sopenharmony_ci}
6462306a36Sopenharmony_ciEXPORT_SYMBOL(fscache_wait_for_operation);
6562306a36Sopenharmony_ci
6662306a36Sopenharmony_ci/*
6762306a36Sopenharmony_ci * Begin an I/O operation on the cache, waiting till we reach the right state.
6862306a36Sopenharmony_ci *
6962306a36Sopenharmony_ci * Attaches the resources required to the operation resources record.
7062306a36Sopenharmony_ci */
7162306a36Sopenharmony_cistatic int fscache_begin_operation(struct netfs_cache_resources *cres,
7262306a36Sopenharmony_ci				   struct fscache_cookie *cookie,
7362306a36Sopenharmony_ci				   enum fscache_want_state want_state,
7462306a36Sopenharmony_ci				   enum fscache_access_trace why)
7562306a36Sopenharmony_ci{
7662306a36Sopenharmony_ci	enum fscache_cookie_state state;
7762306a36Sopenharmony_ci	long timeo;
7862306a36Sopenharmony_ci	bool once_only = false;
7962306a36Sopenharmony_ci
8062306a36Sopenharmony_ci	cres->ops		= NULL;
8162306a36Sopenharmony_ci	cres->cache_priv	= cookie;
8262306a36Sopenharmony_ci	cres->cache_priv2	= NULL;
8362306a36Sopenharmony_ci	cres->debug_id		= cookie->debug_id;
8462306a36Sopenharmony_ci	cres->inval_counter	= cookie->inval_counter;
8562306a36Sopenharmony_ci
8662306a36Sopenharmony_ci	if (!fscache_begin_cookie_access(cookie, why))
8762306a36Sopenharmony_ci		return -ENOBUFS;
8862306a36Sopenharmony_ci
8962306a36Sopenharmony_ciagain:
9062306a36Sopenharmony_ci	spin_lock(&cookie->lock);
9162306a36Sopenharmony_ci
9262306a36Sopenharmony_ci	state = fscache_cookie_state(cookie);
9362306a36Sopenharmony_ci	_enter("c=%08x{%u},%x", cookie->debug_id, state, want_state);
9462306a36Sopenharmony_ci
9562306a36Sopenharmony_ci	switch (state) {
9662306a36Sopenharmony_ci	case FSCACHE_COOKIE_STATE_LOOKING_UP:
9762306a36Sopenharmony_ci	case FSCACHE_COOKIE_STATE_LRU_DISCARDING:
9862306a36Sopenharmony_ci	case FSCACHE_COOKIE_STATE_INVALIDATING:
9962306a36Sopenharmony_ci		goto wait_for_file_wrangling;
10062306a36Sopenharmony_ci	case FSCACHE_COOKIE_STATE_CREATING:
10162306a36Sopenharmony_ci		if (want_state == FSCACHE_WANT_PARAMS)
10262306a36Sopenharmony_ci			goto ready; /* There can be no content */
10362306a36Sopenharmony_ci		goto wait_for_file_wrangling;
10462306a36Sopenharmony_ci	case FSCACHE_COOKIE_STATE_ACTIVE:
10562306a36Sopenharmony_ci		goto ready;
10662306a36Sopenharmony_ci	case FSCACHE_COOKIE_STATE_DROPPED:
10762306a36Sopenharmony_ci	case FSCACHE_COOKIE_STATE_RELINQUISHING:
10862306a36Sopenharmony_ci		WARN(1, "Can't use cookie in state %u\n", cookie->state);
10962306a36Sopenharmony_ci		goto not_live;
11062306a36Sopenharmony_ci	default:
11162306a36Sopenharmony_ci		goto not_live;
11262306a36Sopenharmony_ci	}
11362306a36Sopenharmony_ci
11462306a36Sopenharmony_ciready:
11562306a36Sopenharmony_ci	spin_unlock(&cookie->lock);
11662306a36Sopenharmony_ci	if (!cookie->volume->cache->ops->begin_operation(cres, want_state))
11762306a36Sopenharmony_ci		goto failed;
11862306a36Sopenharmony_ci	return 0;
11962306a36Sopenharmony_ci
12062306a36Sopenharmony_ciwait_for_file_wrangling:
12162306a36Sopenharmony_ci	spin_unlock(&cookie->lock);
12262306a36Sopenharmony_ci	trace_fscache_access(cookie->debug_id, refcount_read(&cookie->ref),
12362306a36Sopenharmony_ci			     atomic_read(&cookie->n_accesses),
12462306a36Sopenharmony_ci			     fscache_access_io_wait);
12562306a36Sopenharmony_ci	timeo = wait_var_event_timeout(&cookie->state,
12662306a36Sopenharmony_ci				       fscache_cookie_state(cookie) != state, 20 * HZ);
12762306a36Sopenharmony_ci	if (timeo <= 1 && !once_only) {
12862306a36Sopenharmony_ci		pr_warn("%s: cookie state change wait timed out: cookie->state=%u state=%u",
12962306a36Sopenharmony_ci			__func__, fscache_cookie_state(cookie), state);
13062306a36Sopenharmony_ci		fscache_print_cookie(cookie, 'O');
13162306a36Sopenharmony_ci		once_only = true;
13262306a36Sopenharmony_ci	}
13362306a36Sopenharmony_ci	goto again;
13462306a36Sopenharmony_ci
13562306a36Sopenharmony_cinot_live:
13662306a36Sopenharmony_ci	spin_unlock(&cookie->lock);
13762306a36Sopenharmony_cifailed:
13862306a36Sopenharmony_ci	cres->cache_priv = NULL;
13962306a36Sopenharmony_ci	cres->ops = NULL;
14062306a36Sopenharmony_ci	fscache_end_cookie_access(cookie, fscache_access_io_not_live);
14162306a36Sopenharmony_ci	_leave(" = -ENOBUFS");
14262306a36Sopenharmony_ci	return -ENOBUFS;
14362306a36Sopenharmony_ci}
14462306a36Sopenharmony_ci
14562306a36Sopenharmony_ciint __fscache_begin_read_operation(struct netfs_cache_resources *cres,
14662306a36Sopenharmony_ci				   struct fscache_cookie *cookie)
14762306a36Sopenharmony_ci{
14862306a36Sopenharmony_ci	return fscache_begin_operation(cres, cookie, FSCACHE_WANT_PARAMS,
14962306a36Sopenharmony_ci				       fscache_access_io_read);
15062306a36Sopenharmony_ci}
15162306a36Sopenharmony_ciEXPORT_SYMBOL(__fscache_begin_read_operation);
15262306a36Sopenharmony_ci
15362306a36Sopenharmony_ciint __fscache_begin_write_operation(struct netfs_cache_resources *cres,
15462306a36Sopenharmony_ci				    struct fscache_cookie *cookie)
15562306a36Sopenharmony_ci{
15662306a36Sopenharmony_ci	return fscache_begin_operation(cres, cookie, FSCACHE_WANT_PARAMS,
15762306a36Sopenharmony_ci				       fscache_access_io_write);
15862306a36Sopenharmony_ci}
15962306a36Sopenharmony_ciEXPORT_SYMBOL(__fscache_begin_write_operation);
16062306a36Sopenharmony_ci
16162306a36Sopenharmony_ci/**
16262306a36Sopenharmony_ci * fscache_dirty_folio - Mark folio dirty and pin a cache object for writeback
16362306a36Sopenharmony_ci * @mapping: The mapping the folio belongs to.
16462306a36Sopenharmony_ci * @folio: The folio being dirtied.
16562306a36Sopenharmony_ci * @cookie: The cookie referring to the cache object
16662306a36Sopenharmony_ci *
16762306a36Sopenharmony_ci * Set the dirty flag on a folio and pin an in-use cache object in memory
16862306a36Sopenharmony_ci * so that writeback can later write to it.  This is intended
16962306a36Sopenharmony_ci * to be called from the filesystem's ->dirty_folio() method.
17062306a36Sopenharmony_ci *
17162306a36Sopenharmony_ci * Return: true if the dirty flag was set on the folio, false otherwise.
17262306a36Sopenharmony_ci */
17362306a36Sopenharmony_cibool fscache_dirty_folio(struct address_space *mapping, struct folio *folio,
17462306a36Sopenharmony_ci				struct fscache_cookie *cookie)
17562306a36Sopenharmony_ci{
17662306a36Sopenharmony_ci	struct inode *inode = mapping->host;
17762306a36Sopenharmony_ci	bool need_use = false;
17862306a36Sopenharmony_ci
17962306a36Sopenharmony_ci	_enter("");
18062306a36Sopenharmony_ci
18162306a36Sopenharmony_ci	if (!filemap_dirty_folio(mapping, folio))
18262306a36Sopenharmony_ci		return false;
18362306a36Sopenharmony_ci	if (!fscache_cookie_valid(cookie))
18462306a36Sopenharmony_ci		return true;
18562306a36Sopenharmony_ci
18662306a36Sopenharmony_ci	if (!(inode->i_state & I_PINNING_FSCACHE_WB)) {
18762306a36Sopenharmony_ci		spin_lock(&inode->i_lock);
18862306a36Sopenharmony_ci		if (!(inode->i_state & I_PINNING_FSCACHE_WB)) {
18962306a36Sopenharmony_ci			inode->i_state |= I_PINNING_FSCACHE_WB;
19062306a36Sopenharmony_ci			need_use = true;
19162306a36Sopenharmony_ci		}
19262306a36Sopenharmony_ci		spin_unlock(&inode->i_lock);
19362306a36Sopenharmony_ci
19462306a36Sopenharmony_ci		if (need_use)
19562306a36Sopenharmony_ci			fscache_use_cookie(cookie, true);
19662306a36Sopenharmony_ci	}
19762306a36Sopenharmony_ci	return true;
19862306a36Sopenharmony_ci}
19962306a36Sopenharmony_ciEXPORT_SYMBOL(fscache_dirty_folio);
20062306a36Sopenharmony_ci
20162306a36Sopenharmony_cistruct fscache_write_request {
20262306a36Sopenharmony_ci	struct netfs_cache_resources cache_resources;
20362306a36Sopenharmony_ci	struct address_space	*mapping;
20462306a36Sopenharmony_ci	loff_t			start;
20562306a36Sopenharmony_ci	size_t			len;
20662306a36Sopenharmony_ci	bool			set_bits;
20762306a36Sopenharmony_ci	netfs_io_terminated_t	term_func;
20862306a36Sopenharmony_ci	void			*term_func_priv;
20962306a36Sopenharmony_ci};
21062306a36Sopenharmony_ci
21162306a36Sopenharmony_civoid __fscache_clear_page_bits(struct address_space *mapping,
21262306a36Sopenharmony_ci			       loff_t start, size_t len)
21362306a36Sopenharmony_ci{
21462306a36Sopenharmony_ci	pgoff_t first = start / PAGE_SIZE;
21562306a36Sopenharmony_ci	pgoff_t last = (start + len - 1) / PAGE_SIZE;
21662306a36Sopenharmony_ci	struct page *page;
21762306a36Sopenharmony_ci
21862306a36Sopenharmony_ci	if (len) {
21962306a36Sopenharmony_ci		XA_STATE(xas, &mapping->i_pages, first);
22062306a36Sopenharmony_ci
22162306a36Sopenharmony_ci		rcu_read_lock();
22262306a36Sopenharmony_ci		xas_for_each(&xas, page, last) {
22362306a36Sopenharmony_ci			end_page_fscache(page);
22462306a36Sopenharmony_ci		}
22562306a36Sopenharmony_ci		rcu_read_unlock();
22662306a36Sopenharmony_ci	}
22762306a36Sopenharmony_ci}
22862306a36Sopenharmony_ciEXPORT_SYMBOL(__fscache_clear_page_bits);
22962306a36Sopenharmony_ci
23062306a36Sopenharmony_ci/*
23162306a36Sopenharmony_ci * Deal with the completion of writing the data to the cache.
23262306a36Sopenharmony_ci */
23362306a36Sopenharmony_cistatic void fscache_wreq_done(void *priv, ssize_t transferred_or_error,
23462306a36Sopenharmony_ci			      bool was_async)
23562306a36Sopenharmony_ci{
23662306a36Sopenharmony_ci	struct fscache_write_request *wreq = priv;
23762306a36Sopenharmony_ci
23862306a36Sopenharmony_ci	fscache_clear_page_bits(wreq->mapping, wreq->start, wreq->len,
23962306a36Sopenharmony_ci				wreq->set_bits);
24062306a36Sopenharmony_ci
24162306a36Sopenharmony_ci	if (wreq->term_func)
24262306a36Sopenharmony_ci		wreq->term_func(wreq->term_func_priv, transferred_or_error,
24362306a36Sopenharmony_ci				was_async);
24462306a36Sopenharmony_ci	fscache_end_operation(&wreq->cache_resources);
24562306a36Sopenharmony_ci	kfree(wreq);
24662306a36Sopenharmony_ci}
24762306a36Sopenharmony_ci
24862306a36Sopenharmony_civoid __fscache_write_to_cache(struct fscache_cookie *cookie,
24962306a36Sopenharmony_ci			      struct address_space *mapping,
25062306a36Sopenharmony_ci			      loff_t start, size_t len, loff_t i_size,
25162306a36Sopenharmony_ci			      netfs_io_terminated_t term_func,
25262306a36Sopenharmony_ci			      void *term_func_priv,
25362306a36Sopenharmony_ci			      bool cond)
25462306a36Sopenharmony_ci{
25562306a36Sopenharmony_ci	struct fscache_write_request *wreq;
25662306a36Sopenharmony_ci	struct netfs_cache_resources *cres;
25762306a36Sopenharmony_ci	struct iov_iter iter;
25862306a36Sopenharmony_ci	int ret = -ENOBUFS;
25962306a36Sopenharmony_ci
26062306a36Sopenharmony_ci	if (len == 0)
26162306a36Sopenharmony_ci		goto abandon;
26262306a36Sopenharmony_ci
26362306a36Sopenharmony_ci	_enter("%llx,%zx", start, len);
26462306a36Sopenharmony_ci
26562306a36Sopenharmony_ci	wreq = kzalloc(sizeof(struct fscache_write_request), GFP_NOFS);
26662306a36Sopenharmony_ci	if (!wreq)
26762306a36Sopenharmony_ci		goto abandon;
26862306a36Sopenharmony_ci	wreq->mapping		= mapping;
26962306a36Sopenharmony_ci	wreq->start		= start;
27062306a36Sopenharmony_ci	wreq->len		= len;
27162306a36Sopenharmony_ci	wreq->set_bits		= cond;
27262306a36Sopenharmony_ci	wreq->term_func		= term_func;
27362306a36Sopenharmony_ci	wreq->term_func_priv	= term_func_priv;
27462306a36Sopenharmony_ci
27562306a36Sopenharmony_ci	cres = &wreq->cache_resources;
27662306a36Sopenharmony_ci	if (fscache_begin_operation(cres, cookie, FSCACHE_WANT_WRITE,
27762306a36Sopenharmony_ci				    fscache_access_io_write) < 0)
27862306a36Sopenharmony_ci		goto abandon_free;
27962306a36Sopenharmony_ci
28062306a36Sopenharmony_ci	ret = cres->ops->prepare_write(cres, &start, &len, i_size, false);
28162306a36Sopenharmony_ci	if (ret < 0)
28262306a36Sopenharmony_ci		goto abandon_end;
28362306a36Sopenharmony_ci
28462306a36Sopenharmony_ci	/* TODO: Consider clearing page bits now for space the write isn't
28562306a36Sopenharmony_ci	 * covering.  This is more complicated than it appears when THPs are
28662306a36Sopenharmony_ci	 * taken into account.
28762306a36Sopenharmony_ci	 */
28862306a36Sopenharmony_ci
28962306a36Sopenharmony_ci	iov_iter_xarray(&iter, ITER_SOURCE, &mapping->i_pages, start, len);
29062306a36Sopenharmony_ci	fscache_write(cres, start, &iter, fscache_wreq_done, wreq);
29162306a36Sopenharmony_ci	return;
29262306a36Sopenharmony_ci
29362306a36Sopenharmony_ciabandon_end:
29462306a36Sopenharmony_ci	return fscache_wreq_done(wreq, ret, false);
29562306a36Sopenharmony_ciabandon_free:
29662306a36Sopenharmony_ci	kfree(wreq);
29762306a36Sopenharmony_ciabandon:
29862306a36Sopenharmony_ci	fscache_clear_page_bits(mapping, start, len, cond);
29962306a36Sopenharmony_ci	if (term_func)
30062306a36Sopenharmony_ci		term_func(term_func_priv, ret, false);
30162306a36Sopenharmony_ci}
30262306a36Sopenharmony_ciEXPORT_SYMBOL(__fscache_write_to_cache);
30362306a36Sopenharmony_ci
30462306a36Sopenharmony_ci/*
30562306a36Sopenharmony_ci * Change the size of a backing object.
30662306a36Sopenharmony_ci */
30762306a36Sopenharmony_civoid __fscache_resize_cookie(struct fscache_cookie *cookie, loff_t new_size)
30862306a36Sopenharmony_ci{
30962306a36Sopenharmony_ci	struct netfs_cache_resources cres;
31062306a36Sopenharmony_ci
31162306a36Sopenharmony_ci	trace_fscache_resize(cookie, new_size);
31262306a36Sopenharmony_ci	if (fscache_begin_operation(&cres, cookie, FSCACHE_WANT_WRITE,
31362306a36Sopenharmony_ci				    fscache_access_io_resize) == 0) {
31462306a36Sopenharmony_ci		fscache_stat(&fscache_n_resizes);
31562306a36Sopenharmony_ci		set_bit(FSCACHE_COOKIE_NEEDS_UPDATE, &cookie->flags);
31662306a36Sopenharmony_ci
31762306a36Sopenharmony_ci		/* We cannot defer a resize as we need to do it inside the
31862306a36Sopenharmony_ci		 * netfs's inode lock so that we're serialised with respect to
31962306a36Sopenharmony_ci		 * writes.
32062306a36Sopenharmony_ci		 */
32162306a36Sopenharmony_ci		cookie->volume->cache->ops->resize_cookie(&cres, new_size);
32262306a36Sopenharmony_ci		fscache_end_operation(&cres);
32362306a36Sopenharmony_ci	} else {
32462306a36Sopenharmony_ci		fscache_stat(&fscache_n_resizes_null);
32562306a36Sopenharmony_ci	}
32662306a36Sopenharmony_ci}
32762306a36Sopenharmony_ciEXPORT_SYMBOL(__fscache_resize_cookie);
328