Merge tag 'for-linus-5.17-rc1-tag' of git://git.kernel.org/pub/scm/linux/kernel/git...
[linux-block.git] / fs / cachefiles / interface.c
index da28ac1fa225d0a41ff3f9b0a028acd021f94e59..51c968cd00a6746363f6047ffd94c22d32c5abd9 100644 (file)
 // SPDX-License-Identifier: GPL-2.0-or-later
 /* FS-Cache interface to CacheFiles
  *
- * Copyright (C) 2007 Red Hat, Inc. All Rights Reserved.
+ * 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/falloc.h>
+#include <trace/events/fscache.h>
 #include "internal.h"
 
-struct cachefiles_lookup_data {
-       struct cachefiles_xattr *auxdata;       /* auxiliary data */
-       char                    *key;           /* key path */
-};
-
-static int cachefiles_attr_changed(struct fscache_object *_object);
+static atomic_t cachefiles_object_debug_id;
 
 /*
- * allocate an object record for a cookie lookup and prepare the lookup data
+ * Allocate a cache object record.
  */
-static struct fscache_object *cachefiles_alloc_object(
-       struct fscache_cache *_cache,
-       struct fscache_cookie *cookie)
+static
+struct cachefiles_object *cachefiles_alloc_object(struct fscache_cookie *cookie)
 {
-       struct cachefiles_lookup_data *lookup_data;
+       struct fscache_volume *vcookie = cookie->volume;
+       struct cachefiles_volume *volume = vcookie->cache_priv;
        struct cachefiles_object *object;
-       struct cachefiles_cache *cache;
-       struct cachefiles_xattr *auxdata;
-       unsigned keylen, auxlen;
-       void *buffer, *p;
-       char *key;
 
-       cache = container_of(_cache, struct cachefiles_cache, cache);
+       _enter("{%s},%x,", vcookie->key, cookie->debug_id);
 
-       _enter("{%s},%x,", cache->cache.identifier, cookie->debug_id);
-
-       lookup_data = kmalloc(sizeof(*lookup_data), cachefiles_gfp);
-       if (!lookup_data)
-               goto nomem_lookup_data;
-
-       /* create a new object record and a temporary leaf image */
-       object = kmem_cache_alloc(cachefiles_object_jar, cachefiles_gfp);
+       object = kmem_cache_zalloc(cachefiles_object_jar, GFP_KERNEL);
        if (!object)
-               goto nomem_object;
-
-       ASSERTCMP(object->backer, ==, NULL);
+               return NULL;
 
-       BUG_ON(test_bit(CACHEFILES_OBJECT_ACTIVE, &object->flags));
-       atomic_set(&object->usage, 1);
+       refcount_set(&object->ref, 1);
 
-       fscache_object_init(&object->fscache, cookie, &cache->cache);
+       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);
 
-       object->type = cookie->def->type;
-
-       /* get hold of the raw key
-        * - stick the length on the front and leave space on the back for the
-        *   encoder
-        */
-       buffer = kmalloc((2 + 512) + 3, cachefiles_gfp);
-       if (!buffer)
-               goto nomem_buffer;
-
-       keylen = cookie->key_len;
-       if (keylen <= sizeof(cookie->inline_key))
-               p = cookie->inline_key;
-       else
-               p = cookie->key;
-       memcpy(buffer + 2, p, keylen);
-
-       *(uint16_t *)buffer = keylen;
-       ((char *)buffer)[keylen + 2] = 0;
-       ((char *)buffer)[keylen + 3] = 0;
-       ((char *)buffer)[keylen + 4] = 0;
-
-       /* turn the raw key into something that can work with as a filename */
-       key = cachefiles_cook_key(buffer, keylen + 2, object->type);
-       if (!key)
-               goto nomem_key;
-
-       /* get hold of the auxiliary data and prepend the object type */
-       auxdata = buffer;
-       auxlen = cookie->aux_len;
-       if (auxlen) {
-               if (auxlen <= sizeof(cookie->inline_aux))
-                       p = cookie->inline_aux;
-               else
-                       p = cookie->aux;
-               memcpy(auxdata->data, p, auxlen);
-       }
-
-       auxdata->len = auxlen + 1;
-       auxdata->type = cookie->type;
-
-       lookup_data->auxdata = auxdata;
-       lookup_data->key = key;
-       object->lookup_data = lookup_data;
-
-       _leave(" = %x [%p]", object->fscache.debug_id, lookup_data);
-       return &object->fscache;
-
-nomem_key:
-       kfree(buffer);
-nomem_buffer:
-       BUG_ON(test_bit(CACHEFILES_OBJECT_ACTIVE, &object->flags));
-       kmem_cache_free(cachefiles_object_jar, object);
-       fscache_object_destroyed(&cache->cache);
-nomem_object:
-       kfree(lookup_data);
-nomem_lookup_data:
-       _leave(" = -ENOMEM");
-       return ERR_PTR(-ENOMEM);
+       fscache_count_object(vcookie->cache);
+       trace_cachefiles_ref(object->debug_id, cookie->debug_id, 1,
+                            cachefiles_obj_new);
+       return object;
 }
 
 /*
- * attempt to look up the nominated node in this cache
- * - return -ETIMEDOUT to be scheduled again
+ * Note that an object has been seen.
  */
-static int cachefiles_lookup_object(struct fscache_object *_object)
+void cachefiles_see_object(struct cachefiles_object *object,
+                          enum cachefiles_obj_ref_trace why)
 {
-       struct cachefiles_lookup_data *lookup_data;
-       struct cachefiles_object *parent, *object;
-       struct cachefiles_cache *cache;
-       const struct cred *saved_cred;
-       int ret;
-
-       _enter("{OBJ%x}", _object->debug_id);
-
-       cache = container_of(_object->cache, struct cachefiles_cache, cache);
-       parent = container_of(_object->parent,
-                             struct cachefiles_object, fscache);
-       object = container_of(_object, struct cachefiles_object, fscache);
-       lookup_data = object->lookup_data;
-
-       ASSERTCMP(lookup_data, !=, NULL);
-
-       /* look up the key, creating any missing bits */
-       cachefiles_begin_secure(cache, &saved_cred);
-       ret = cachefiles_walk_to_object(parent, object,
-                                       lookup_data->key,
-                                       lookup_data->auxdata);
-       cachefiles_end_secure(cache, saved_cred);
-
-       /* polish off by setting the attributes of non-index files */
-       if (ret == 0 &&
-           object->fscache.cookie->def->type != FSCACHE_COOKIE_TYPE_INDEX)
-               cachefiles_attr_changed(&object->fscache);
-
-       if (ret < 0 && ret != -ETIMEDOUT) {
-               if (ret != -ENOBUFS)
-                       pr_warn("Lookup failed error %d\n", ret);
-               fscache_object_lookup_error(&object->fscache);
-       }
-
-       _leave(" [%d]", ret);
-       return ret;
+       trace_cachefiles_ref(object->debug_id, object->cookie->debug_id,
+                            refcount_read(&object->ref), why);
 }
 
 /*
- * indication of lookup completion
+ * Increment the usage count on an object;
  */
-static void cachefiles_lookup_complete(struct fscache_object *_object)
+struct cachefiles_object *cachefiles_grab_object(struct cachefiles_object *object,
+                                                enum cachefiles_obj_ref_trace why)
 {
-       struct cachefiles_object *object;
-
-       object = container_of(_object, struct cachefiles_object, fscache);
-
-       _enter("{OBJ%x,%p}", object->fscache.debug_id, object->lookup_data);
+       int r;
 
-       if (object->lookup_data) {
-               kfree(object->lookup_data->key);
-               kfree(object->lookup_data->auxdata);
-               kfree(object->lookup_data);
-               object->lookup_data = NULL;
-       }
+       __refcount_inc(&object->ref, &r);
+       trace_cachefiles_ref(object->debug_id, object->cookie->debug_id, r, why);
+       return object;
 }
 
 /*
- * increment the usage count on an inode object (may fail if unmounting)
+ * dispose of a reference to an object
  */
-static
-struct fscache_object *cachefiles_grab_object(struct fscache_object *_object,
-                                             enum fscache_obj_ref_trace why)
+void cachefiles_put_object(struct cachefiles_object *object,
+                          enum cachefiles_obj_ref_trace why)
 {
-       struct cachefiles_object *object =
-               container_of(_object, struct cachefiles_object, fscache);
-       int u;
+       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);
 
-       _enter("{OBJ%x,%d}", _object->debug_id, atomic_read(&object->usage));
+               kfree(object->d_name);
 
-#ifdef CACHEFILES_DEBUG_SLAB
-       ASSERT((atomic_read(&object->usage) & 0xffff0000) != 0x6b6b0000);
-#endif
+               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);
+       }
 
-       u = atomic_inc_return(&object->usage);
-       trace_cachefiles_ref(object, _object->cookie,
-                            (enum cachefiles_obj_ref_trace)why, u);
-       return &object->fscache;
+       _leave("");
 }
 
 /*
- * update the auxiliary data for an object object on disk
+ * Adjust the size of a cache file if necessary to match the DIO size.  We keep
+ * the EOF marker a multiple of DIO blocks so that we don't fall back to doing
+ * non-DIO for a partial block straddling the EOF, but we also have to be
+ * careful of someone expanding the file and accidentally accreting the
+ * padding.
  */
-static void cachefiles_update_object(struct fscache_object *_object)
+static int cachefiles_adjust_size(struct cachefiles_object *object)
 {
-       struct cachefiles_object *object;
-       struct cachefiles_xattr *auxdata;
-       struct cachefiles_cache *cache;
-       struct fscache_cookie *cookie;
-       const struct cred *saved_cred;
-       const void *aux;
-       unsigned auxlen;
+       struct iattr newattrs;
+       struct file *file = object->file;
+       uint64_t ni_size;
+       loff_t oi_size;
+       int ret;
 
-       _enter("{OBJ%x}", _object->debug_id);
+       ni_size = object->cookie->object_size;
+       ni_size = round_up(ni_size, CACHEFILES_DIO_BLOCK_SIZE);
 
-       object = container_of(_object, struct cachefiles_object, fscache);
-       cache = container_of(object->fscache.cache, struct cachefiles_cache,
-                            cache);
+       _enter("{OBJ%x},[%llu]",
+              object->debug_id, (unsigned long long) ni_size);
 
-       if (!fscache_use_cookie(_object)) {
-               _leave(" [relinq]");
-               return;
-       }
+       if (!file)
+               return -ENOBUFS;
 
-       cookie = object->fscache.cookie;
-       auxlen = cookie->aux_len;
+       oi_size = i_size_read(file_inode(file));
+       if (oi_size == ni_size)
+               return 0;
 
-       if (!auxlen) {
-               fscache_unuse_cookie(_object);
-               _leave(" [no aux]");
-               return;
-       }
+       inode_lock(file_inode(file));
 
-       auxdata = kmalloc(2 + auxlen + 3, cachefiles_gfp);
-       if (!auxdata) {
-               fscache_unuse_cookie(_object);
-               _leave(" [nomem]");
-               return;
+       /* if there's an extension to a partial page at the end of the backing
+        * file, we need to discard the partial page so that we pick up new
+        * data after it */
+       if (oi_size & ~PAGE_MASK && ni_size > oi_size) {
+               _debug("discard tail %llx", oi_size);
+               newattrs.ia_valid = ATTR_SIZE;
+               newattrs.ia_size = oi_size & PAGE_MASK;
+               ret = cachefiles_inject_remove_error();
+               if (ret == 0)
+                       ret = notify_change(&init_user_ns, file->f_path.dentry,
+                                           &newattrs, NULL);
+               if (ret < 0)
+                       goto truncate_failed;
        }
 
-       aux = (auxlen <= sizeof(cookie->inline_aux)) ?
-               cookie->inline_aux : cookie->aux;
+       newattrs.ia_valid = ATTR_SIZE;
+       newattrs.ia_size = ni_size;
+       ret = cachefiles_inject_write_error();
+       if (ret == 0)
+               ret = notify_change(&init_user_ns, file->f_path.dentry,
+                                   &newattrs, NULL);
 
-       memcpy(auxdata->data, aux, auxlen);
-       fscache_unuse_cookie(_object);
+truncate_failed:
+       inode_unlock(file_inode(file));
 
-       auxdata->len = auxlen + 1;
-       auxdata->type = cookie->type;
+       if (ret < 0)
+               trace_cachefiles_io_error(NULL, file_inode(file), ret,
+                                         cachefiles_trace_notify_change_error);
+       if (ret == -EIO) {
+               cachefiles_io_error_obj(object, "Size set failed");
+               ret = -ENOBUFS;
+       }
 
-       cachefiles_begin_secure(cache, &saved_cred);
-       cachefiles_update_object_xattr(object, auxdata);
-       cachefiles_end_secure(cache, saved_cred);
-       kfree(auxdata);
-       _leave("");
+       _leave(" = %d", ret);
+       return ret;
 }
 
 /*
- * discard the resources pinned by an object and effect retirement if
- * requested
+ * Attempt to look up the nominated node in this cache
  */
-static void cachefiles_drop_object(struct fscache_object *_object)
+static bool cachefiles_lookup_cookie(struct fscache_cookie *cookie)
 {
        struct cachefiles_object *object;
-       struct cachefiles_cache *cache;
+       struct cachefiles_cache *cache = cookie->volume->cache->cache_priv;
        const struct cred *saved_cred;
-       struct inode *inode;
-       blkcnt_t i_blocks = 0;
+       bool success;
 
-       ASSERT(_object);
+       object = cachefiles_alloc_object(cookie);
+       if (!object)
+               goto fail;
 
-       object = container_of(_object, struct cachefiles_object, fscache);
+       _enter("{OBJ%x}", object->debug_id);
 
-       _enter("{OBJ%x,%d}",
-              object->fscache.debug_id, atomic_read(&object->usage));
+       if (!cachefiles_cook_key(object))
+               goto fail_put;
 
-       cache = container_of(object->fscache.cache,
-                            struct cachefiles_cache, cache);
+       cookie->cache_priv = object;
 
-#ifdef CACHEFILES_DEBUG_SLAB
-       ASSERT((atomic_read(&object->usage) & 0xffff0000) != 0x6b6b0000);
-#endif
+       cachefiles_begin_secure(cache, &saved_cred);
 
-       /* We need to tidy the object up if we did in fact manage to open it.
-        * It's possible for us to get here before the object is fully
-        * initialised if the parent goes away or the object gets retired
-        * before we set it up.
-        */
-       if (object->dentry) {
-               /* delete retired objects */
-               if (test_bit(FSCACHE_OBJECT_RETIRED, &object->fscache.flags) &&
-                   _object != cache->cache.fsdef
-                   ) {
-                       _debug("- retire object OBJ%x", object->fscache.debug_id);
-                       inode = d_backing_inode(object->dentry);
-                       if (inode)
-                               i_blocks = inode->i_blocks;
-
-                       cachefiles_begin_secure(cache, &saved_cred);
-                       cachefiles_delete_object(cache, object);
-                       cachefiles_end_secure(cache, saved_cred);
-               }
+       success = cachefiles_look_up_object(object);
+       if (!success)
+               goto fail_withdraw;
 
-               /* close the filesystem stuff attached to the object */
-               if (object->backer != object->dentry)
-                       dput(object->backer);
-               object->backer = NULL;
-       }
+       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_adjust_size(object);
 
-       /* note that the object is now inactive */
-       if (test_bit(CACHEFILES_OBJECT_ACTIVE, &object->flags))
-               cachefiles_mark_object_inactive(cache, object, i_blocks);
+       cachefiles_end_secure(cache, saved_cred);
+       _leave(" = t");
+       return true;
 
-       dput(object->dentry);
-       object->dentry = NULL;
+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;
 
-       _leave("");
+fail_put:
+       cachefiles_put_object(object, cachefiles_obj_put_alloc_fail);
+fail:
+       return false;
 }
 
 /*
- * dispose of a reference to an object
+ * Shorten the backing object to discard any dirty data and free up
+ * any unused granules.
  */
-void cachefiles_put_object(struct fscache_object *_object,
-                          enum fscache_obj_ref_trace why)
+static bool cachefiles_shorten_object(struct cachefiles_object *object,
+                                     struct file *file, loff_t new_size)
 {
-       struct cachefiles_object *object;
-       struct fscache_cache *cache;
-       int u;
-
-       ASSERT(_object);
-
-       object = container_of(_object, struct cachefiles_object, fscache);
-
-       _enter("{OBJ%x,%d}",
-              object->fscache.debug_id, atomic_read(&object->usage));
-
-#ifdef CACHEFILES_DEBUG_SLAB
-       ASSERT((atomic_read(&object->usage) & 0xffff0000) != 0x6b6b0000);
-#endif
-
-       ASSERTIFCMP(object->fscache.parent,
-                   object->fscache.parent->n_children, >, 0);
-
-       u = atomic_dec_return(&object->usage);
-       trace_cachefiles_ref(object, _object->cookie,
-                            (enum cachefiles_obj_ref_trace)why, u);
-       ASSERTCMP(u, !=, -1);
-       if (u == 0) {
-               _debug("- kill object OBJ%x", object->fscache.debug_id);
+       struct cachefiles_cache *cache = object->volume->cache;
+       struct inode *inode = file_inode(file);
+       loff_t i_size, dio_size;
+       int ret;
 
-               ASSERT(!test_bit(CACHEFILES_OBJECT_ACTIVE, &object->flags));
-               ASSERTCMP(object->fscache.parent, ==, NULL);
-               ASSERTCMP(object->backer, ==, NULL);
-               ASSERTCMP(object->dentry, ==, NULL);
-               ASSERTCMP(object->fscache.n_ops, ==, 0);
-               ASSERTCMP(object->fscache.n_children, ==, 0);
+       dio_size = round_up(new_size, CACHEFILES_DIO_BLOCK_SIZE);
+       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 (object->lookup_data) {
-                       kfree(object->lookup_data->key);
-                       kfree(object->lookup_data->auxdata);
-                       kfree(object->lookup_data);
-                       object->lookup_data = NULL;
+       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);
+               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;
                }
-
-               cache = object->fscache.cache;
-               fscache_object_destroy(&object->fscache);
-               kmem_cache_free(cachefiles_object_jar, object);
-               fscache_object_destroyed(cache);
        }
 
-       _leave("");
+       return true;
 }
 
 /*
- * sync a cache
+ * Resize the backing object.
  */
-static void cachefiles_sync_cache(struct fscache_cache *_cache)
+static void cachefiles_resize_cookie(struct netfs_cache_resources *cres,
+                                    loff_t new_size)
 {
-       struct cachefiles_cache *cache;
+       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;
-       int ret;
+       struct file *file = cachefiles_cres_file(cres);
+       loff_t old_size = cookie->object_size;
 
-       _enter("%s", _cache->tag->name);
+       _enter("%llu->%llu", old_size, new_size);
 
-       cache = container_of(_cache, struct cachefiles_cache, cache);
-
-       /* make sure all pages pinned by operations on behalf of the netfs are
-        * written to disc */
-       cachefiles_begin_secure(cache, &saved_cred);
-       down_read(&cache->mnt->mnt_sb->s_umount);
-       ret = sync_filesystem(cache->mnt->mnt_sb);
-       up_read(&cache->mnt->mnt_sb->s_umount);
-       cachefiles_end_secure(cache, saved_cred);
+       if (new_size < old_size) {
+               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;
+               return;
+       }
 
-       if (ret == -EIO)
-               cachefiles_io_error(cache,
-                                   "Attempt to sync backing fs superblock"
-                                   " returned error %d",
-                                   ret);
+       /* The file is being expanded.  We don't need to do anything
+        * particularly.  cookie->initial_size doesn't change and so the point
+        * at which we have to download before doesn't change.
+        */
+       cookie->object_size = new_size;
 }
 
 /*
- * check if the backing cache is updated to FS-Cache
- * - called by FS-Cache when evaluates if need to invalidate the cache
+ * Commit changes to the object as we drop it.
  */
-static int cachefiles_check_consistency(struct fscache_operation *op)
+static void cachefiles_commit_object(struct cachefiles_object *object,
+                                    struct cachefiles_cache *cache)
 {
-       struct cachefiles_object *object;
-       struct cachefiles_cache *cache;
-       const struct cred *saved_cred;
-       int ret;
+       bool update = false;
 
-       _enter("{OBJ%x}", op->object->debug_id);
+       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);
 
-       object = container_of(op->object, struct cachefiles_object, fscache);
-       cache = container_of(object->fscache.cache,
-                            struct cachefiles_cache, cache);
+       if (test_bit(CACHEFILES_OBJECT_USING_TMPFILE, &object->flags))
+               cachefiles_commit_tmpfile(cache, object);
+}
 
-       cachefiles_begin_secure(cache, &saved_cred);
-       ret = cachefiles_check_auxdata(object);
-       cachefiles_end_secure(cache, saved_cred);
+/*
+ * 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)
+{
+       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);
+       }
 
-       _leave(" = %d", ret);
-       return ret;
+       cachefiles_unmark_inode_in_use(object, object->file);
+       if (object->file) {
+               fput(object->file);
+               object->file = NULL;
+       }
 }
 
 /*
- * notification the attributes on an object have changed
- * - called with reads/writes excluded by FS-Cache
+ * Withdraw caching for a cookie.
  */
-static int cachefiles_attr_changed(struct fscache_object *_object)
+static void cachefiles_withdraw_cookie(struct fscache_cookie *cookie)
 {
-       struct cachefiles_object *object;
-       struct cachefiles_cache *cache;
+       struct cachefiles_object *object = cookie->cache_priv;
+       struct cachefiles_cache *cache = object->volume->cache;
        const struct cred *saved_cred;
-       struct iattr newattrs;
-       uint64_t ni_size;
-       loff_t oi_size;
-       int ret;
-
-       ni_size = _object->store_limit_l;
-
-       _enter("{OBJ%x},[%llu]",
-              _object->debug_id, (unsigned long long) ni_size);
-
-       object = container_of(_object, struct cachefiles_object, fscache);
-       cache = container_of(object->fscache.cache,
-                            struct cachefiles_cache, cache);
-
-       if (ni_size == object->i_size)
-               return 0;
-
-       if (!object->backer)
-               return -ENOBUFS;
 
-       ASSERT(d_is_reg(object->backer));
+       _enter("o=%x", object->debug_id);
+       cachefiles_see_object(object, cachefiles_obj_see_withdraw_cookie);
 
-       fscache_set_store_limit(&object->fscache, ni_size);
-
-       oi_size = i_size_read(d_backing_inode(object->backer));
-       if (oi_size == ni_size)
-               return 0;
-
-       cachefiles_begin_secure(cache, &saved_cred);
-       inode_lock(d_inode(object->backer));
-
-       /* if there's an extension to a partial page at the end of the backing
-        * file, we need to discard the partial page so that we pick up new
-        * data after it */
-       if (oi_size & ~PAGE_MASK && ni_size > oi_size) {
-               _debug("discard tail %llx", oi_size);
-               newattrs.ia_valid = ATTR_SIZE;
-               newattrs.ia_size = oi_size & PAGE_MASK;
-               ret = notify_change(&init_user_ns, object->backer, &newattrs, NULL);
-               if (ret < 0)
-                       goto truncate_failed;
+       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);
        }
 
-       newattrs.ia_valid = ATTR_SIZE;
-       newattrs.ia_size = ni_size;
-       ret = notify_change(&init_user_ns, object->backer, &newattrs, NULL);
-
-truncate_failed:
-       inode_unlock(d_inode(object->backer));
-       cachefiles_end_secure(cache, saved_cred);
-
-       if (ret == -EIO) {
-               fscache_set_store_limit(&object->fscache, 0);
-               cachefiles_io_error_obj(object, "Size set failed");
-               ret = -ENOBUFS;
+       if (object->file) {
+               cachefiles_begin_secure(cache, &saved_cred);
+               cachefiles_clean_up_object(object, cache);
+               cachefiles_end_secure(cache, saved_cred);
        }
 
-       _leave(" = %d", ret);
-       return ret;
+       cookie->cache_priv = NULL;
+       cachefiles_put_object(object, cachefiles_obj_put_detach);
 }
 
 /*
- * Invalidate an object
+ * Invalidate the storage associated with a cookie.
  */
-static void cachefiles_invalidate_object(struct fscache_operation *op)
+static bool cachefiles_invalidate_cookie(struct fscache_cookie *cookie)
 {
-       struct cachefiles_object *object;
-       struct cachefiles_cache *cache;
-       const struct cred *saved_cred;
-       struct path path;
-       uint64_t ni_size;
-       int ret;
+       struct cachefiles_object *object = cookie->cache_priv;
+       struct file *new_file, *old_file;
+       bool old_tmpfile;
 
-       object = container_of(op->object, struct cachefiles_object, fscache);
-       cache = container_of(object->fscache.cache,
-                            struct cachefiles_cache, cache);
+       _enter("o=%x,[%llu]", object->debug_id, object->cookie->object_size);
 
-       ni_size = op->object->store_limit_l;
+       old_tmpfile = test_bit(CACHEFILES_OBJECT_USING_TMPFILE, &object->flags);
 
-       _enter("{OBJ%x},[%llu]",
-              op->object->debug_id, (unsigned long long)ni_size);
+       if (!object->file) {
+               fscache_resume_after_invalidation(cookie);
+               _leave(" = t [light]");
+               return true;
+       }
 
-       if (object->backer) {
-               ASSERT(d_is_reg(object->backer));
+       new_file = cachefiles_create_tmpfile(object);
+       if (IS_ERR(new_file))
+               goto failed;
 
-               fscache_set_store_limit(&object->fscache, ni_size);
+       /* Substitute the VFS target */
+       _debug("sub");
+       spin_lock(&object->lock);
 
-               path.dentry = object->backer;
-               path.mnt = cache->mnt;
+       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);
 
-               cachefiles_begin_secure(cache, &saved_cred);
-               ret = vfs_truncate(&path, 0);
-               if (ret == 0)
-                       ret = vfs_truncate(&path, ni_size);
-               cachefiles_end_secure(cache, saved_cred);
+       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];
 
-               if (ret != 0) {
-                       fscache_set_store_limit(&object->fscache, 0);
-                       if (ret == -EIO)
-                               cachefiles_io_error_obj(object,
-                                                       "Invalidate failed");
+                       inode_lock_nested(d_inode(fan), I_MUTEX_PARENT);
+                       cachefiles_bury_object(volume->cache, object, fan,
+                                              old_file->f_path.dentry,
+                                              FSCACHE_OBJECT_INVALIDATED);
                }
+               fput(old_file);
        }
 
-       fscache_op_complete(op, true);
-       _leave("");
-}
+       _leave(" = t");
+       return true;
 
-/*
- * dissociate a cache from all the pages it was backing
- */
-static void cachefiles_dissociate_pages(struct fscache_cache *cache)
-{
-       _enter("");
+failed:
+       _leave(" = f");
+       return false;
 }
 
 const struct fscache_cache_ops cachefiles_cache_ops = {
        .name                   = "cachefiles",
-       .alloc_object           = cachefiles_alloc_object,
-       .lookup_object          = cachefiles_lookup_object,
-       .lookup_complete        = cachefiles_lookup_complete,
-       .grab_object            = cachefiles_grab_object,
-       .update_object          = cachefiles_update_object,
-       .invalidate_object      = cachefiles_invalidate_object,
-       .drop_object            = cachefiles_drop_object,
-       .put_object             = cachefiles_put_object,
-       .sync_cache             = cachefiles_sync_cache,
-       .attr_changed           = cachefiles_attr_changed,
-       .read_or_alloc_page     = cachefiles_read_or_alloc_page,
-       .read_or_alloc_pages    = cachefiles_read_or_alloc_pages,
-       .allocate_page          = cachefiles_allocate_page,
-       .allocate_pages         = cachefiles_allocate_pages,
-       .write_page             = cachefiles_write_page,
-       .uncache_page           = cachefiles_uncache_page,
-       .dissociate_pages       = cachefiles_dissociate_pages,
-       .check_consistency      = cachefiles_check_consistency,
-       .begin_read_operation   = cachefiles_begin_read_operation,
+       .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,
 };