/* FS-Cache cache handling * * Copyright (C) 2007 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. */ #define FSCACHE_DEBUG_LEVEL CACHE #include #include #include "internal.h" LIST_HEAD(fscache_cache_list); DECLARE_RWSEM(fscache_addremove_sem); static LIST_HEAD(fscache_cache_tag_list); /* * look up a cache tag */ struct fscache_cache_tag *__fscache_lookup_cache_tag(const char *name) { struct fscache_cache_tag *tag, *xtag; /* firstly check for the existence of the tag under read lock */ down_read(&fscache_addremove_sem); list_for_each_entry(tag, &fscache_cache_tag_list, link) { if (strcmp(tag->name, name) == 0) { atomic_inc(&tag->usage); up_read(&fscache_addremove_sem); return tag; } } up_read(&fscache_addremove_sem); /* the tag does not exist - create a candidate */ xtag = kzalloc(sizeof(*xtag) + strlen(name) + 1, GFP_KERNEL); if (!xtag) /* return a dummy tag if out of memory */ return ERR_PTR(-ENOMEM); atomic_set(&xtag->usage, 1); strcpy(xtag->name, name); /* write lock, search again and add if still not present */ down_write(&fscache_addremove_sem); list_for_each_entry(tag, &fscache_cache_tag_list, link) { if (strcmp(tag->name, name) == 0) { atomic_inc(&tag->usage); up_write(&fscache_addremove_sem); kfree(xtag); return tag; } } list_add_tail(&xtag->link, &fscache_cache_tag_list); up_write(&fscache_addremove_sem); return xtag; } /* * release a reference to a cache tag */ void __fscache_release_cache_tag(struct fscache_cache_tag *tag) { if (tag != ERR_PTR(-ENOMEM)) { down_write(&fscache_addremove_sem); if (atomic_dec_and_test(&tag->usage)) list_del_init(&tag->link); else tag = NULL; up_write(&fscache_addremove_sem); kfree(tag); } } /* * select a cache in which to store an object * - the cache addremove semaphore must be at least read-locked by the caller * - the object will never be an index */ struct fscache_cache *fscache_select_cache_for_object( struct fscache_cookie *cookie) { struct fscache_cache_tag *tag; struct fscache_object *object; struct fscache_cache *cache; _enter(""); if (list_empty(&fscache_cache_list)) { _leave(" = NULL [no cache]"); return NULL; } /* we check the parent to determine the cache to use */ spin_lock(&cookie->lock); /* the first in the parent's backing list should be the preferred * cache */ if (!hlist_empty(&cookie->backing_objects)) { object = hlist_entry(cookie->backing_objects.first, struct fscache_object, cookie_link); cache = object->cache; if (object->state >= FSCACHE_OBJECT_DYING || test_bit(FSCACHE_IOERROR, &cache->flags)) cache = NULL; spin_unlock(&cookie->lock); _leave(" = %p [parent]", cache); return cache; } /* the parent is unbacked */ if (cookie->def->type != FSCACHE_COOKIE_TYPE_INDEX) { /* cookie not an index and is unbacked */ spin_unlock(&cookie->lock); _leave(" = NULL [cookie ub,ni]"); return NULL; } spin_unlock(&cookie->lock); if (!cookie->def->select_cache) goto no_preference; /* ask the netfs for its preference */ tag = cookie->def->select_cache(cookie->parent->netfs_data, cookie->netfs_data); if (!tag) goto no_preference; if (tag == ERR_PTR(-ENOMEM)) { _leave(" = NULL [nomem tag]"); return NULL; } if (!tag->cache) { _leave(" = NULL [unbacked tag]"); return NULL; } if (test_bit(FSCACHE_IOERROR, &tag->cache->flags)) return NULL; _leave(" = %p [specific]", tag->cache); return tag->cache; no_preference: /* netfs has no preference - just select first cache */ cache = list_entry(fscache_cache_list.next, struct fscache_cache, link); _leave(" = %p [first]", cache); return cache; }