/*
* net/sunrpc/cache.c
*
* Generic code for various authentication-related caches
* used by sunrpc clients and servers.
*
* Copyright (C) 2002 Neil Brown <neilb@cse.unsw.edu.au>
*
* Released under terms in GPL version 2. See COPYING.
*
*/
#include <linux/types.h>
#include <linux/fs.h>
#include <linux/file.h>
#include <linux/slab.h>
#include <linux/signal.h>
#include <linux/sched.h>
#include <linux/kmod.h>
#include <linux/list.h>
#include <linux/module.h>
#include <linux/ctype.h>
#include <asm/uaccess.h>
#include <linux/poll.h>
#include <linux/seq_file.h>
#include <linux/proc_fs.h>
#include <linux/net.h>
#include <linux/workqueue.h>
#include <linux/mutex.h>
#include <asm/ioctls.h>
#include <linux/sunrpc/types.h>
#include <linux/sunrpc/cache.h>
#include <linux/sunrpc/stats.h>
#define RPCDBG_FACILITY RPCDBG_CACHE
static void cache_defer_req(struct cache_req *req, struct cache_head *item);
static void cache_revisit_request(struct cache_head *item);
static void cache_init(struct cache_head *h)
{
time_t now = get_seconds();
h->next = NULL;
h->flags = 0;
kref_init(&h->ref);
h->expiry_time = now + CACHE_NEW_EXPIRY;
h->last_refresh = now;
}
struct cache_head *sunrpc_cache_lookup(struct cache_detail *detail,
struct cache_head *key, int hash)
{
struct cache_head **head, **hp;
struct cache_head *new = NULL;
head = &detail->hash_table[hash];
read_lock(&detail->hash_lock);
for (hp=head; *hp != NULL ; hp = &(*hp)->next) {
struct cache_head *tmp = *hp;
if (detail->match(tmp, key)) {
cache_get(tmp);
read_unlock(&detail->hash_lock);
return tmp;
}
}
read_unlock(&detail->hash_lock);
/* Didn't find anything, insert an empty entry */
new = detail->alloc();
if (!new)
return NULL;
cache_init(new);
write_lock(&detail->hash_lock);
/* check if entry appeared while we slept */
for (hp=head; *hp != NULL ; hp = &(*hp)->next) {
struct cache_head *tmp = *hp;
if (detail->match(tmp, key)) {
cache_get(tmp);
write_unlock(&detail->hash_lock);
cache_put(new, detail);
return tmp;
}
}
detail->init(new, key);
new->next = *head;
*head = new;
detail->entries++;
cache_get(new);
write_unlock(&detail->hash_lock);
return new;
}
EXPORT_SYMBOL(sunrpc_cache_lookup);
static void queue_loose(struct cache_detail *detail, struct cache_head *ch);
static int cache_fresh_locked(struct cache_head *head, time_t expiry)
{
head->expiry_time = expiry;
head->last_refresh = get_seconds();
return !test_and_set_bit(CACHE_VALID, &head->flags);
}
static void cache_fresh_unlocked(struct cache_head *head,
struct cache_detail *detail, int new)
{
if (new)
cache_revisit_request(head);
if (test_and_clear_bit(CACHE_PENDING, &head->flags)) {
cache_revisit_request(head);
queue_loose(detail, head);
}
}
struct cache_head *sunrpc_cache_update(struct cache_detail *detail,
struct cache_head *new, struct cache_head *old, int hash)
{
/* The 'old' entry is to be replaced by 'new'.
* If 'old' is not VALID, we update it directly,
* otherwise we need to replace it
*/
struct cache_head **head;
struct cache_head *tmp;
int is_new;
if (!test_bit(CACHE_VALID, &old->flags)) {
write_lock(&detail->hash_lock);
if (!test_bit(CACHE_VALID, &old->flags)) {
if (test_bit(CACHE_NEGATIVE, &new->flags))
set_bit(CACHE_NEGATIVE, &old->flags);
else
detail->update(old, new);
is_new = cache_fresh_locked(old, new->expiry_time);
write_unlock(&detail->hash_lock);
cache_fresh_unlocked(old, detail, is_new);
return old;
}
write_unlock(&detail->hash_lock);
}
/* We need to insert a new entry */
tmp = detail->alloc();
if (!tmp) {
cache_put(old, detail);
return NULL;
}
cache_init(tmp);
detail->init(tmp, old);
head = &detail->hash_table[hash];
write_lock(&detail->hash_lock);
if (test_bit(CACHE_NEGATIVE, &new->flags))
set_bit(CACHE_NEGATIVE, &tmp->flags);
else
detail->update(tmp, new);
tmp->next = *head;
*head = t
|