/* AFS caching stuff
  *
- * Copyright (C) 2007 Red Hat, Inc. All Rights Reserved.
+ * Copyright (C) 2008 Red Hat, Inc. All Rights Reserved.
  * Written by David Howells (dhowells@redhat.com)
  *
  * This program is free software; you can redistribute it and/or
  * 2 of the License, or (at your option) any later version.
  */
 
-#ifdef AFS_CACHING_SUPPORT
-static cachefs_match_val_t afs_cell_cache_match(void *target,
-                                               const void *entry);
-static void afs_cell_cache_update(void *source, void *entry);
-
-struct cachefs_index_def afs_cache_cell_index_def = {
-       .name                   = "cell_ix",
-       .data_size              = sizeof(struct afs_cache_cell),
-       .keys[0]                = { CACHEFS_INDEX_KEYS_ASCIIZ, 64 },
-       .match                  = afs_cell_cache_match,
-       .update                 = afs_cell_cache_update,
+#include <linux/slab.h>
+#include <linux/sched.h>
+#include "internal.h"
+
+static uint16_t afs_cell_cache_get_key(const void *cookie_netfs_data,
+                                      void *buffer, uint16_t buflen);
+static uint16_t afs_cell_cache_get_aux(const void *cookie_netfs_data,
+                                      void *buffer, uint16_t buflen);
+static enum fscache_checkaux afs_cell_cache_check_aux(void *cookie_netfs_data,
+                                                     const void *buffer,
+                                                     uint16_t buflen);
+
+static uint16_t afs_vlocation_cache_get_key(const void *cookie_netfs_data,
+                                           void *buffer, uint16_t buflen);
+static uint16_t afs_vlocation_cache_get_aux(const void *cookie_netfs_data,
+                                           void *buffer, uint16_t buflen);
+static enum fscache_checkaux afs_vlocation_cache_check_aux(
+       void *cookie_netfs_data, const void *buffer, uint16_t buflen);
+
+static uint16_t afs_volume_cache_get_key(const void *cookie_netfs_data,
+                                        void *buffer, uint16_t buflen);
+
+static uint16_t afs_vnode_cache_get_key(const void *cookie_netfs_data,
+                                       void *buffer, uint16_t buflen);
+static void afs_vnode_cache_get_attr(const void *cookie_netfs_data,
+                                    uint64_t *size);
+static uint16_t afs_vnode_cache_get_aux(const void *cookie_netfs_data,
+                                       void *buffer, uint16_t buflen);
+static enum fscache_checkaux afs_vnode_cache_check_aux(void *cookie_netfs_data,
+                                                      const void *buffer,
+                                                      uint16_t buflen);
+static void afs_vnode_cache_now_uncached(void *cookie_netfs_data);
+
+struct fscache_netfs afs_cache_netfs = {
+       .name                   = "afs",
+       .version                = 0,
+};
+
+struct fscache_cookie_def afs_cell_cache_index_def = {
+       .name           = "AFS.cell",
+       .type           = FSCACHE_COOKIE_TYPE_INDEX,
+       .get_key        = afs_cell_cache_get_key,
+       .get_aux        = afs_cell_cache_get_aux,
+       .check_aux      = afs_cell_cache_check_aux,
+};
+
+struct fscache_cookie_def afs_vlocation_cache_index_def = {
+       .name                   = "AFS.vldb",
+       .type                   = FSCACHE_COOKIE_TYPE_INDEX,
+       .get_key                = afs_vlocation_cache_get_key,
+       .get_aux                = afs_vlocation_cache_get_aux,
+       .check_aux              = afs_vlocation_cache_check_aux,
+};
+
+struct fscache_cookie_def afs_volume_cache_index_def = {
+       .name           = "AFS.volume",
+       .type           = FSCACHE_COOKIE_TYPE_INDEX,
+       .get_key        = afs_volume_cache_get_key,
+};
+
+struct fscache_cookie_def afs_vnode_cache_index_def = {
+       .name                   = "AFS.vnode",
+       .type                   = FSCACHE_COOKIE_TYPE_DATAFILE,
+       .get_key                = afs_vnode_cache_get_key,
+       .get_attr               = afs_vnode_cache_get_attr,
+       .get_aux                = afs_vnode_cache_get_aux,
+       .check_aux              = afs_vnode_cache_check_aux,
+       .now_uncached           = afs_vnode_cache_now_uncached,
 };
-#endif
 
 /*
- * match a cell record obtained from the cache
+ * set the key for the index entry
  */
-#ifdef AFS_CACHING_SUPPORT
-static cachefs_match_val_t afs_cell_cache_match(void *target,
-                                               const void *entry)
+static uint16_t afs_cell_cache_get_key(const void *cookie_netfs_data,
+                                      void *buffer, uint16_t bufmax)
 {
-       const struct afs_cache_cell *ccell = entry;
-       struct afs_cell *cell = target;
+       const struct afs_cell *cell = cookie_netfs_data;
+       uint16_t klen;
 
-       _enter("{%s},{%s}", ccell->name, cell->name);
+       _enter("%p,%p,%u", cell, buffer, bufmax);
 
-       if (strncmp(ccell->name, cell->name, sizeof(ccell->name)) == 0) {
-               _leave(" = SUCCESS");
-               return CACHEFS_MATCH_SUCCESS;
-       }
+       klen = strlen(cell->name);
+       if (klen > bufmax)
+               return 0;
 
-       _leave(" = FAILED");
-       return CACHEFS_MATCH_FAILED;
+       memcpy(buffer, cell->name, klen);
+       return klen;
 }
-#endif
 
 /*
- * update a cell record in the cache
+ * provide new auxilliary cache data
  */
-#ifdef AFS_CACHING_SUPPORT
-static void afs_cell_cache_update(void *source, void *entry)
+static uint16_t afs_cell_cache_get_aux(const void *cookie_netfs_data,
+                                      void *buffer, uint16_t bufmax)
 {
-       struct afs_cache_cell *ccell = entry;
-       struct afs_cell *cell = source;
+       const struct afs_cell *cell = cookie_netfs_data;
+       uint16_t dlen;
 
-       _enter("%p,%p", source, entry);
+       _enter("%p,%p,%u", cell, buffer, bufmax);
 
-       strncpy(ccell->name, cell->name, sizeof(ccell->name));
+       dlen = cell->vl_naddrs * sizeof(cell->vl_addrs[0]);
+       dlen = min(dlen, bufmax);
+       dlen &= ~(sizeof(cell->vl_addrs[0]) - 1);
 
-       memcpy(ccell->vl_servers,
-              cell->vl_addrs,
-              min(sizeof(ccell->vl_servers), sizeof(cell->vl_addrs)));
+       memcpy(buffer, cell->vl_addrs, dlen);
+       return dlen;
+}
 
+/*
+ * check that the auxilliary data indicates that the entry is still valid
+ */
+static enum fscache_checkaux afs_cell_cache_check_aux(void *cookie_netfs_data,
+                                                     const void *buffer,
+                                                     uint16_t buflen)
+{
+       _leave(" = OKAY");
+       return FSCACHE_CHECKAUX_OKAY;
 }
-#endif
-
-#ifdef AFS_CACHING_SUPPORT
-static cachefs_match_val_t afs_vlocation_cache_match(void *target,
-                                                    const void *entry);
-static void afs_vlocation_cache_update(void *source, void *entry);
-
-struct cachefs_index_def afs_vlocation_cache_index_def = {
-       .name           = "vldb",
-       .data_size      = sizeof(struct afs_cache_vlocation),
-       .keys[0]        = { CACHEFS_INDEX_KEYS_ASCIIZ, 64 },
-       .match          = afs_vlocation_cache_match,
-       .update         = afs_vlocation_cache_update,
-};
-#endif
 
+/*****************************************************************************/
 /*
- * match a VLDB record stored in the cache
- * - may also load target from entry
+ * set the key for the index entry
  */
-#ifdef AFS_CACHING_SUPPORT
-static cachefs_match_val_t afs_vlocation_cache_match(void *target,
-                                                    const void *entry)
+static uint16_t afs_vlocation_cache_get_key(const void *cookie_netfs_data,
+                                           void *buffer, uint16_t bufmax)
 {
-       const struct afs_cache_vlocation *vldb = entry;
-       struct afs_vlocation *vlocation = target;
+       const struct afs_vlocation *vlocation = cookie_netfs_data;
+       uint16_t klen;
+
+       _enter("{%s},%p,%u", vlocation->vldb.name, buffer, bufmax);
+
+       klen = strnlen(vlocation->vldb.name, sizeof(vlocation->vldb.name));
+       if (klen > bufmax)
+               return 0;
 
-       _enter("{%s},{%s}", vlocation->vldb.name, vldb->name);
+       memcpy(buffer, vlocation->vldb.name, klen);
 
-       if (strncmp(vlocation->vldb.name, vldb->name, sizeof(vldb->name)) == 0
-           ) {
-               if (!vlocation->valid ||
-                   vlocation->vldb.rtime == vldb->rtime
+       _leave(" = %u", klen);
+       return klen;
+}
+
+/*
+ * provide new auxilliary cache data
+ */
+static uint16_t afs_vlocation_cache_get_aux(const void *cookie_netfs_data,
+                                           void *buffer, uint16_t bufmax)
+{
+       const struct afs_vlocation *vlocation = cookie_netfs_data;
+       uint16_t dlen;
+
+       _enter("{%s},%p,%u", vlocation->vldb.name, buffer, bufmax);
+
+       dlen = sizeof(struct afs_cache_vlocation);
+       dlen -= offsetof(struct afs_cache_vlocation, nservers);
+       if (dlen > bufmax)
+               return 0;
+
+       memcpy(buffer, (uint8_t *)&vlocation->vldb.nservers, dlen);
+
+       _leave(" = %u", dlen);
+       return dlen;
+}
+
+/*
+ * check that the auxilliary data indicates that the entry is still valid
+ */
+static
+enum fscache_checkaux afs_vlocation_cache_check_aux(void *cookie_netfs_data,
+                                                   const void *buffer,
+                                                   uint16_t buflen)
+{
+       const struct afs_cache_vlocation *cvldb;
+       struct afs_vlocation *vlocation = cookie_netfs_data;
+       uint16_t dlen;
+
+       _enter("{%s},%p,%u", vlocation->vldb.name, buffer, buflen);
+
+       /* check the size of the data is what we're expecting */
+       dlen = sizeof(struct afs_cache_vlocation);
+       dlen -= offsetof(struct afs_cache_vlocation, nservers);
+       if (dlen != buflen)
+               return FSCACHE_CHECKAUX_OBSOLETE;
+
+       cvldb = container_of(buffer, struct afs_cache_vlocation, nservers);
+
+       /* if what's on disk is more valid than what's in memory, then use the
+        * VL record from the cache */
+       if (!vlocation->valid || vlocation->vldb.rtime == cvldb->rtime) {
+               memcpy((uint8_t *)&vlocation->vldb.nservers, buffer, dlen);
+               vlocation->valid = 1;
+               _leave(" = SUCCESS [c->m]");
+               return FSCACHE_CHECKAUX_OKAY;
+       }
+
+       /* need to update the cache if the cached info differs */
+       if (memcmp(&vlocation->vldb, buffer, dlen) != 0) {
+               /* delete if the volume IDs for this name differ */
+               if (memcmp(&vlocation->vldb.vid, &cvldb->vid,
+                          sizeof(cvldb->vid)) != 0
                    ) {
-                       vlocation->vldb = *vldb;
-                       vlocation->valid = 1;
-                       _leave(" = SUCCESS [c->m]");
-                       return CACHEFS_MATCH_SUCCESS;
-               } else if (memcmp(&vlocation->vldb, vldb, sizeof(*vldb)) != 0) {
-                       /* delete if VIDs for this name differ */
-                       if (memcmp(&vlocation->vldb.vid,
-                                  &vldb->vid,
-                                  sizeof(vldb->vid)) != 0) {
-                               _leave(" = DELETE");
-                               return CACHEFS_MATCH_SUCCESS_DELETE;
-                       }
-
-                       _leave(" = UPDATE");
-                       return CACHEFS_MATCH_SUCCESS_UPDATE;
-               } else {
-                       _leave(" = SUCCESS");
-                       return CACHEFS_MATCH_SUCCESS;
+                       _leave(" = OBSOLETE");
+                       return FSCACHE_CHECKAUX_OBSOLETE;
                }
+
+               _leave(" = UPDATE");
+               return FSCACHE_CHECKAUX_NEEDS_UPDATE;
        }
 
-       _leave(" = FAILED");
-       return CACHEFS_MATCH_FAILED;
+       _leave(" = OKAY");
+       return FSCACHE_CHECKAUX_OKAY;
 }
-#endif
 
+/*****************************************************************************/
 /*
- * update a VLDB record stored in the cache
+ * set the key for the volume index entry
  */
-#ifdef AFS_CACHING_SUPPORT
-static void afs_vlocation_cache_update(void *source, void *entry)
+static uint16_t afs_volume_cache_get_key(const void *cookie_netfs_data,
+                                       void *buffer, uint16_t bufmax)
 {
-       struct afs_cache_vlocation *vldb = entry;
-       struct afs_vlocation *vlocation = source;
+       const struct afs_volume *volume = cookie_netfs_data;
+       uint16_t klen;
+
+       _enter("{%u},%p,%u", volume->type, buffer, bufmax);
+
+       klen = sizeof(volume->type);
+       if (klen > bufmax)
+               return 0;
 
-       _enter("");
+       memcpy(buffer, &volume->type, sizeof(volume->type));
+
+       _leave(" = %u", klen);
+       return klen;
 
-       *vldb = vlocation->vldb;
 }
-#endif
-
-#ifdef AFS_CACHING_SUPPORT
-static cachefs_match_val_t afs_volume_cache_match(void *target,
-                                                 const void *entry);
-static void afs_volume_cache_update(void *source, void *entry);
-
-struct cachefs_index_def afs_volume_cache_index_def = {
-       .name           = "volume",
-       .data_size      = sizeof(struct afs_cache_vhash),
-       .keys[0]        = { CACHEFS_INDEX_KEYS_BIN, 1 },
-       .keys[1]        = { CACHEFS_INDEX_KEYS_BIN, 1 },
-       .match          = afs_volume_cache_match,
-       .update         = afs_volume_cache_update,
-};
-#endif
 
+/*****************************************************************************/
 /*
- * match a volume hash record stored in the cache
+ * set the key for the index entry
  */
-#ifdef AFS_CACHING_SUPPORT
-static cachefs_match_val_t afs_volume_cache_match(void *target,
-                                                 const void *entry)
+static uint16_t afs_vnode_cache_get_key(const void *cookie_netfs_data,
+                                       void *buffer, uint16_t bufmax)
 {
-       const struct afs_cache_vhash *vhash = entry;
-       struct afs_volume *volume = target;
+       const struct afs_vnode *vnode = cookie_netfs_data;
+       uint16_t klen;
 
-       _enter("{%u},{%u}", volume->type, vhash->vtype);
+       _enter("{%x,%x,%llx},%p,%u",
+              vnode->fid.vnode, vnode->fid.unique, vnode->status.data_version,
+              buffer, bufmax);
 
-       if (volume->type == vhash->vtype) {
-               _leave(" = SUCCESS");
-               return CACHEFS_MATCH_SUCCESS;
-       }
+       klen = sizeof(vnode->fid.vnode);
+       if (klen > bufmax)
+               return 0;
+
+       memcpy(buffer, &vnode->fid.vnode, sizeof(vnode->fid.vnode));
 
-       _leave(" = FAILED");
-       return CACHEFS_MATCH_FAILED;
+       _leave(" = %u", klen);
+       return klen;
 }
-#endif
 
 /*
- * update a volume hash record stored in the cache
+ * provide updated file attributes
  */
-#ifdef AFS_CACHING_SUPPORT
-static void afs_volume_cache_update(void *source, void *entry)
+static void afs_vnode_cache_get_attr(const void *cookie_netfs_data,
+                                    uint64_t *size)
 {
-       struct afs_cache_vhash *vhash = entry;
-       struct afs_volume *volume = source;
+       const struct afs_vnode *vnode = cookie_netfs_data;
 
-       _enter("");
+       _enter("{%x,%x,%llx},",
+              vnode->fid.vnode, vnode->fid.unique,
+              vnode->status.data_version);
 
-       vhash->vtype = volume->type;
+       *size = vnode->status.size;
 }
-#endif
-
-#ifdef AFS_CACHING_SUPPORT
-static cachefs_match_val_t afs_vnode_cache_match(void *target,
-                                                const void *entry);
-static void afs_vnode_cache_update(void *source, void *entry);
-
-struct cachefs_index_def afs_vnode_cache_index_def = {
-       .name           = "vnode",
-       .data_size      = sizeof(struct afs_cache_vnode),
-       .keys[0]        = { CACHEFS_INDEX_KEYS_BIN, 4 },
-       .match          = afs_vnode_cache_match,
-       .update         = afs_vnode_cache_update,
-};
-#endif
 
 /*
- * match a vnode record stored in the cache
+ * provide new auxilliary cache data
+ */
+static uint16_t afs_vnode_cache_get_aux(const void *cookie_netfs_data,
+                                       void *buffer, uint16_t bufmax)
+{
+       const struct afs_vnode *vnode = cookie_netfs_data;
+       uint16_t dlen;
+
+       _enter("{%x,%x,%Lx},%p,%u",
+              vnode->fid.vnode, vnode->fid.unique, vnode->status.data_version,
+              buffer, bufmax);
+
+       dlen = sizeof(vnode->fid.unique) + sizeof(vnode->status.data_version);
+       if (dlen > bufmax)
+               return 0;
+
+       memcpy(buffer, &vnode->fid.unique, sizeof(vnode->fid.unique));
+       buffer += sizeof(vnode->fid.unique);
+       memcpy(buffer, &vnode->status.data_version,
+              sizeof(vnode->status.data_version));
+
+       _leave(" = %u", dlen);
+       return dlen;
+}
+
+/*
+ * check that the auxilliary data indicates that the entry is still valid
  */
-#ifdef AFS_CACHING_SUPPORT
-static cachefs_match_val_t afs_vnode_cache_match(void *target,
-                                                const void *entry)
+static enum fscache_checkaux afs_vnode_cache_check_aux(void *cookie_netfs_data,
+                                                      const void *buffer,
+                                                      uint16_t buflen)
 {
-       const struct afs_cache_vnode *cvnode = entry;
-       struct afs_vnode *vnode = target;
-
-       _enter("{%x,%x,%Lx},{%x,%x,%Lx}",
-              vnode->fid.vnode,
-              vnode->fid.unique,
-              vnode->status.version,
-              cvnode->vnode_id,
-              cvnode->vnode_unique,
-              cvnode->data_version);
-
-       if (vnode->fid.vnode != cvnode->vnode_id) {
-               _leave(" = FAILED");
-               return CACHEFS_MATCH_FAILED;
+       struct afs_vnode *vnode = cookie_netfs_data;
+       uint16_t dlen;
+
+       _enter("{%x,%x,%llx},%p,%u",
+              vnode->fid.vnode, vnode->fid.unique, vnode->status.data_version,
+              buffer, buflen);
+
+       /* check the size of the data is what we're expecting */
+       dlen = sizeof(vnode->fid.unique) + sizeof(vnode->status.data_version);
+       if (dlen != buflen) {
+               _leave(" = OBSOLETE [len %hx != %hx]", dlen, buflen);
+               return FSCACHE_CHECKAUX_OBSOLETE;
        }
 
-       if (vnode->fid.unique != cvnode->vnode_unique ||
-           vnode->status.version != cvnode->data_version) {
-               _leave(" = DELETE");
-               return CACHEFS_MATCH_SUCCESS_DELETE;
+       if (memcmp(buffer,
+                  &vnode->fid.unique,
+                  sizeof(vnode->fid.unique)
+                  ) != 0) {
+               unsigned unique;
+
+               memcpy(&unique, buffer, sizeof(unique));
+
+               _leave(" = OBSOLETE [uniq %x != %x]",
+                      unique, vnode->fid.unique);
+               return FSCACHE_CHECKAUX_OBSOLETE;
+       }
+
+       if (memcmp(buffer + sizeof(vnode->fid.unique),
+                  &vnode->status.data_version,
+                  sizeof(vnode->status.data_version)
+                  ) != 0) {
+               afs_dataversion_t version;
+
+               memcpy(&version, buffer + sizeof(vnode->fid.unique),
+                      sizeof(version));
+
+               _leave(" = OBSOLETE [vers %llx != %llx]",
+                      version, vnode->status.data_version);
+               return FSCACHE_CHECKAUX_OBSOLETE;
        }
 
        _leave(" = SUCCESS");
-       return CACHEFS_MATCH_SUCCESS;
+       return FSCACHE_CHECKAUX_OKAY;
 }
-#endif
 
 /*
- * update a vnode record stored in the cache
+ * indication the cookie is no longer uncached
+ * - this function is called when the backing store currently caching a cookie
+ *   is removed
+ * - the netfs should use this to clean up any markers indicating cached pages
+ * - this is mandatory for any object that may have data
  */
-#ifdef AFS_CACHING_SUPPORT
-static void afs_vnode_cache_update(void *source, void *entry)
+static void afs_vnode_cache_now_uncached(void *cookie_netfs_data)
 {
-       struct afs_cache_vnode *cvnode = entry;
-       struct afs_vnode *vnode = source;
+       struct afs_vnode *vnode = cookie_netfs_data;
+       struct pagevec pvec;
+       pgoff_t first;
+       int loop, nr_pages;
+
+       _enter("{%x,%x,%Lx}",
+              vnode->fid.vnode, vnode->fid.unique, vnode->status.data_version);
+
+       pagevec_init(&pvec, 0);
+       first = 0;
+
+       for (;;) {
+               /* grab a bunch of pages to clean */
+               nr_pages = pagevec_lookup(&pvec, vnode->vfs_inode.i_mapping,
+                                         first,
+                                         PAGEVEC_SIZE - pagevec_count(&pvec));
+               if (!nr_pages)
+                       break;
 
-       _enter("");
+               for (loop = 0; loop < nr_pages; loop++)
+                       ClearPageFsCache(pvec.pages[loop]);
+
+               first = pvec.pages[nr_pages - 1]->index + 1;
+
+               pvec.nr = nr_pages;
+               pagevec_release(&pvec);
+               cond_resched();
+       }
 
-       cvnode->vnode_id        = vnode->fid.vnode;
-       cvnode->vnode_unique    = vnode->fid.unique;
-       cvnode->data_version    = vnode->status.version;
+       _leave("");
 }
-#endif
 
 static int afs_releasepage(struct page *page, gfp_t gfp_flags);
 static int afs_launder_page(struct page *page);
 
+static int afs_readpages(struct file *filp, struct address_space *mapping,
+                        struct list_head *pages, unsigned nr_pages);
+
 const struct file_operations afs_file_operations = {
        .open           = afs_open,
        .release        = afs_release,
 
 const struct address_space_operations afs_fs_aops = {
        .readpage       = afs_readpage,
+       .readpages      = afs_readpages,
        .set_page_dirty = afs_set_page_dirty,
        .launder_page   = afs_launder_page,
        .releasepage    = afs_releasepage,
 /*
  * deal with notification that a page was read from the cache
  */
-#ifdef AFS_CACHING_SUPPORT
-static void afs_readpage_read_complete(void *cookie_data,
-                                      struct page *page,
-                                      void *data,
-                                      int error)
+static void afs_file_readpage_read_complete(struct page *page,
+                                           void *data,
+                                           int error)
 {
-       _enter("%p,%p,%p,%d", cookie_data, page, data, error);
+       _enter("%p,%p,%d", page, data, error);
 
-       if (error)
-               SetPageError(page);
-       else
+       /* if the read completes with an error, we just unlock the page and let
+        * the VM reissue the readpage */
+       if (!error)
                SetPageUptodate(page);
        unlock_page(page);
-
 }
-#endif
-
-/*
- * deal with notification that a page was written to the cache
- */
-#ifdef AFS_CACHING_SUPPORT
-static void afs_readpage_write_complete(void *cookie_data,
-                                       struct page *page,
-                                       void *data,
-                                       int error)
-{
-       _enter("%p,%p,%p,%d", cookie_data, page, data, error);
-
-       unlock_page(page);
-}
-#endif
 
 /*
  * AFS read page from file, directory or symlink
        if (test_bit(AFS_VNODE_DELETED, &vnode->flags))
                goto error;
 
-#ifdef AFS_CACHING_SUPPORT
        /* is it cached? */
-       ret = cachefs_read_or_alloc_page(vnode->cache,
+#ifdef CONFIG_AFS_FSCACHE
+       ret = fscache_read_or_alloc_page(vnode->cache,
                                         page,
                                         afs_file_readpage_read_complete,
                                         NULL,
 #else
        ret = -ENOBUFS;
 #endif
-
        switch (ret) {
-               /* read BIO submitted and wb-journal entry found */
-       case 1:
-               BUG(); // TODO - handle wb-journal match
-
                /* read BIO submitted (page in cache) */
        case 0:
                break;
 
-               /* no page available in cache */
-       case -ENOBUFS:
+               /* page not yet cached */
        case -ENODATA:
+               _debug("cache said ENODATA");
+               goto go_on;
+
+               /* page will not be cached */
+       case -ENOBUFS:
+               _debug("cache said ENOBUFS");
        default:
+       go_on:
                offset = page->index << PAGE_CACHE_SHIFT;
                len = min_t(size_t, i_size_read(inode) - offset, PAGE_SIZE);
 
                                set_bit(AFS_VNODE_DELETED, &vnode->flags);
                                ret = -ESTALE;
                        }
-#ifdef AFS_CACHING_SUPPORT
-                       cachefs_uncache_page(vnode->cache, page);
+
+#ifdef CONFIG_AFS_FSCACHE
+                       fscache_uncache_page(vnode->cache, page);
 #endif
+                       BUG_ON(PageFsCache(page));
                        goto error;
                }
 
                SetPageUptodate(page);
 
-#ifdef AFS_CACHING_SUPPORT
-               if (cachefs_write_page(vnode->cache,
-                                      page,
-                                      afs_file_readpage_write_complete,
-                                      NULL,
-                                      GFP_KERNEL) != 0
-                   ) {
-                       cachefs_uncache_page(vnode->cache, page);
-                       unlock_page(page);
+               /* send the page to the cache */
+#ifdef CONFIG_AFS_FSCACHE
+               if (PageFsCache(page) &&
+                   fscache_write_page(vnode->cache, page, GFP_KERNEL) != 0) {
+                       fscache_uncache_page(vnode->cache, page);
+                       BUG_ON(PageFsCache(page));
                }
-#else
-               unlock_page(page);
 #endif
+               unlock_page(page);
        }
 
        _leave(" = 0");
 }
 
 /*
- * invalidate part or all of a page
+ * read a set of pages
  */
-static void afs_invalidatepage(struct page *page, unsigned long offset)
+static int afs_readpages(struct file *file, struct address_space *mapping,
+                        struct list_head *pages, unsigned nr_pages)
 {
-       int ret = 1;
+       struct afs_vnode *vnode;
+       int ret = 0;
 
-       _enter("{%lu},%lu", page->index, offset);
+       _enter(",{%lu},,%d", mapping->host->i_ino, nr_pages);
 
-       BUG_ON(!PageLocked(page));
+       vnode = AFS_FS_I(mapping->host);
+       if (vnode->flags & AFS_VNODE_DELETED) {
+               _leave(" = -ESTALE");
+               return -ESTALE;
+       }
 
-       if (PagePrivate(page)) {
-               /* We release buffers only if the entire page is being
-                * invalidated.
-                * The get_block cached value has been unconditionally
-                * invalidated, so real IO is not possible anymore.
-                */
-               if (offset == 0) {
-                       BUG_ON(!PageLocked(page));
-
-                       ret = 0;
-                       if (!PageWriteback(page))
-                               ret = page->mapping->a_ops->releasepage(page,
-                                                                       0);
-                       /* possibly should BUG_ON(!ret); - neilb */
-               }
+       /* attempt to read as many of the pages as possible */
+#ifdef CONFIG_AFS_FSCACHE
+       ret = fscache_read_or_alloc_pages(vnode->cache,
+                                         mapping,
+                                         pages,
+                                         &nr_pages,
+                                         afs_file_readpage_read_complete,
+                                         NULL,
+                                         mapping_gfp_mask(mapping));
+#else
+       ret = -ENOBUFS;
+#endif
+
+       switch (ret) {
+               /* all pages are being read from the cache */
+       case 0:
+               BUG_ON(!list_empty(pages));
+               BUG_ON(nr_pages != 0);
+               _leave(" = 0 [reading all]");
+               return 0;
+
+               /* there were pages that couldn't be read from the cache */
+       case -ENODATA:
+       case -ENOBUFS:
+               break;
+
+               /* other error */
+       default:
+               _leave(" = %d", ret);
+               return ret;
        }
 
-       _leave(" = %d", ret);
+       /* load the missing pages from the network */
+       ret = read_cache_pages(mapping, pages, (void *) afs_readpage, file);
+
+       _leave(" = %d [netting]", ret);
+       return ret;
 }
 
 /*
 }
 
 /*
- * release a page and cleanup its private data
+ * invalidate part or all of a page
+ * - release a page and clean up its private data if offset is 0 (indicating
+ *   the entire page)
+ */
+static void afs_invalidatepage(struct page *page, unsigned long offset)
+{
+       struct afs_writeback *wb = (struct afs_writeback *) page_private(page);
+
+       _enter("{%lu},%lu", page->index, offset);
+
+       BUG_ON(!PageLocked(page));
+
+       /* we clean up only if the entire page is being invalidated */
+       if (offset == 0) {
+#ifdef CONFIG_AFS_FSCACHE
+               if (PageFsCache(page)) {
+                       struct afs_vnode *vnode = AFS_FS_I(page->mapping->host);
+                       fscache_wait_on_page_write(vnode->cache, page);
+                       fscache_uncache_page(vnode->cache, page);
+                       ClearPageFsCache(page);
+               }
+#endif
+
+               if (PagePrivate(page)) {
+                       if (wb && !PageWriteback(page)) {
+                               set_page_private(page, 0);
+                               afs_put_writeback(wb);
+                       }
+
+                       if (!page_private(page))
+                               ClearPagePrivate(page);
+               }
+       }
+
+       _leave("");
+}
+
+/*
+ * release a page and clean up its private state if it's not busy
+ * - return true if the page can now be released, false if not
  */
 static int afs_releasepage(struct page *page, gfp_t gfp_flags)
 {
+       struct afs_writeback *wb = (struct afs_writeback *) page_private(page);
        struct afs_vnode *vnode = AFS_FS_I(page->mapping->host);
-       struct afs_writeback *wb;
 
        _enter("{{%x:%u}[%lu],%lx},%x",
               vnode->fid.vid, vnode->fid.vnode, page->index, page->flags,
               gfp_flags);
 
+       /* deny if page is being written to the cache and the caller hasn't
+        * elected to wait */
+#ifdef CONFIG_AFS_FSCACHE
+       if (PageFsCache(page)) {
+               if (fscache_check_page_write(vnode->cache, page)) {
+                       if (!(gfp_flags & __GFP_WAIT)) {
+                               _leave(" = F [cache busy]");
+                               return 0;
+                       }
+                       fscache_wait_on_page_write(vnode->cache, page);
+               }
+
+               fscache_uncache_page(vnode->cache, page);
+               ClearPageFsCache(page);
+       }
+#endif
+
        if (PagePrivate(page)) {
-               wb = (struct afs_writeback *) page_private(page);
-               ASSERT(wb != NULL);
-               set_page_private(page, 0);
+               if (wb) {
+                       set_page_private(page, 0);
+                       afs_put_writeback(wb);
+               }
                ClearPagePrivate(page);
-               afs_put_writeback(wb);
        }
 
-       _leave(" = 0");
-       return 0;
+       /* indicate that the page can be released */
+       _leave(" = T");
+       return 1;
 }