From: David Howells The attached patch splits the generic part of CacheFS out as FS-Cache - a general cache manager. FS-Cache now mediates between cache backends (such as CacheFS) and network filesystems: +---------+ | | +-----------+ | NFS |--+ | | | | | +-->| CacheFS | +---------+ | +----------+ | | /dev/hda5 | | | | | +-----------+ +---------+ +-->| | | | | | |--+ +-------------+ | AFS |----->| FS-Cache | | | | | | |----->| Cache Files | +---------+ +-->| | | /var/cache | | | |--+ +-------------+ +---------+ | +----------+ | | | | | +-------------+ | ISOFS |--+ | | | | | +-->| ReiserCache | +---------+ | / | +-------------+ This will allow cache backends other than CacheFS to be added to the system without any need to change the netfs's that use FS-Cache. Signed-Off-By: David Howells Signed-off-by: Andrew Morton --- 25-akpm/fs/Kconfig | 15 25-akpm/fs/Makefile | 1 25-akpm/fs/fscache/Makefile | 13 25-akpm/fs/fscache/cookie.c | 973 ++++++++++++++++++++++++++++++++++ 25-akpm/fs/fscache/fscache-int.h | 81 ++ 25-akpm/fs/fscache/fsdef.c | 90 +++ 25-akpm/fs/fscache/main.c | 111 +++ 25-akpm/fs/fscache/page.c | 231 ++++++++ 25-akpm/include/linux/fscache-cache.h | 205 +++++++ 25-akpm/include/linux/fscache.h | 357 ++++++++++++ 10 files changed, 2075 insertions(+), 2 deletions(-) diff -puN /dev/null fs/fscache/cookie.c --- /dev/null 2003-09-15 06:40:47.000000000 -0700 +++ 25-akpm/fs/fscache/cookie.c 2004-12-03 20:56:45.242882224 -0800 @@ -0,0 +1,973 @@ +/* cookie.c: general filesystem cache cookie management + * + * Copyright (C) 2004 Red Hat, Inc. All Rights Reserved. + * Written by David Howells (dhowells@redhat.com) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + */ + +#include +#include "fscache-int.h" + +LIST_HEAD(fscache_netfs_list); +LIST_HEAD(fscache_cache_list); +DECLARE_RWSEM(fscache_addremove_sem); + +kmem_cache_t *fscache_cookie_jar; + +static void fscache_withdraw_node(struct fscache_cache *cache, + struct fscache_node *node); + +/*****************************************************************************/ +/* + * register a network filesystem for caching + */ +int __fscache_register_netfs(struct fscache_netfs *netfs, + struct fscache_index_def *primary_idef) +{ + struct fscache_netfs *ptr; + int ret; + + _enter("{%s}", netfs->name); + + INIT_LIST_HEAD(&netfs->link); + + /* allocate a cookie for the primary index */ + netfs->primary_index = + kmem_cache_alloc(fscache_cookie_jar, SLAB_KERNEL); + + if (!netfs->primary_index) { + _leave(" = -ENOMEM"); + return -ENOMEM; + } + + /* initialise the primary index cookie */ + memset(netfs->primary_index, 0, sizeof(*netfs->primary_index)); + + atomic_set(&netfs->primary_index->usage, 1); + atomic_set(&netfs->primary_index->children, 0); + + netfs->primary_index->idef = primary_idef; + netfs->primary_index->iparent = &fscache_fsdef_index; + netfs->primary_index->netfs = netfs; + netfs->primary_index->netfs_data = netfs; + + atomic_inc(&netfs->primary_index->iparent->usage); + atomic_inc(&netfs->primary_index->iparent->children); + + rwlock_init(&netfs->primary_index->lock); + init_rwsem(&netfs->primary_index->sem); + INIT_LIST_HEAD(&netfs->primary_index->search_results); + INIT_LIST_HEAD(&netfs->primary_index->backing_nodes); + + /* check the netfs type is not already present */ + down_write(&fscache_addremove_sem); + + ret = -EEXIST; + list_for_each_entry(ptr, &fscache_netfs_list, link) { + if (strcmp(ptr->name, netfs->name) == 0) + goto already_registered; + } + + list_add(&netfs->link, &fscache_netfs_list); + ret = 0; + + printk("FS-Cache: netfs '%s' registered for caching\n", netfs->name); + + already_registered: + up_write(&fscache_addremove_sem); + + if (ret < 0) { + netfs->primary_index->iparent = NULL; + __fscache_cookie_put(netfs->primary_index); + netfs->primary_index = NULL; + } + + _leave(" = %d", ret); + return ret; + +} /* end __fscache_register_netfs() */ + +EXPORT_SYMBOL(__fscache_register_netfs); + +/*****************************************************************************/ +/* + * unregister a network filesystem from the cache + * - all cookies must have been released first + */ +void __fscache_unregister_netfs(struct fscache_netfs *netfs) +{ + _enter("{%s.%u}", netfs->name, netfs->version); + + down_write(&fscache_addremove_sem); + + list_del(&netfs->link); + fscache_relinquish_cookie(netfs->primary_index, 0); + + up_write(&fscache_addremove_sem); + + printk("FS-Cache: netfs '%s' unregistered from caching\n", netfs->name); + + _leave(""); + +} /* end __fscache_unregister_netfs() */ + +EXPORT_SYMBOL(__fscache_unregister_netfs); + +/*****************************************************************************/ +/* + * initialise a cache record + */ +void fscache_init_cache(struct fscache_cache *cache, + struct fscache_cache_ops *ops, + unsigned fsdef_ino, + const char *idfmt, + ...) +{ + va_list va; + + memset(cache, 0, sizeof(*cache)); + + cache->ops = ops; + + va_start(va, idfmt); + vsnprintf(cache->identifier, sizeof(cache->identifier), idfmt, va); + va_end(va); + + INIT_LIST_HEAD(&cache->link); + INIT_LIST_HEAD(&cache->node_list); + spin_lock_init(&cache->node_list_lock); + + INIT_LIST_HEAD(&cache->fsdef_srch.link); + cache->fsdef_srch.cache = cache; + cache->fsdef_srch.ino = fsdef_ino; + +} /* end fscache_init_cache() */ + +EXPORT_SYMBOL(fscache_init_cache); + +/*****************************************************************************/ +/* + * declare a mounted cache as being open for business + */ +void fscache_add_cache(struct fscache_cache *cache) +{ + struct fscache_node *ifsdef; + + BUG_ON(!cache->ops); + + _enter("{%s.%s}", cache->ops->name, cache->identifier); + + /* prepare an active-node record for the FSDEF index of this cache */ + ifsdef = cache->ops->lookup_node(cache, cache->fsdef_srch.ino); + BUG_ON(IS_ERR(ifsdef)); /* there shouldn't be an error as FSDEF is the + * root dir of the FS and so should already be + * in core */ + + if (!cache->ops->grab_node(ifsdef)) + BUG(); + + ifsdef->cookie = &fscache_fsdef_index; + + down_write(&fscache_addremove_sem); + + /* add the cache to the list */ + list_add(&cache->link, &fscache_cache_list); + + /* add the cache's netfs definition index node to the cache's + * list */ + spin_lock(&cache->node_list_lock); + list_add_tail(&ifsdef->cache_link, &cache->node_list); + spin_unlock(&cache->node_list_lock); + + /* add the cache's netfs definition index node to the top level index + * cookie as a known backing node */ + down_write(&fscache_fsdef_index.sem); + + list_add_tail(&cache->fsdef_srch.link, + &fscache_fsdef_index.search_results); + list_add_tail(&ifsdef->cookie_link, + &fscache_fsdef_index.backing_nodes); + + atomic_inc(&fscache_fsdef_index.usage); + + /* done */ + up_write(&fscache_fsdef_index.sem); + up_write(&fscache_addremove_sem); + _leave(""); + +} /* end fscache_add_cache() */ + +EXPORT_SYMBOL(fscache_add_cache); + +/*****************************************************************************/ +/* + * withdraw an unmounted cache from the active service + */ +void fscache_withdraw_cache(struct fscache_cache *cache) +{ + struct fscache_node *node; + + _enter(""); + + /* make the cache unavailable for cookie acquisition */ + set_bit(FSCACHE_CACHE_WITHDRAWN, &cache->flags); + + down_write(&fscache_addremove_sem); + list_del_init(&cache->link); + up_write(&fscache_addremove_sem); + + /* mark all nodes as being withdrawn */ + spin_lock(&cache->node_list_lock); + list_for_each_entry(node, &cache->node_list, cache_link) { + set_bit(FSCACHE_NODE_WITHDRAWN, &node->flags); + } + spin_unlock(&cache->node_list_lock); + + /* make sure all pages pinned by operations on behalf of the netfs are + * written to disc */ + cache->ops->sync(cache); + + /* dissociate all the netfs pages backed by this cache from the block + * mappings in the cache */ + cache->ops->dissociate_pages(cache); + + /* we now have to destroy all the active nodes pertaining to this + * cache */ + spin_lock(&cache->node_list_lock); + + while (!list_empty(&cache->node_list)) { + node = list_entry(cache->node_list.next, struct fscache_node, + cache_link); + list_del(&node->cache_link); + spin_unlock(&cache->node_list_lock); + + /* we've extracted an active node from the tree - now dispose + * of it */ + fscache_withdraw_node(cache, node); + cache->ops->put_node(node); + + spin_lock(&cache->node_list_lock); + } + + spin_unlock(&cache->node_list_lock); + + _leave(""); + +} /* end fscache_withdraw_cache() */ + +EXPORT_SYMBOL(fscache_withdraw_cache); + +/*****************************************************************************/ +/* + * withdraw a node from active service + * - need break the links to a cached object cookie + * - called under two situations: + * (1) recycler decides to reclaim an in-use node + * (2) a cache is unmounted + * - have to take care as the cookie can be being relinquished by the netfs + * simultaneously + * - the active node is pinned by the caller holding a refcount on it + */ +static void fscache_withdraw_node(struct fscache_cache *cache, + struct fscache_node *node) +{ + struct fscache_search_result *srch; + struct fscache_cookie *cookie, *xcookie = NULL; + + _enter(""); + + /* first of all we have to break the links between the node and the + * cookie + * - we have to hold both semaphores BUT we have to get the cookie sem + * FIRST + */ + cache->ops->lock_node(node); + + cookie = node->cookie; + if (cookie) { + /* pin the cookie so that is doesn't escape */ + atomic_inc(&cookie->usage); + + /* re-order the locks to avoid deadlock */ + cache->ops->unlock_node(node); + down_write(&cookie->sem); + cache->ops->lock_node(node); + + /* erase references from the node to the cookie */ + list_del_init(&node->cookie_link); + + xcookie = node->cookie; + node->cookie = NULL; + + /* delete the search result record for this node from the + * cookie's list */ + list_for_each_entry(srch, &cookie->search_results, link) { + if (srch->cache == cache) + goto found_record; + } + BUG(); + + found_record: + list_del_init(&srch->link); + + if (srch != &cache->fsdef_srch) { + dbgfree(srch); + kfree(srch); + } + + up_write(&cookie->sem); + } + + cache->ops->unlock_node(node); + + /* we've broken the links between cookie and node */ + if (xcookie) { + fscache_cookie_put(xcookie); + cache->ops->put_node(node); + } + + /* unpin the cookie */ + if (cookie) + fscache_cookie_put(cookie); + + _leave(""); + +} /* end fscache_withdraw_node() */ + +/*****************************************************************************/ +/* + * search for representation of an object in its parent cache + * - the cookie semaphore must be locked by the caller + * - returns -ENODATA if the object or one of its ancestors doesn't exist + */ +static int fscache_search_for_object(struct fscache_cookie *cookie, + struct fscache_cache *cache) +{ + struct fscache_search_result *srch; + struct fscache_cookie *iparent; + struct fscache_node *ipnode, *node; + int ret; + + iparent = cookie->iparent; + if (!iparent) { + /* FSDEF entries don't have a parent */ + _enter("{.fsdef},%s.%s", + cache->ops->name, cache->identifier); + BUG_ON(list_empty(&cookie->backing_nodes)); + BUG_ON(list_empty(&cookie->search_results)); + _leave(" = 0 [.fsdef]"); + return 0; + } + + _enter("{%s/%s},%s.%s", + iparent->idef->name, + cookie->idef ? (char *) cookie->idef->name : "", + cache->ops->name, cache->identifier); + + /* see if there's a search result for this object already */ + read_lock(&cookie->lock); + + list_for_each_entry(srch, &cookie->search_results, link) { + _debug("check entry %p x %p [ino %u]", + cookie, cache, srch->ino); + + if (srch->cache == cache) { + read_unlock(&cookie->lock); + _debug("found entry"); + + if (srch->ino) { + _leave(" = 0 [found ino %u]", srch->ino); + return 0; + } + + /* entry is negative */ + _leave(" = -ENODATA"); + return -ENODATA; + } + } + + read_unlock(&cookie->lock); + + /* allocate an initially negative entry for this object */ + _debug("alloc entry %p x %p", cookie, cache); + + srch = kmalloc(sizeof(*srch), GFP_KERNEL); + if (!srch) { + _leave(" = -ENOMEM"); + return -ENOMEM; + } + + srch->cache = cache; + srch->ino = 0; + INIT_LIST_HEAD(&srch->link); + + /* we need see if there's an entry for this cache in this object's + * parent index, so the first thing to do is to see if the parent index + * is represented on disc + */ + down_read(&iparent->sem); + + ret = fscache_search_for_object(iparent, cache); + if (ret < 0) { + if (ret != -ENODATA) + goto error; + + /* set a negative entry */ + list_add_tail(&srch->link, &cookie->search_results); + goto done; + } + + /* find the parent's backing node */ + read_lock(&iparent->lock); + list_for_each_entry(ipnode, &iparent->backing_nodes, cookie_link) { + if (ipnode->cache == cache) + goto found_parent_entry; + } + + BUG(); + + found_parent_entry: + read_unlock(&iparent->lock); + _debug("found_parent_entry"); + + /* search the parent index for a reference compatible with this + * object */ + ret = cache->ops->index_search(ipnode, cookie, srch); + switch (ret) { + default: + goto error; + + case 0: + /* found - allocate a node */ + node = cache->ops->lookup_node(cache, srch->ino); + if (IS_ERR(node)) { + ret = PTR_ERR(node); + goto error; + } + + cache->ops->lock_node(node); + + /* a node should only ever be attached to one cookie */ + BUG_ON(!list_empty(&node->cookie_link)); + + /* attach the node to the cache's node list */ + if (list_empty(&node->cache_link)) { + if (!cache->ops->grab_node(node)) + goto igrab_failed_upput; + + spin_lock(&cache->node_list_lock); + list_add_tail(&node->cache_link, &cache->node_list); + spin_unlock(&cache->node_list_lock); + } + + /* attach the node to the cookie */ + node->cookie = cookie; + atomic_inc(&cookie->usage); + + write_lock(&iparent->lock); + list_add_tail(&srch->link, &cookie->search_results); + list_add_tail(&node->cookie_link, &cookie->backing_nodes); + write_unlock(&iparent->lock); + + cache->ops->unlock_node(node); + break; + + case -ENOENT: + /* we can at least set a valid negative entry */ + list_add_tail(&srch->link, &cookie->search_results); + ret = -ENODATA; + break; + } + + done: + up_read(&iparent->sem); + _leave(" = %d", ret); + return ret; + + igrab_failed_upput: + cache->ops->unlock_node(node); + cache->ops->put_node(node); + ret = -ENOENT; + error: + up_read(&iparent->sem); + dbgfree(srch); + kfree(srch); + _leave(" = %d", ret); + return ret; + +} /* end fscache_search_for_object() */ + +/*****************************************************************************/ +/* + * instantiate the object in the specified cache + * - the cookie must be write-locked by the caller + * - search must have been performed first (so lists of search results are + * filled out) + * - all parent index objects are instantiated if necessary + */ +static int fscache_instantiate_object(struct fscache_cookie *cookie, + struct fscache_cache *cache) +{ + struct fscache_search_result *srch; + struct fscache_cookie *iparent; + struct fscache_node *ipnode, *node; + int ret; + + iparent = cookie->iparent; + if (!iparent) + return 0; /* FSDEF entries don't have a parent */ + + _enter("{%s/%s},", + iparent->idef->name, + cookie->idef ? (char *) cookie->idef->name : ""); + + /* find the search result for this object */ + list_for_each_entry(srch, &cookie->search_results, link) { + if (srch->cache == cache) + goto found_search_result; + } + + BUG(); + + found_search_result: + if (srch->ino) { + /* it was instantiated already */ + _leave(" = 0 [found ino %u]", srch->ino); + return 0; + } + + /* we need to insert an entry for this cache in the object's parent + * index, so the first thing to do is make sure that the parent index + * is represented on disc + */ + down_write(&iparent->sem); + + ret = fscache_instantiate_object(iparent, cache); + if (ret < 0) + goto error; + + /* the parent index's node should now be available */ + list_for_each_entry(ipnode, &iparent->backing_nodes, cookie_link) { + if (ipnode->cache == cache) + goto found_parent_node; + } + + BUG(); + + found_parent_node: + _debug("found_parent_node: node=%p", ipnode); + + BUG_ON(ipnode->cookie != iparent); + + /* allocate an entry within the parent index node */ + ret = cache->ops->index_add(ipnode, cookie, srch); + if (ret < 0) + goto error; + + /* we're going to need an in-memory reflection of the node too */ + node = cache->ops->lookup_node(cache, srch->ino); + if (IS_ERR(node)) { + ret = PTR_ERR(node); + goto error_x; /* uh-oh... our search record is now wrong */ + } + + /* keep track of it */ + cache->ops->lock_node(node); + + BUG_ON(!list_empty(&node->cookie_link)); + + /* attach to the cache's node list */ + if (list_empty(&node->cache_link)) { + if (!cache->ops->grab_node(node)) + goto error_xi; + + spin_lock(&cache->node_list_lock); + list_add_tail(&node->cache_link, &cache->node_list); + spin_unlock(&cache->node_list_lock); + } + + /* attach to the cookie's search result list */ + node->cookie = cookie; + atomic_inc(&cookie->usage); + list_add_tail(&node->cookie_link, &cookie->backing_nodes); + + /* done */ + cache->ops->unlock_node(node); + up_write(&iparent->sem); + _leave(" = 0 [new]"); + return 0; + + /* if we get an error after having instantiated a node on disc, just + * discard the search record so we find it next time */ + error_xi: + cache->ops->unlock_node(node); + cache->ops->put_node(node); + ret = -ENOENT; + error_x: + list_del(&srch->link); + dbgfree(srch); + kfree(srch); + srch = NULL; + error: + up_write(&iparent->sem); + _leave(" = %d", ret); + return ret; + +} /* end fscache_instantiate_object() */ + +/*****************************************************************************/ +/* + * select a cache on which to store a file + * - the cache addremove semaphore must be at least read-locked by the caller + */ +static struct fscache_cache *fscache_select_cache_for_file(void) +{ + struct fscache_cache *cache; + + _enter(""); + + /* TODO: make more intelligent than just choosing the first cache */ + cache = NULL; + if (!list_empty(&fscache_cache_list)) + cache = list_entry(fscache_cache_list.next, + struct fscache_cache, + link); + + _leave(" = %p", cache); + return cache; + +} /* end fscache_select_cache_for_file() */ + +/*****************************************************************************/ +/* + * request a cookie to represent a data file or an index + * - iparent specifies the parent index to pin in memory + * - the top level index cookie for each netfs is stored in the fscache_netfs + * struct upon registration + * - idef is NULL for a data file + * - idef points to the definition for an index + * - the netfs_data will be passed to the functions pointed to in *idef + * - all attached caches will be searched to see if they contain this object + * - index objects aren't stored on disc until there's a dependent file that + * needs storing + * - file objects are stored in a selected cache immediately, and all the + * indexes forming the path to it are instantiated if necessary + * - we never let on to the netfs about errors + * - we may set a negative cookie pointer, but that's okay + */ +struct fscache_cookie *__fscache_acquire_cookie(struct fscache_cookie *iparent, + struct fscache_index_def *idef, + void *netfs_data) +{ + struct fscache_cookie *cookie; + struct fscache_cache *cache; + int ret = 0; + + _enter("{%s},{%s},%p", + iparent ? (char *) iparent->idef->name : "", + idef ? (char *) idef->name : "", + netfs_data); + + /* if there's no parent cookie, then we don't create one here either */ + if (iparent == FSCACHE_NEGATIVE_COOKIE) { + _leave(" [no parent]"); + return FSCACHE_NEGATIVE_COOKIE; + } + + /* if it's going to be an index then validate the index data */ + if (idef) { + size_t dsize; + int loop; + + if (!idef->name[0]) { + printk("FS-Cache: %s.%s.%p: nameless index\n", + iparent->netfs->name, + iparent->idef->name, + idef); + return FSCACHE_NEGATIVE_COOKIE; + } + + dsize = idef->data_size; + + for (loop = 0; loop < 4; loop++) { + if (idef->keys[loop].type >= + FSCACHE_INDEX_KEYS__LAST) { + printk("FS-Cache: %s.%s.%s:" + " index type %u unsupported\n", + iparent->netfs->name, + iparent->idef->name, + idef->name, + idef->keys[loop].type); + return FSCACHE_NEGATIVE_COOKIE; + } + + dsize += idef->keys[loop].len; + } + + if (dsize > 400) { + printk("FS-Cache: %s.%s.%s:" + " index entry size exceeds maximum %u>400\n", + iparent->netfs->name, + iparent->idef->name, + idef->name, + dsize); + return FSCACHE_NEGATIVE_COOKIE; + } + } + + /* allocate and initialise a cookie */ + cookie = kmem_cache_alloc(fscache_cookie_jar, SLAB_KERNEL); + if (!cookie) { + _leave(" [ENOMEM]"); + return FSCACHE_NEGATIVE_COOKIE; + } + + atomic_set(&cookie->usage, 1); + atomic_set(&cookie->children, 0); + + atomic_inc(&iparent->usage); + atomic_inc(&iparent->children); + + cookie->idef = idef; + cookie->iparent = iparent; + cookie->netfs = iparent->netfs; + cookie->netfs_data = netfs_data; + + /* now we need to see whether the backing objects for this cookie yet + * exist, if not there'll be nothing to search */ + down_read(&fscache_addremove_sem); + + if (list_empty(&fscache_cache_list)) { + up_read(&fscache_addremove_sem); + _leave(" [no caches]"); + return cookie; + } + + down_write(&cookie->sem); + + /* search every cache we know about to see if the object is already + * present */ + list_for_each_entry(cache, &fscache_cache_list, link) { + ret = fscache_search_for_object(cookie, cache); + switch (ret) { + case 0: + if (!cookie->idef) + break; /* only want the first file entry */ + case -ENODATA: + ret = 0; + continue; + default: + goto error; + } + } + + /* if the object is a cookie then we need do nothing more here - we + * create indexes on disc when we need them as an index may exist in + * multiple caches */ + if (cookie->idef) + goto done; + + /* the object is a file - we need to select a cache in which to store + * it */ + ret = -ENOMEDIUM; + cache = fscache_select_cache_for_file(); + if (!cache) + goto error; /* couldn't decide on a cache */ + + /* create a file index entry on disc, along with all the indexes + * required to find it again later */ + ret = fscache_instantiate_object(cookie, cache); + if (ret == 0) + goto done; + + error: + printk("FS-Cache: error from cache fs: %d\n", ret); + if (cookie) { + __fscache_cookie_put(cookie); + cookie = FSCACHE_NEGATIVE_COOKIE; + atomic_dec(&iparent->children); + } + + done: + up_write(&cookie->sem); + up_read(&fscache_addremove_sem); + _leave(" = %p", cookie); + return cookie; + +} /* end __fscache_acquire_cookie() */ + +EXPORT_SYMBOL(__fscache_acquire_cookie); + +/*****************************************************************************/ +/* + * release a cookie back to the cache + * - the object will be marked as recyclable on disc if retire is true + * - all dependents of this cookie must have already been unregistered + * (indexes/files/pages) + */ +void __fscache_relinquish_cookie(struct fscache_cookie *cookie, int retire) +{ + struct fscache_cache *cache; + struct fscache_node *node; + + _enter("%p{%s},%d", + cookie, + cookie && cookie->idef ? (char *) cookie->idef->name : "", + retire); + + if (cookie == FSCACHE_NEGATIVE_COOKIE) { + _leave(" [no cookie]"); + return; + } + + if (atomic_read(&cookie->children) != 0) { + printk("FS-Cache: cookie still has children\n"); + BUG(); + } + + /* detach pointers back to netfs */ + down_write(&cookie->sem); + + cookie->netfs_data = NULL; + cookie->idef = NULL; + + read_lock(&cookie->lock); + + /* queue retired objects for recycling */ + if (retire) { + list_for_each_entry(node, + &cookie->backing_nodes, + cookie_link) { + set_bit(FSCACHE_NODE_RECYCLING, &node->flags); + } + } + + /* break links with all the active nodes */ + while (!list_empty(&cookie->backing_nodes)) { + node = list_entry(cookie->backing_nodes.next, + struct fscache_node, + cookie_link); + + /* detach each cache node from the object cookie */ + set_bit(FSCACHE_NODE_RELEASING, &node->flags); + + list_del_init(&node->cookie_link); + read_unlock(&cookie->lock); + + cache = node->cache; + cache->ops->lock_node(node); + node->cookie = NULL; + cache->ops->unlock_node(node); + + if (atomic_dec_and_test(&cookie->usage)) + /* the cookie refcount shouldn't be reduced to 0 yet */ + BUG(); + + cache->ops->put_node(node); + + read_lock(&cookie->lock); + } + + read_unlock(&cookie->lock); + up_write(&cookie->sem); + + if (cookie->iparent) + atomic_dec(&cookie->iparent->children); + + /* finally dispose of the cookie */ + fscache_cookie_put(cookie); + + _leave(""); + +} /* end __fscache_relinquish_cookie() */ + +EXPORT_SYMBOL(__fscache_relinquish_cookie); + +/*****************************************************************************/ +/* + * update the index entries backing a cookie + */ +void __fscache_update_cookie(struct fscache_cookie *cookie) +{ + struct fscache_node *ixnode, *node; + + _enter("{%s}", + cookie && + cookie->idef ? (char *) cookie->idef->name : ""); + + if (cookie == FSCACHE_NEGATIVE_COOKIE) { + _leave(" [no cookie]"); + return; + } + + down_write(&cookie->sem); + down_write(&cookie->iparent->sem); + + /* update the index entry on disc in each cache backing this cookie */ + list_for_each_entry(node, &cookie->backing_nodes, cookie_link) { + ixnode = fscache_find_parent_node(node); + node->cache->ops->index_update(ixnode, node); + } + + up_write(&cookie->iparent->sem); + up_write(&cookie->sem); + _leave(""); + +} /* end __fscache_update_cookie() */ + +EXPORT_SYMBOL(__fscache_update_cookie); + +/*****************************************************************************/ +/* + * destroy a cookie + */ +void __fscache_cookie_put(struct fscache_cookie *cookie) +{ + struct fscache_search_result *srch; + + _enter("%p", cookie); + + if (cookie->iparent) + fscache_cookie_put(cookie->iparent); + + /* dispose of any cached search results */ + while (!list_empty(&cookie->search_results)) { + srch = list_entry(cookie->search_results.next, + struct fscache_search_result, + link); + + list_del(&srch->link); + kfree(srch); + } + + BUG_ON(!list_empty(&cookie->search_results)); + BUG_ON(!list_empty(&cookie->backing_nodes)); + kmem_cache_free(fscache_cookie_jar, cookie); + + _leave(""); + +} /* end __fscache_cookie_put() */ + +/*****************************************************************************/ +/* + * initialise an cookie jar slab element prior to any use + */ +void fscache_cookie_init_once(void *_cookie, kmem_cache_t *cachep, + unsigned long flags) +{ + struct fscache_cookie *cookie = _cookie; + + if ((flags & (SLAB_CTOR_VERIFY|SLAB_CTOR_CONSTRUCTOR)) == + SLAB_CTOR_CONSTRUCTOR) { + memset(cookie, 0, sizeof(*cookie)); + rwlock_init(&cookie->lock); + init_rwsem(&cookie->sem); + INIT_LIST_HEAD(&cookie->search_results); + INIT_LIST_HEAD(&cookie->backing_nodes); + } + +} /* end fscache_cookie_init_once() */ diff -puN /dev/null fs/fscache/fscache-int.h --- /dev/null 2003-09-15 06:40:47.000000000 -0700 +++ 25-akpm/fs/fscache/fscache-int.h 2004-12-03 20:56:45.242882224 -0800 @@ -0,0 +1,81 @@ +/* fscache-int.h: internal definitions + * + * Copyright (C) 2004 Red Hat, Inc. All Rights Reserved. + * Written by David Howells (dhowells@redhat.com) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + */ + +#ifndef _FSCACHE_INT_H +#define _FSCACHE_INT_H + +#include +#include +#include + +extern kmem_cache_t *fscache_cookie_jar; + +extern struct fscache_cookie fscache_fsdef_index; + +extern void fscache_cookie_init_once(void *_cookie, kmem_cache_t *cachep, unsigned long flags); + +extern void __fscache_cookie_put(struct fscache_cookie *cookie); + +static inline void fscache_cookie_put(struct fscache_cookie *cookie) +{ + BUG_ON(atomic_read(&cookie->usage) <= 0); + + if (atomic_dec_and_test(&cookie->usage)) + __fscache_cookie_put(cookie); + +} + +/*****************************************************************************/ +/* + * debug tracing + */ +#define dbgprintk(FMT,...) \ + printk("[%-6.6s] "FMT"\n",current->comm ,##__VA_ARGS__) +#define _dbprintk(FMT,...) do { } while(0) + +#define kenter(FMT,...) dbgprintk("==> %s("FMT")",__FUNCTION__ ,##__VA_ARGS__) +#define kleave(FMT,...) dbgprintk("<== %s()"FMT"",__FUNCTION__ ,##__VA_ARGS__) +#define kdebug(FMT,...) dbgprintk(FMT ,##__VA_ARGS__) + +#define kjournal(FMT,...) _dbprintk(FMT ,##__VA_ARGS__) + +#define dbgfree(ADDR) _dbprintk("%p:%d: FREEING %p",__FILE__,__LINE__,ADDR) + +#define dbgpgalloc(PAGE) \ +do { \ + _dbprintk("PGALLOC %s:%d: %p {%lx,%lu}\n", \ + __FILE__,__LINE__, \ + (PAGE),(PAGE)->mapping->host->i_ino,(PAGE)->index \ + ); \ +} while(0) + +#define dbgpgfree(PAGE) \ +do { \ + if ((PAGE)) \ + _dbprintk("PGFREE %s:%d: %p {%lx,%lu}\n", \ + __FILE__,__LINE__, \ + (PAGE), \ + (PAGE)->mapping->host->i_ino, \ + (PAGE)->index \ + ); \ +} while(0) + +#ifdef __KDEBUG +#define _enter(FMT,...) kenter(FMT,##__VA_ARGS__) +#define _leave(FMT,...) kleave(FMT,##__VA_ARGS__) +#define _debug(FMT,...) kdebug(FMT,##__VA_ARGS__) +#else +#define _enter(FMT,...) do { } while(0) +#define _leave(FMT,...) do { } while(0) +#define _debug(FMT,...) do { } while(0) +#endif + +#endif /* _FSCACHE_INT_H */ diff -puN /dev/null fs/fscache/fsdef.c --- /dev/null 2003-09-15 06:40:47.000000000 -0700 +++ 25-akpm/fs/fscache/fsdef.c 2004-12-03 20:56:45.243882072 -0800 @@ -0,0 +1,90 @@ +/* fsdef.c: filesystem index definition + * + * Copyright (C) 2004 Red Hat, Inc. All Rights Reserved. + * Written by David Howells (dhowells@redhat.com) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + */ + +#include +#include "fscache-int.h" + +static fscache_match_val_t fscache_fsdef_index_match(void *target, + const void *entry); + +static void fscache_fsdef_index_update(void *source, void *entry); + +static struct fscache_index_def fscache_fsdef_index_def = { + .name = ".fsdef", + .data_size = sizeof(struct fscache_fsdef_index_entry), + .keys = { + { .type = FSCACHE_INDEX_KEYS_ASCIIZ, .len = 24 }, + }, + .match = fscache_fsdef_index_match, + .update = fscache_fsdef_index_update +}; + +struct fscache_cookie fscache_fsdef_index = { + .usage = ATOMIC_INIT(1), + .idef = &fscache_fsdef_index_def, + .lock = RW_LOCK_UNLOCKED, + .sem = __RWSEM_INITIALIZER(fscache_fsdef_index.sem), + .search_results = LIST_HEAD_INIT(fscache_fsdef_index.search_results), + .backing_nodes = LIST_HEAD_INIT(fscache_fsdef_index.backing_nodes), +}; + +EXPORT_SYMBOL(fscache_fsdef_index); + +/*****************************************************************************/ +/* + * see if the netfs definition matches + */ +static fscache_match_val_t fscache_fsdef_index_match(void *target, + const void *entry) +{ + const struct fscache_fsdef_index_entry *fsdef = entry; + struct fscache_netfs *netfs = target; + + _enter("%p,%p", target, entry); + + /* name and version must both match with what's on disc */ + _debug("{%s.%u},{%s.%u}", + netfs->name, netfs->version, fsdef->name, fsdef->version); + + if (strncmp(netfs->name, fsdef->name, sizeof(fsdef->name)) != 0) { + _leave(" = FAILED"); + return FSCACHE_MATCH_FAILED; + } + + if (netfs->version == fsdef->version) { + _leave(" = SUCCESS"); + return FSCACHE_MATCH_SUCCESS; + } + + /* an entry of the same name but different version is scheduled for + * deletion */ + _leave(" = SUCCESS_DELETE"); + return FSCACHE_MATCH_SUCCESS_DELETE; + +} /* end fscache_fsdef_index_match() */ + +/*****************************************************************************/ +/* + * update the netfs definition to be stored on disc + */ +static void fscache_fsdef_index_update(void *source, void *entry) +{ + struct fscache_fsdef_index_entry *fsdef = entry; + struct fscache_netfs *netfs = source; + + _enter("{%s.%u},", netfs->name, netfs->version); + + /* install the netfs name and version in the top-level index entry */ + strncpy(fsdef->name, netfs->name, sizeof(fsdef->name)); + + fsdef->version = netfs->version; + +} /* end fscache_fsdef_index_update() */ diff -puN /dev/null fs/fscache/main.c --- /dev/null 2003-09-15 06:40:47.000000000 -0700 +++ 25-akpm/fs/fscache/main.c 2004-12-03 20:56:45.243882072 -0800 @@ -0,0 +1,111 @@ +/* main.c: general filesystem caching manager + * + * Copyright (C) 2004 Red Hat, Inc. All Rights Reserved. + * Written by David Howells (dhowells@redhat.com) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + */ + +#include +#include +#include +#include +#include +#include "fscache-int.h" + +int fscache_debug = 0; + +static int fscache_init(void); +static void fscache_exit(void); + +fs_initcall(fscache_init); +module_exit(fscache_exit); + +MODULE_DESCRIPTION("FS Cache Manager"); +MODULE_AUTHOR("Red Hat, Inc."); +MODULE_LICENSE("GPL"); + +/*****************************************************************************/ +/* + * initialise the fs caching module + */ +static int fscache_init(void) +{ + fscache_cookie_jar = + kmem_cache_create("fscache_cookie_jar", + sizeof(struct fscache_cookie), + 0, + SLAB_HWCACHE_ALIGN, + fscache_cookie_init_once, + NULL); + + if (!fscache_cookie_jar) { + printk(KERN_NOTICE + "FS-Cache: Failed to allocate a cookie jar\n"); + return -ENOMEM; + } + + printk(KERN_INFO "fscache: general fs caching registered\n"); + return 0; + +} /* end fscache_init() */ + +/*****************************************************************************/ +/* + * clean up on module removal + */ +static void __exit fscache_exit(void) +{ + printk(KERN_INFO "FS-Cache: general fs caching unregistering\n"); + + kmem_cache_destroy(fscache_cookie_jar); + +} /* end fscache_exit() */ + +/*****************************************************************************/ +/* + * clear the dead space between task_struct and kernel stack + * - called by supplying -finstrument-functions to gcc + */ +#if 0 +void __cyg_profile_func_enter (void *this_fn, void *call_site) +__attribute__((no_instrument_function)); + +void __cyg_profile_func_enter (void *this_fn, void *call_site) +{ + asm volatile(" movl %%esp,%%edi \n" + " andl %0,%%edi \n" + " addl %1,%%edi \n" + " movl %%esp,%%ecx \n" + " subl %%edi,%%ecx \n" + " shrl $2,%%ecx \n" + " movl $0xedededed,%%eax \n" + " rep stosl \n" + : + : "i"(~(THREAD_SIZE-1)), "i"(sizeof(struct thread_info)) + : "eax", "ecx", "edi", "memory", "cc" + ); +} + +void __cyg_profile_func_exit(void *this_fn, void *call_site) +__attribute__((no_instrument_function)); + +void __cyg_profile_func_exit(void *this_fn, void *call_site) +{ + asm volatile(" movl %%esp,%%edi \n" + " andl %0,%%edi \n" + " addl %1,%%edi \n" + " movl %%esp,%%ecx \n" + " subl %%edi,%%ecx \n" + " shrl $2,%%ecx \n" + " movl $0xdadadada,%%eax \n" + " rep stosl \n" + : + : "i"(~(THREAD_SIZE-1)), "i"(sizeof(struct thread_info)) + : "eax", "ecx", "edi", "memory", "cc" + ); +} +#endif diff -puN /dev/null fs/fscache/Makefile --- /dev/null 2003-09-15 06:40:47.000000000 -0700 +++ 25-akpm/fs/fscache/Makefile 2004-12-03 20:56:45.244881920 -0800 @@ -0,0 +1,13 @@ +# +# Makefile for general filesystem caching code +# + +#CFLAGS += -finstrument-functions + +fscache-objs := \ + cookie.o \ + fsdef.o \ + main.o \ + page.o + +obj-$(CONFIG_FSCACHE) := fscache.o diff -puN /dev/null fs/fscache/page.c --- /dev/null 2003-09-15 06:40:47.000000000 -0700 +++ 25-akpm/fs/fscache/page.c 2004-12-03 20:56:45.245881768 -0800 @@ -0,0 +1,231 @@ +/* page.c: general filesystem cache cookie management + * + * Copyright (C) 2004 Red Hat, Inc. All Rights Reserved. + * Written by David Howells (dhowells@redhat.com) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + */ + +#include +#include +#include +#include "fscache-int.h" + +/*****************************************************************************/ +/* + * read a page from the cache or allocate a block in which to store it + * - we return: + * -ENOMEM - out of memory, nothing done + * -ENOBUFS - no backing node available in which to cache the block + * -ENODATA - no data available in the backing node for this block + * 0 - dispatched a read - it'll call end_io_func() when finished + */ +int __fscache_read_or_alloc_page(struct fscache_cookie *cookie, + struct page *page, + fscache_rw_complete_t end_io_func, + void *end_io_data, + unsigned long gfp) +{ + struct fscache_node *node; + struct fscache_page *pageio; + int ret; + + _enter("%p,{%lu},", cookie, page->index); + + if (cookie == FSCACHE_NEGATIVE_COOKIE) { + _leave(" -ENOBUFS [no cookie]"); + return -ENOBUFS; + } + + if (list_empty(&cookie->backing_nodes)) { + _leave(" -ENOBUFS [no backing nodes]"); + return -ENOBUFS; + } + + BUG_ON(cookie->idef); /* not supposed to use this for indexes */ + + /* get the cache-cookie for this page */ + pageio = cookie->netfs->ops->get_page_token(page); + if (IS_ERR(pageio)) { + _leave(" = %ld", PTR_ERR(pageio)); + return PTR_ERR(pageio); + } + + /* prevent the file from being uncached whilst we access it */ + down_read(&cookie->sem); + + ret = -ENOBUFS; + if (!list_empty(&cookie->backing_nodes)) { + /* get and pin the backing node */ + node = list_entry(cookie->backing_nodes.next, + struct fscache_node, + cookie_link); + + if (node->cache->ops->grab_node(node)) { + /* ask the cache to honour the operation */ + ret = node->cache->ops->read_or_alloc_page(node, + page, + pageio, + end_io_func, + end_io_data, + gfp); + + node->cache->ops->put_node(node); + } + + } + up_read(&cookie->sem); + _leave(" = %d", ret); + return ret; + +} /* end __fscache_read_or_alloc_page() */ + +EXPORT_SYMBOL(__fscache_read_or_alloc_page); + +/*****************************************************************************/ +/* + * request a page be stored in the cache + * - returns: + * -ENOMEM - out of memory, nothing done + * -ENOBUFS - no backing node available in which to cache the page + * 0 - dispatched a write - it'll call end_io_func() when finished + */ +int __fscache_write_page(struct fscache_cookie *cookie, + struct page *page, + fscache_rw_complete_t end_io_func, + void *end_io_data, + unsigned long gfp) +{ + struct fscache_page *pageio; + struct fscache_node *node; + int ret; + + _enter("%p,{%lu},", cookie, page->index); + + if (cookie == FSCACHE_NEGATIVE_COOKIE) { + _leave(" -ENOBUFS [no cookie]"); + return -ENOBUFS; /* no actual cookie */ + } + + BUG_ON(cookie->idef); /* not supposed to use this for indexes */ + + /* get the cache-cookie for this page */ + pageio = cookie->netfs->ops->get_page_token(page); + if (IS_ERR(pageio)) { + _leave(" = %ld", PTR_ERR(pageio)); + return PTR_ERR(pageio); + } + + /* prevent the file from been uncached whilst we deal with it */ + down_read(&cookie->sem); + + ret = -ENOBUFS; + if (!list_empty(&cookie->backing_nodes) && pageio->mapped_block) { + node = list_entry(cookie->backing_nodes.next, + struct fscache_node, + cookie_link); + + /* ask the cache to honour the operation */ + ret = node->cache->ops->write_page(node, + page, + pageio, + end_io_func, + end_io_data, + gfp); + } + + up_read(&cookie->sem); + _leave(" = %d", ret); + return ret; + +} /* end __fscache_write_page() */ + +EXPORT_SYMBOL(__fscache_write_page); + +/*****************************************************************************/ +/* + * remove a page from the cache + * - if the block backing the page still has a vjentry then the block will be + * recycled + */ +void __fscache_uncache_page(struct fscache_cookie *cookie, struct page *page) +{ + struct fscache_page *pageio; + struct fscache_node *node; + + _enter(",{%lu}", page->index); + + if (cookie == FSCACHE_NEGATIVE_COOKIE) { + _leave(" [no cookie]"); + return; + } + + BUG_ON(cookie->idef); /* not supposed to use this for indexes */ + + /* get the cache-cookie for this page */ + pageio = cookie->netfs->ops->get_page_token(page); + if (IS_ERR(pageio)) { + _leave(" [get_page_cookie() = %ld]", PTR_ERR(pageio)); + return; + } + + if (list_empty(&cookie->backing_nodes)) { + BUG_ON(pageio->mapped_block); + _leave(" [no backing]"); + return; + } + + if (!pageio->mapped_block) { + _leave(" [no mapping]"); + return; + } + + /* ask the cache to honour the operation */ + down_read(&cookie->sem); + + if (!list_empty(&cookie->backing_nodes) && pageio->mapped_block) { + node = list_entry(cookie->backing_nodes.next, + struct fscache_node, + cookie_link); + + node->cache->ops->uncache_page(node, pageio); + } + + up_read(&cookie->sem); + + _leave(""); + return; + +} /* end __fscache_uncache_page() */ + +EXPORT_SYMBOL(__fscache_uncache_page); + +/*****************************************************************************/ +/* + * get a page caching token from for a page, allocating it and attaching it to + * the page's private pointer if it doesn't exist + */ +struct fscache_page * __fscache_page_get_private(struct page *page, + unsigned gfp_flags) +{ + struct fscache_page *pageio = (struct fscache_page *) page->private; + + if (!pageio) { + pageio = kmalloc(sizeof(*pageio), gfp_flags); + if (!pageio) + return ERR_PTR(-ENOMEM); + + memset(pageio, 0, sizeof(*pageio)); + rwlock_init(&pageio->lock); + + page->private = (unsigned long) pageio; + SetPagePrivate(page); + } + + return pageio; +} /* end __fscache_page_get_private() */ + +EXPORT_SYMBOL(__fscache_page_get_private); diff -puN fs/Kconfig~split-general-cache-manager-from-cachefs fs/Kconfig --- 25/fs/Kconfig~split-general-cache-manager-from-cachefs 2004-12-03 20:56:45.232883744 -0800 +++ 25-akpm/fs/Kconfig 2004-12-03 20:56:45.247881464 -0800 @@ -506,10 +506,21 @@ config AUTOFS4_FS menu "Caches" -config CACHEFS - tristate "Filesystem caching support" +config FSCACHE + tristate "General filesystem cache manager" depends on EXPERIMENTAL help + This option enables a generic filesystem caching manager that can be + used by various network and other filesystems to cache data + locally. Diffent sorts of caches can be plugged in, depending on the + resources available. + + See Documentation/filesystems/fscache.txt for more information. + +config CACHEFS + tristate "Filesystem caching filesystem" + depends on FSCACHE + help This filesystem acts as a cache for other filesystems - primarily networking filesystems - rather than thus allowing fast local disc to enhance the speed of slower devices. diff -puN fs/Makefile~split-general-cache-manager-from-cachefs fs/Makefile --- 25/fs/Makefile~split-general-cache-manager-from-cachefs 2004-12-03 20:56:45.234883440 -0800 +++ 25-akpm/fs/Makefile 2004-12-03 20:56:45.247881464 -0800 @@ -47,6 +47,7 @@ obj-y += devpts/ obj-$(CONFIG_PROFILING) += dcookies.o # Do not add any filesystems before this line +obj-$(CONFIG_FSCACHE) += fscache/ obj-$(CONFIG_REISERFS_FS) += reiserfs/ obj-$(CONFIG_EXT3_FS) += ext3/ # Before ext2 so root fs can be ext3 obj-$(CONFIG_JBD) += jbd/ diff -puN /dev/null include/linux/fscache-cache.h --- /dev/null 2003-09-15 06:40:47.000000000 -0700 +++ 25-akpm/include/linux/fscache-cache.h 2004-12-03 20:56:45.248881312 -0800 @@ -0,0 +1,205 @@ +/* fscache-cache.h: general filesystem caching backing cache interface + * + * Copyright (C) 2004 Red Hat, Inc. All Rights Reserved. + * Written by David Howells (dhowells@redhat.com) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + */ + +#ifndef _LINUX_FSCACHE_CACHE_H +#define _LINUX_FSCACHE_CACHE_H + +#include + +struct fscache_cache; +struct fscache_cache_ops; +struct fscache_node; +struct fscache_search_result; + +struct fscache_search_result { + struct list_head link; /* link in search_results */ + struct fscache_cache *cache; /* cache searched */ + unsigned ino; /* node ID (or 0 if negative) */ +}; + +struct fscache_cache { + struct fscache_cache_ops *ops; + struct list_head link; /* link in list of caches */ + size_t max_index_size; /* maximum size of index data */ + unsigned long flags; +#define FSCACHE_CACHE_WITHDRAWN 0 /* T if cache has been withdrawn */ + + char identifier[32]; /* cache label */ + + /* node management */ + struct list_head node_list; /* list of data/index nodes */ + spinlock_t node_list_lock; + struct fscache_search_result fsdef_srch; /* search result for the fsdef index */ +}; + +extern void fscache_init_cache(struct fscache_cache *cache, + struct fscache_cache_ops *ops, + unsigned fsdef_ino, + const char *idfmt, + ...) __attribute__ ((format (printf,4,5))); + +extern void fscache_add_cache(struct fscache_cache *cache); +extern void fscache_withdraw_cache(struct fscache_cache *cache); + +/* see if a cache has been withdrawn */ +static inline int fscache_is_cache_withdrawn(struct fscache_cache *cache) +{ + return test_bit(FSCACHE_CACHE_WITHDRAWN, &cache->flags); +} + +/*****************************************************************************/ +/* + * cache operations + */ +struct fscache_cache_ops { + /* name of cache provider */ + const char *name; + + /* look up the nominated node for this cache */ + struct fscache_node *(*lookup_node)(struct fscache_cache *cache, unsigned ino); + + /* increment the usage count on this inode (may fail if unmounting) */ + struct fscache_node *(*grab_node)(struct fscache_node *node); + + /* lock a semaphore on a node */ + void (*lock_node)(struct fscache_node *node); + + /* unlock a semaphore on a node */ + void (*unlock_node)(struct fscache_node *node); + + /* dispose of a reference to a node */ + void (*put_node)(struct fscache_node *node); + + /* search an index for an inode to back a cookie + * - the "inode number" should be set in result->ino + */ + int (*index_search)(struct fscache_node *node, struct fscache_cookie *cookie, + struct fscache_search_result *result); + + /* create a new file or inode, with an entry in the named index + * - the "inode number" should be set in result->ino + */ + int (*index_add)(struct fscache_node *node, struct fscache_cookie *cookie, + struct fscache_search_result *result); + + /* update the index entry for a node + * - the netfs's update operation should be called + */ + int (*index_update)(struct fscache_node *ixnode, + struct fscache_node *node); + + /* sync a cache */ + void (*sync)(struct fscache_cache *cache); + + /* dissociate a cache from all the pages it was backing */ + void (*dissociate_pages)(struct fscache_cache *cache); + + /* request a backing block for a page be read or allocated in the + * cache */ + int (*read_or_alloc_page)(struct fscache_node *node, + struct page *page, + struct fscache_page *pageio, + fscache_rw_complete_t end_io_func, + void *end_io_data, + unsigned long gfp); + + /* write a page to its backing block in the cache */ + int (*write_page)(struct fscache_node *node, + struct page *page, + struct fscache_page *pageio, + fscache_rw_complete_t end_io_func, + void *end_io_data, + unsigned long gfp); + + /* detach a backing block from a page */ + void (*uncache_page)(struct fscache_node *node, + struct fscache_page *pageio); +}; + +/*****************************************************************************/ +/* + * data file or index object cookie + * - a file will only appear in one cache + * - a request to cache a file may or may not be honoured, subject to + * constraints such as disc space + * - indexes files are created on disc just-in-time + */ +struct fscache_cookie { + atomic_t usage; /* number of users of this cookie */ + atomic_t children; /* number of children of this cookie */ + rwlock_t lock; /* list access lock */ + struct rw_semaphore sem; /* list creation vs scan lock */ + struct list_head search_results; /* results of searching iparent */ + struct list_head backing_nodes; /* node(s) backing this file/index */ + struct fscache_index_def *idef; /* index definition */ + struct fscache_cookie *iparent; /* index holding this entry */ + struct fscache_netfs *netfs; /* owner network fs definition */ + void *netfs_data; /* back pointer to netfs */ +}; + +extern struct fscache_cookie fscache_fsdef_index; + +/*****************************************************************************/ +/* + * on-disc cache file or index handle + */ +struct fscache_node { + unsigned long flags; +#define FSCACHE_NODE_ISINDEX 0 /* T if inode is index file (F if file) */ +#define FSCACHE_NODE_RELEASING 1 /* T if inode is being released */ +#define FSCACHE_NODE_RECYCLING 2 /* T if inode is being retired */ +#define FSCACHE_NODE_WITHDRAWN 3 /* T if inode has been withdrawn */ + + struct list_head cache_link; /* link in cache->node_list */ + struct list_head cookie_link; /* link in cookie->backing_nodes */ + struct fscache_cache *cache; /* cache that supplied this node */ + struct fscache_cookie *cookie; /* netfs's file/index object */ +}; + +static inline +void fscache_node_init(struct fscache_node *node) +{ + node->flags = 0; + INIT_LIST_HEAD(&node->cache_link); + INIT_LIST_HEAD(&node->cookie_link); + node->cache = NULL; + node->cookie = NULL; +} + +/* find the parent index node for a node */ +static inline +struct fscache_node *fscache_find_parent_node(struct fscache_node *node) +{ + struct fscache_cookie *cookie = node->cookie; + struct fscache_cache *cache = node->cache; + struct fscache_node *parent; + + list_for_each_entry(parent, + &cookie->iparent->backing_nodes, + cookie_link + ) { + if (parent->cache == cache) + return parent; + } + + return NULL; +} + +/*****************************************************************************/ +/* + * definition of the contents of an FSDEF index entry + */ +struct fscache_fsdef_index_entry { + uint8_t name[24]; /* name of netfs */ + uint32_t version; /* version of layout */ +}; + +#endif /* _LINUX_FSCACHE_CACHE_H */ diff -puN /dev/null include/linux/fscache.h --- /dev/null 2003-09-15 06:40:47.000000000 -0700 +++ 25-akpm/include/linux/fscache.h 2004-12-03 20:56:45.250881008 -0800 @@ -0,0 +1,357 @@ +/* fscache.h: general filesystem caching interface + * + * Copyright (C) 2004 Red Hat, Inc. All Rights Reserved. + * Written by David Howells (dhowells@redhat.com) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + */ + +#ifndef _LINUX_FSCACHE_H +#define _LINUX_FSCACHE_H + +#include +#include +#include +#include + +#ifdef CONFIG_FSCACHE_MODULE +#define CONFIG_FSCACHE +#endif + +struct fscache_cookie; +struct fscache_netfs; +struct fscache_netfs_operations; +struct fscache_page; + +#define FSCACHE_NEGATIVE_COOKIE NULL + +typedef void (*fscache_rw_complete_t)(void *cookie_data, + struct page *page, + void *data, + int error); + +/* result of index entry comparison */ +typedef enum { + /* no match */ + FSCACHE_MATCH_FAILED, + + /* successful match */ + FSCACHE_MATCH_SUCCESS, + + /* successful match, entry requires update */ + FSCACHE_MATCH_SUCCESS_UPDATE, + + /* successful match, entry requires deletion */ + FSCACHE_MATCH_SUCCESS_DELETE, +} fscache_match_val_t; + +/*****************************************************************************/ +/* + * fscache index definition + * - each index file contains a number of fixed size entries + * - they don't have to fit exactly into a page, but if they don't, the gap + * at the end of the page will not be used + */ +struct fscache_index_def +{ + /* name of index */ + uint8_t name[8]; + + /* size of data to be stored in index */ + uint16_t data_size; + + /* key description (for displaying in cache mountpoint) */ + struct { + uint8_t type; + uint16_t len; + } keys[4]; + +#define FSCACHE_INDEX_KEYS_NOTUSED 0 +#define FSCACHE_INDEX_KEYS_BIN 1 +#define FSCACHE_INDEX_KEYS_BIN_SZ1 2 +#define FSCACHE_INDEX_KEYS_BIN_SZ2 3 +#define FSCACHE_INDEX_KEYS_BIN_SZ4 4 +#define FSCACHE_INDEX_KEYS_ASCIIZ 5 +#define FSCACHE_INDEX_KEYS_IPV4ADDR 6 +#define FSCACHE_INDEX_KEYS_IPV6ADDR 7 +#define FSCACHE_INDEX_KEYS__LAST FSCACHE_INDEX_KEYS_IPV6ADDR + + /* see if entry matches the specified key + * - the netfs data from the cookie being used as the target is + * presented + * - entries that aren't in use will not be presented for matching + */ + fscache_match_val_t (*match)(void *target_netfs_data, + const void *entry); + + /* update entry from key + * - the netfs data from the cookie being used as the source is + * presented + */ + void (*update)(void *source_netfs_data, void *entry); +}; + +/* pattern used to fill dead space in an index entry */ +#define FSCACHE_INDEX_DEADFILL_PATTERN 0x79 + +#ifdef CONFIG_FSCACHE +extern struct fscache_cookie *__fscache_acquire_cookie(struct fscache_cookie *iparent, + struct fscache_index_def *idef, + void *netfs_data); + +extern void __fscache_relinquish_cookie(struct fscache_cookie *cookie, + int retire); + +extern void __fscache_update_cookie(struct fscache_cookie *cookie); +#endif + +static inline +struct fscache_cookie *fscache_acquire_cookie(struct fscache_cookie *iparent, + struct fscache_index_def *idef, + void *netfs_data) +{ +#ifdef CONFIG_FSCACHE + if (iparent != FSCACHE_NEGATIVE_COOKIE) + return __fscache_acquire_cookie(iparent, idef, netfs_data); +#endif + return FSCACHE_NEGATIVE_COOKIE; +} + +static inline +void fscache_relinquish_cookie(struct fscache_cookie *cookie, + int retire) +{ +#ifdef CONFIG_FSCACHE + if (cookie != FSCACHE_NEGATIVE_COOKIE) + __fscache_relinquish_cookie(cookie, retire); +#endif +} + +static inline +void fscache_update_cookie(struct fscache_cookie *cookie) +{ +#ifdef CONFIG_FSCACHE + if (cookie != FSCACHE_NEGATIVE_COOKIE) + __fscache_update_cookie(cookie); +#endif +} + +/*****************************************************************************/ +/* + * fscache cached network filesystem type + * - name, version and ops must be filled in before registration + * - all other fields will be set during registration + */ +struct fscache_netfs +{ + const char *name; /* filesystem name */ + unsigned version; /* indexing version */ + struct fscache_cookie *primary_index; + struct fscache_netfs_operations *ops; + struct list_head link; /* internal link */ +}; + +struct fscache_netfs_operations +{ + /* get page-to-block mapping token for a page + * - one should be allocated if it doesn't exist + * - returning -ENODATA will cause this page to be ignored + * - typically, the struct will be attached to page->private + */ + struct fscache_page *(*get_page_token)(struct page *page); +}; + +#ifdef CONFIG_FSCACHE +extern int __fscache_register_netfs(struct fscache_netfs *netfs, + struct fscache_index_def *primary_idef); +extern void __fscache_unregister_netfs(struct fscache_netfs *netfs); +#endif + +static inline +int fscache_register_netfs(struct fscache_netfs *netfs, + struct fscache_index_def *primary_idef) +{ +#ifdef CONFIG_FSCACHE + return __fscache_register_netfs(netfs, primary_idef); +#else + return 0; +#endif +} + +static inline +void fscache_unregister_netfs(struct fscache_netfs *netfs) +{ +#ifdef CONFIG_FSCACHE + __fscache_unregister_netfs(netfs); +#endif +} + +/*****************************************************************************/ +/* + * page mapping cookie + * - stores the mapping of a page to a block in the cache (may also be null) + * - note that the mapping may be removed without notice if a cache is removed + */ +struct fscache_page +{ + void *mapped_block; /* block mirroring this page */ + rwlock_t lock; + + unsigned long flags; +#define FSCACHE_PAGE_BOUNDARY 0 /* next block has a different + * indirection chain */ +#define FSCACHE_PAGE_NEW 1 /* this is a newly allocated block */ +}; + +/* + * read a page from the cache or allocate a block in which to store it + * - if the cookie is not backed by a file: + * - -ENOBUFS will be returned and nothing more will be done + * - else if the page is backed by a block in the cache: + * - a read will be started which will call end_io_func on completion + * - the wb-journal will be searched for an entry pertaining to this block + * - if an entry is found: + * - 1 will be returned [not yet supported] + * else + * - 0 will be returned + * - else if the page is unbacked: + * - a block will be allocated and attached + * - the validity journal will be marked to note the block does not yet + * contain valid data + * - -ENODATA will be returned + */ +#ifdef CONFIG_FSCACHE +extern int __fscache_read_or_alloc_page(struct fscache_cookie *cookie, + struct page *page, + fscache_rw_complete_t end_io_func, + void *end_io_data, + unsigned long gfp); +#endif + +static inline +int fscache_read_or_alloc_page(struct fscache_cookie *cookie, + struct page *page, + fscache_rw_complete_t end_io_func, + void *end_io_data, + unsigned long gfp) +{ +#ifdef CONFIG_FSCACHE + if (cookie != FSCACHE_NEGATIVE_COOKIE) + return __fscache_read_or_alloc_page(cookie, page, end_io_func, + end_io_data, gfp); +#endif + return -ENOBUFS; +} + +/* + * request a page be stored in the cache + * - this request may be ignored if no cache block is currently attached, in + * which case it: + * - returns -ENOBUFS + * - if a cache block was already allocated: + * - the page cookie will be updated to reflect the block selected + * - a BIO will be dispatched to write the page (end_io_func will be called + * from the completion function) + * - end_io_func can be NULL, in which case a default function will just + * clear the writeback bit on the page + * - any associated validity journal entry will be cleared + * - returns 0 + */ +#ifdef CONFIG_FSCACHE +extern int __fscache_write_page(struct fscache_cookie *cookie, + struct page *page, + fscache_rw_complete_t end_io_func, + void *end_io_data, + unsigned long gfp); +#endif + +static inline +int fscache_write_page(struct fscache_cookie *cookie, + struct page *page, + fscache_rw_complete_t end_io_func, + void *end_io_data, + unsigned long gfp) +{ +#ifdef CONFIG_FSCACHE + if (cookie != FSCACHE_NEGATIVE_COOKIE) + return __fscache_write_page(cookie, page, end_io_func, + end_io_data, gfp); +#endif + return -ENOBUFS; +} + +/* + * indicate that caching is no longer required on a page + * - note: cannot cancel any outstanding BIOs between this page and the cache + */ +#ifdef CONFIG_FSCACHE +extern void __fscache_uncache_page(struct fscache_cookie *cookie, + struct page *page); +#endif + +static inline +void fscache_uncache_page(struct fscache_cookie *cookie, + struct page *page) +{ +#ifdef CONFIG_FSCACHE + __fscache_uncache_page(cookie, page); +#endif +} + +/* + * keep track of pages changed locally but not yet committed + */ +#if 0 /* TODO */ +extern void fscache_writeback_prepare(struct fscache_cookie *cookie, + struct page *page, + unsigned short from, + unsigned short to); + +extern void fscache_writeback_committed(struct fscache_cookie *cookie, + struct page *page, + unsigned short from, + unsigned short to); + +extern void fscache_writeback_aborted(struct fscache_cookie *cookie, + struct page *page, + unsigned short from, + unsigned short to); +#endif + +/* + * convenience routines for mapping page->private directly to a struct + * fscache_page + */ +static inline +struct fscache_page *__fscache_page_grab_private(struct page *page) +{ + return (struct fscache_page *) (PagePrivate(page) ? page->private : 0); +} + +#define fscache_page_grab_private(X) \ +({ \ + BUG_ON(!PagePrivate(X)); \ + __fscache_page_grab_private(X); \ +}) + + +#ifdef CONFIG_FSCACHE +extern struct fscache_page *__fscache_page_get_private(struct page *page, + unsigned gfp); +#endif + +static inline +struct fscache_page *fscache_page_get_private(struct page *page, + unsigned gfp) +{ +#ifdef CONFIG_FSCACHE + return __fscache_page_get_private(page, gfp); +#else + return ERR_PTR(-EIO); +#endif +} + +#endif /* _LINUX_FSCACHE_H */ _