linux/fs/cachefiles/interface.c
David Howells b6134f7674
fscache, cachefiles: Fix resize handling
Fix the resize handling in fscache and cachefiles by passing down a flag to
indicate if the file is being shrunk as the cache's idea of how big the
file should be may be stale and thus not useful in comparison to the
object's size.

The issue is that netfs_perform_write() updates the network filesystem's
file's i_size as it adds data to the pagecache, but the cache isn't updated
until writeback flushes that data.  A truncation may then scrap that data
and so the cache never sees it and doesn't update before it is told to
resize.  In the resize, cachefiles was comparing the new size against the
current cache size and then thinking the file was being expanded.

Note that this is a bit more complicated for ceph than other filesystems as
ceph splits the truncation of the local pagecache and fscache to after the
RPC reply handler has updated the inode record - the reply handler
communicates the need for the truncation finalisation through ceph inode
flags.

To this end, ceph is changed such that the outstanding truncation count in the
inode turns into a bool and just set instead of being incremented (the code
only cares if it's zero or non-zero) and then a second bool is added to record
if the file was being shrunk.

Reported-by: Marc Dionne <marc.dionne@auristor.com>
Fixes: 1a9fe4b6e787 ("cachefiles: Don't rely on backing fs storage map for most use cases")
Signed-off-by: David Howells <dhowells@redhat.com>
Link: https://patch.msgid.link/1091807.1791483394@warthog.procyon.org.uk
Reviewed-by: Alex Markuze <amarkuze@redhat.com>
cc: Ilya Dryomov <idryomov@gmail.com>
cc: Alex Markuze <amarkuze@redhat.com>
cc: Viacheslav Dubeyko <slava@dubeyko.com>
cc: Paulo Alcantara <pc@manguebit.org>
cc: Matthew Wilcox <willy@infradead.org>
cc: v9fs@lists.linux.dev
cc: linux-afs@lists.infradead.org
cc: ceph-devel@vger.kernel.org
cc: linux-cifs@vger.kernel.org
cc: netfs@lists.linux.dev
cc: linux-fsdevel@vger.kernel.org
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
2026-10-16 11:02:26 +02:00

396 lines
11 KiB
C

// SPDX-License-Identifier: GPL-2.0-or-later
/* FS-Cache interface to CacheFiles
*
* Copyright (C) 2021 Red Hat, Inc. All Rights Reserved.
* Written by David Howells (dhowells@redhat.com)
*/
#include <linux/slab.h>
#include <linux/mount.h>
#include <linux/xattr.h>
#include <linux/file.h>
#include <linux/namei.h>
#include <linux/falloc.h>
#include <trace/events/fscache.h>
#include "internal.h"
static atomic_t cachefiles_object_debug_id;
/*
* Allocate a cache object record.
*/
static
struct cachefiles_object *cachefiles_alloc_object(struct fscache_cookie *cookie)
{
struct fscache_volume *vcookie = cookie->volume;
struct cachefiles_volume *volume = vcookie->cache_priv;
struct cachefiles_object *object;
_enter("{%s},%x,", vcookie->key, cookie->debug_id);
object = kmem_cache_zalloc(cachefiles_object_jar, GFP_KERNEL);
if (!object)
return NULL;
refcount_set(&object->ref, 1);
spin_lock_init(&object->lock);
INIT_LIST_HEAD(&object->cache_link);
object->volume = volume;
object->debug_id = atomic_inc_return(&cachefiles_object_debug_id);
object->cookie = fscache_get_cookie(cookie, fscache_cookie_get_attach_object);
fscache_count_object(vcookie->cache);
trace_cachefiles_ref(object->debug_id, cookie->debug_id, 1,
cachefiles_obj_new);
return object;
}
/*
* Note that an object has been seen.
*/
void cachefiles_see_object(struct cachefiles_object *object,
enum cachefiles_obj_ref_trace why)
{
trace_cachefiles_ref(object->debug_id, object->cookie->debug_id,
refcount_read(&object->ref), why);
}
/*
* Increment the usage count on an object;
*/
struct cachefiles_object *cachefiles_grab_object(struct cachefiles_object *object,
enum cachefiles_obj_ref_trace why)
{
int r;
__refcount_inc(&object->ref, &r);
trace_cachefiles_ref(object->debug_id, object->cookie->debug_id, r, why);
return object;
}
/*
* dispose of a reference to an object
*/
void cachefiles_put_object(struct cachefiles_object *object,
enum cachefiles_obj_ref_trace why)
{
unsigned int object_debug_id = object->debug_id;
unsigned int cookie_debug_id = object->cookie->debug_id;
struct fscache_cache *cache;
bool done;
int r;
done = __refcount_dec_and_test(&object->ref, &r);
trace_cachefiles_ref(object_debug_id, cookie_debug_id, r, why);
if (done) {
_debug("- kill object OBJ%x", object_debug_id);
ASSERTCMP(object->file, ==, NULL);
kfree(object->d_name);
cache = object->volume->cache->cache;
fscache_put_cookie(object->cookie, fscache_cookie_put_object);
object->cookie = NULL;
kmem_cache_free(cachefiles_object_jar, object);
fscache_uncount_object(cache);
}
_leave("");
}
/*
* Attempt to look up the nominated node in this cache
*/
static bool cachefiles_lookup_cookie(struct fscache_cookie *cookie)
{
struct cachefiles_object *object;
struct cachefiles_cache *cache = cookie->volume->cache->cache_priv;
const struct cred *saved_cred;
bool success;
object = cachefiles_alloc_object(cookie);
if (!object)
goto fail;
_enter("{OBJ%x}", object->debug_id);
if (!cachefiles_cook_key(object))
goto fail_put;
cookie->cache_priv = object;
cachefiles_begin_secure(cache, &saved_cred);
success = cachefiles_look_up_object(object);
if (!success)
goto fail_withdraw;
cachefiles_see_object(object, cachefiles_obj_see_lookup_cookie);
spin_lock(&cache->object_list_lock);
list_add(&object->cache_link, &cache->object_list);
spin_unlock(&cache->object_list_lock);
cachefiles_end_secure(cache, saved_cred);
_leave(" = t");
return true;
fail_withdraw:
cachefiles_end_secure(cache, saved_cred);
cachefiles_see_object(object, cachefiles_obj_see_lookup_failed);
fscache_caching_failed(cookie);
_debug("failed c=%08x o=%08x", cookie->debug_id, object->debug_id);
/* The caller holds an access count on the cookie, so we need them to
* drop it before we can withdraw the object.
*/
return false;
fail_put:
cachefiles_put_object(object, cachefiles_obj_put_alloc_fail);
fail:
return false;
}
/*
* Shorten the backing object to discard any dirty data and free up
* any unused granules.
*/
static bool cachefiles_shorten_object(struct cachefiles_object *object,
struct file *file, uoff_t new_size)
{
struct cachefiles_cache *cache = object->volume->cache;
struct inode *inode = file_inode(file);
uoff_t i_size, dio_size;
int ret;
dio_size = round_up(new_size, cache->bsize);
i_size = i_size_read(inode);
trace_cachefiles_trunc(object, inode, i_size, dio_size,
cachefiles_trunc_shrink);
ret = cachefiles_inject_remove_error();
if (ret == 0)
ret = vfs_truncate(&file->f_path, dio_size);
if (ret < 0) {
trace_cachefiles_io_error(object, file_inode(file), ret,
cachefiles_trace_trunc_error);
cachefiles_io_error_obj(object, "Trunc-to-size failed %d", ret);
cachefiles_remove_object_xattr(cache, object, file->f_path.dentry);
return false;
}
if (new_size < dio_size) {
trace_cachefiles_trunc(object, inode, dio_size, new_size,
cachefiles_trunc_dio_adjust);
ret = cachefiles_inject_write_error();
if (ret == 0)
ret = vfs_fallocate(file, FALLOC_FL_ZERO_RANGE,
new_size, dio_size - new_size);
if (ret < 0) {
trace_cachefiles_io_error(object, file_inode(file), ret,
cachefiles_trace_fallocate_error);
cachefiles_io_error_obj(object, "Trunc-to-dio-size failed %d", ret);
cachefiles_remove_object_xattr(cache, object, file->f_path.dentry);
return false;
}
}
object->object_size = new_size;
return true;
}
/*
* Resize the backing object.
*/
static void cachefiles_resize_cookie(struct netfs_cache_resources *cres,
uoff_t new_size, bool shrank)
{
struct cachefiles_object *object = cachefiles_cres_object(cres);
struct cachefiles_cache *cache = object->volume->cache;
struct fscache_cookie *cookie = object->cookie;
const struct cred *saved_cred;
struct file *file = cachefiles_cres_file(cres);
uoff_t i_size = i_size_read(file_inode(file));
_enter("%llu->%llu", object->object_size, new_size);
/* If the file shrank, we need to downsize the backing file and clear
* the end of the final block.
*/
if (shrank) {
if (new_size >= i_size)
goto out;
cachefiles_begin_secure(cache, &saved_cred);
cachefiles_shorten_object(object, file, new_size);
cachefiles_end_secure(cache, saved_cred);
object->cookie->object_size = new_size;
if (new_size == 0)
object->content_info = CACHEFILES_CONTENT_NO_DATA;
return;
}
/* The file is being expanded. We don't need to do anything
* particularly. The tail of the last block should have been cleared
* both when it is written and when it is shrunk.
*/
out:
object->object_size = new_size;
cookie->object_size = new_size;
}
/*
* Commit changes to the object as we drop it.
*/
static void cachefiles_commit_object(struct cachefiles_object *object,
struct cachefiles_cache *cache)
{
bool update = false;
if (test_and_clear_bit(FSCACHE_COOKIE_LOCAL_WRITE, &object->cookie->flags))
update = true;
if (test_and_clear_bit(FSCACHE_COOKIE_NEEDS_UPDATE, &object->cookie->flags))
update = true;
if (update)
cachefiles_set_object_xattr(object);
if (test_bit(CACHEFILES_OBJECT_USING_TMPFILE, &object->flags))
cachefiles_commit_tmpfile(cache, object);
}
/*
* Finalise and object and close the VFS structs that we have.
*/
static void cachefiles_clean_up_object(struct cachefiles_object *object,
struct cachefiles_cache *cache)
{
struct file *file;
if (test_bit(FSCACHE_COOKIE_RETIRED, &object->cookie->flags)) {
if (!test_bit(CACHEFILES_OBJECT_USING_TMPFILE, &object->flags)) {
cachefiles_see_object(object, cachefiles_obj_see_clean_delete);
_debug("- inval object OBJ%x", object->debug_id);
cachefiles_delete_object(object, FSCACHE_OBJECT_WAS_RETIRED);
} else {
cachefiles_see_object(object, cachefiles_obj_see_clean_drop_tmp);
_debug("- inval object OBJ%x tmpfile", object->debug_id);
}
} else {
cachefiles_see_object(object, cachefiles_obj_see_clean_commit);
cachefiles_commit_object(object, cache);
}
cachefiles_unmark_inode_in_use(object, object->file);
spin_lock(&object->lock);
file = object->file;
object->file = NULL;
spin_unlock(&object->lock);
if (file)
fput(file);
}
/*
* Withdraw caching for a cookie.
*/
static void cachefiles_withdraw_cookie(struct fscache_cookie *cookie)
{
struct cachefiles_object *object = cookie->cache_priv;
struct cachefiles_cache *cache = object->volume->cache;
const struct cred *saved_cred;
_enter("o=%x", object->debug_id);
cachefiles_see_object(object, cachefiles_obj_see_withdraw_cookie);
if (!list_empty(&object->cache_link)) {
spin_lock(&cache->object_list_lock);
cachefiles_see_object(object, cachefiles_obj_see_withdrawal);
list_del_init(&object->cache_link);
spin_unlock(&cache->object_list_lock);
}
if (object->file) {
cachefiles_begin_secure(cache, &saved_cred);
cachefiles_clean_up_object(object, cache);
cachefiles_end_secure(cache, saved_cred);
}
cookie->cache_priv = NULL;
cachefiles_put_object(object, cachefiles_obj_put_detach);
}
/*
* Invalidate the storage associated with a cookie.
*/
static bool cachefiles_invalidate_cookie(struct fscache_cookie *cookie)
{
struct cachefiles_object *object = cookie->cache_priv;
struct file *new_file, *old_file;
bool old_tmpfile;
_enter("o=%x,[%llu]", object->debug_id, object->cookie->object_size);
old_tmpfile = test_bit(CACHEFILES_OBJECT_USING_TMPFILE, &object->flags);
if (!object->file) {
fscache_resume_after_invalidation(cookie);
_leave(" = t [light]");
return true;
}
new_file = cachefiles_create_tmpfile(object);
if (IS_ERR(new_file))
goto failed;
/* Substitute the VFS target */
_debug("sub");
spin_lock(&object->lock);
old_file = object->file;
object->file = new_file;
object->content_info = CACHEFILES_CONTENT_NO_DATA;
set_bit(CACHEFILES_OBJECT_USING_TMPFILE, &object->flags);
set_bit(FSCACHE_COOKIE_NEEDS_UPDATE, &object->cookie->flags);
spin_unlock(&object->lock);
_debug("subbed");
/* Allow I/O to take place again */
fscache_resume_after_invalidation(cookie);
if (old_file) {
if (!old_tmpfile) {
struct cachefiles_volume *volume = object->volume;
struct dentry *fan = volume->fanout[(u8)cookie->key_hash];
struct dentry *obj;
obj = start_removing_dentry(fan, old_file->f_path.dentry);
if (!IS_ERR(obj))
cachefiles_bury_object(volume->cache, object,
fan, obj,
FSCACHE_OBJECT_INVALIDATED);
}
fput(old_file);
}
_leave(" = t");
return true;
failed:
_leave(" = f");
return false;
}
const struct fscache_cache_ops cachefiles_cache_ops = {
.name = "cachefiles",
.acquire_volume = cachefiles_acquire_volume,
.free_volume = cachefiles_free_volume,
.lookup_cookie = cachefiles_lookup_cookie,
.withdraw_cookie = cachefiles_withdraw_cookie,
.invalidate_cookie = cachefiles_invalidate_cookie,
.begin_operation = cachefiles_begin_operation,
.resize_cookie = cachefiles_resize_cookie,
.prepare_to_write = cachefiles_prepare_to_write,
};