2008 lines · 47.8 KB · C
RawDownload
// SPDX-License-Identifier: GPL-2.0-only/* * 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> */ #include <linux/types.h>#include <linux/fs.h>#include <linux/file.h>#include <linux/hex.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 <linux/string_helpers.h>#include <linux/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 <linux/pagemap.h>#include <asm/ioctls.h>#include <linux/sunrpc/types.h>#include <linux/sunrpc/cache.h>#include <linux/sunrpc/stats.h>#include <linux/sunrpc/rpc_pipe_fs.h>#include <net/genetlink.h>#include <trace/events/sunrpc.h> #include "netns.h"#include "netlink.h"#include "fail.h" #define	 RPCDBG_FACILITY RPCDBG_CACHE static bool 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, struct cache_detail *detail){	time64_t now = seconds_since_boot();	INIT_HLIST_NODE(&h->cache_list);	h->flags = 0;	kref_init(&h->ref);	h->expiry_time = now + CACHE_NEW_EXPIRY;	if (now <= detail->flush_time)		/* ensure it isn't already expired */		now = detail->flush_time + 1;	h->last_refresh = now;} static void cache_fresh_unlocked(struct cache_head *head,				struct cache_detail *detail); static struct cache_head *sunrpc_cache_find_rcu(struct cache_detail *detail,						struct cache_head *key,						int hash){	struct hlist_head *head = &detail->hash_table[hash];	struct cache_head *tmp; 	rcu_read_lock();	hlist_for_each_entry_rcu(tmp, head, cache_list) {		if (!detail->match(tmp, key))			continue;		if (test_bit(CACHE_VALID, &tmp->flags) &&		    cache_is_expired(detail, tmp))			continue;		tmp = cache_get_rcu(tmp);		rcu_read_unlock();		return tmp;	}	rcu_read_unlock();	return NULL;} static void sunrpc_begin_cache_remove_entry(struct cache_head *ch,					    struct cache_detail *cd){	/* Must be called under cd->hash_lock */	hlist_del_init_rcu(&ch->cache_list);	set_bit(CACHE_CLEANED, &ch->flags);	cd->entries --;} static void sunrpc_end_cache_remove_entry(struct cache_head *ch,					  struct cache_detail *cd){	cache_fresh_unlocked(ch, cd);	cache_put(ch, cd);} static struct cache_head *sunrpc_cache_add_entry(struct cache_detail *detail,						 struct cache_head *key,						 int hash){	struct cache_head *new, *tmp, *freeme = NULL;	struct hlist_head *head = &detail->hash_table[hash]; 	new = detail->alloc();	if (!new)		return NULL;	/* must fully initialise 'new', else	 * we might get lose if we need to	 * cache_put it soon.	 */	cache_init(new, detail);	detail->init(new, key); 	spin_lock(&detail->hash_lock); 	/* check if entry appeared while we slept */	hlist_for_each_entry_rcu(tmp, head, cache_list,				 lockdep_is_held(&detail->hash_lock)) {		if (!detail->match(tmp, key))			continue;		if (test_bit(CACHE_VALID, &tmp->flags) &&		    cache_is_expired(detail, tmp)) {			sunrpc_begin_cache_remove_entry(tmp, detail);			trace_cache_entry_expired(detail, tmp);			freeme = tmp;			break;		}		cache_get(tmp);		spin_unlock(&detail->hash_lock);		cache_put(new, detail);		return tmp;	} 	cache_get(new);	hlist_add_head_rcu(&new->cache_list, head);	detail->entries++;	if (detail->nextcheck > new->expiry_time)		detail->nextcheck = new->expiry_time + 1;	spin_unlock(&detail->hash_lock); 	if (freeme)		sunrpc_end_cache_remove_entry(freeme, detail);	return new;} struct cache_head *sunrpc_cache_lookup_rcu(struct cache_detail *detail,					   struct cache_head *key, int hash){	struct cache_head *ret; 	ret = sunrpc_cache_find_rcu(detail, key, hash);	if (ret)		return ret;	/* Didn't find anything, insert an empty entry */	return sunrpc_cache_add_entry(detail, key, hash);}EXPORT_SYMBOL_GPL(sunrpc_cache_lookup_rcu); static void cache_dequeue(struct cache_detail *detail, struct cache_head *ch); static void cache_fresh_locked(struct cache_head *head, time64_t expiry,			       struct cache_detail *detail){	time64_t now = seconds_since_boot();	if (now <= detail->flush_time)		/* ensure it isn't immediately treated as expired */		now = detail->flush_time + 1;	head->expiry_time = expiry;	head->last_refresh = now;	smp_wmb(); /* paired with smp_rmb() in cache_is_valid() */	set_bit(CACHE_VALID, &head->flags);} static void cache_fresh_unlocked(struct cache_head *head,				 struct cache_detail *detail){	if (test_and_clear_bit(CACHE_PENDING, &head->flags)) {		cache_revisit_request(head);		cache_dequeue(detail, head);	}} static void cache_make_negative(struct cache_detail *detail,				struct cache_head *h){	set_bit(CACHE_NEGATIVE, &h->flags);	trace_cache_entry_make_negative(detail, h);} static void cache_entry_update(struct cache_detail *detail,			       struct cache_head *h,			       struct cache_head *new){	if (!test_bit(CACHE_NEGATIVE, &new->flags)) {		detail->update(h, new);		trace_cache_entry_update(detail, h);	} else {		cache_make_negative(detail, h);	}} 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 *tmp; 	if (!test_bit(CACHE_VALID, &old->flags)) {		spin_lock(&detail->hash_lock);		if (!test_bit(CACHE_VALID, &old->flags)) {			cache_entry_update(detail, old, new);			cache_fresh_locked(old, new->expiry_time, detail);			spin_unlock(&detail->hash_lock);			cache_fresh_unlocked(old, detail);			return old;		}		spin_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);	detail->init(tmp, old); 	spin_lock(&detail->hash_lock);	cache_entry_update(detail, tmp, new);	cache_get(tmp);	hlist_add_head_rcu(&tmp->cache_list, &detail->hash_table[hash]);	detail->entries++;	cache_fresh_locked(tmp, new->expiry_time, detail);	cache_fresh_locked(old, 0, detail);	spin_unlock(&detail->hash_lock);	cache_fresh_unlocked(tmp, detail);	cache_fresh_unlocked(old, detail);	cache_put(old, detail);	return tmp;}EXPORT_SYMBOL_GPL(sunrpc_cache_update); static inline int cache_is_valid(struct cache_head *h){	if (!test_bit(CACHE_VALID, &h->flags))		return -EAGAIN;	else {		/* entry is valid */		if (test_bit(CACHE_NEGATIVE, &h->flags))			return -ENOENT;		else {			/*			 * In combination with write barrier in			 * sunrpc_cache_update, ensures that anyone			 * using the cache entry after this sees the			 * updated contents:			 */			smp_rmb();			return 0;		}	}} static int try_to_negate_entry(struct cache_detail *detail, struct cache_head *h){	int rv; 	spin_lock(&detail->hash_lock);	rv = cache_is_valid(h);	if (rv == -EAGAIN) {		cache_make_negative(detail, h);		cache_fresh_locked(h, seconds_since_boot()+CACHE_NEW_EXPIRY,				   detail);		rv = -ENOENT;	}	spin_unlock(&detail->hash_lock);	cache_fresh_unlocked(h, detail);	return rv;} int cache_check_rcu(struct cache_detail *detail,		    struct cache_head *h, struct cache_req *rqstp){	int rv;	time64_t refresh_age, age; 	/* First decide return status as best we can */	rv = cache_is_valid(h); 	/* now see if we want to start an upcall */	refresh_age = (h->expiry_time - h->last_refresh);	age = seconds_since_boot() - h->last_refresh; 	if (rqstp == NULL) {		if (rv == -EAGAIN)			rv = -ENOENT;	} else if (rv == -EAGAIN ||		   (h->expiry_time != 0 && age > refresh_age/2)) {		dprintk("RPC:       Want update, refage=%lld, age=%lld\n",				refresh_age, age);		switch (detail->cache_upcall(detail, h)) {		case -EINVAL:			rv = try_to_negate_entry(detail, h);			break;		case -EAGAIN:			cache_fresh_unlocked(h, detail);			break;		}	} 	if (rv == -EAGAIN) {		if (!cache_defer_req(rqstp, h)) {			/*			 * Request was not deferred; handle it as best			 * we can ourselves:			 */			rv = cache_is_valid(h);			if (rv == -EAGAIN)				rv = -ETIMEDOUT;		}	} 	return rv;}EXPORT_SYMBOL_GPL(cache_check_rcu); /* * This is the generic cache management routine for all * the authentication caches. * It checks the currency of a cache item and will (later) * initiate an upcall to fill it if needed. * * * Returns 0 if the cache_head can be used, or cache_puts it and returns * -EAGAIN if upcall is pending and request has been queued * -ETIMEDOUT if upcall failed or request could not be queue or *           upcall completed but item is still invalid (implying that *           the cache item has been replaced with a newer one). * -ENOENT if cache entry was negative */int cache_check(struct cache_detail *detail,		struct cache_head *h, struct cache_req *rqstp){	int rv; 	rv = cache_check_rcu(detail, h, rqstp);	if (rv)		cache_put(h, detail);	return rv;}EXPORT_SYMBOL_GPL(cache_check); /* * caches need to be periodically cleaned. * For this we maintain a list of cache_detail and * a current pointer into that list and into the table * for that entry. * * Each time cache_clean is called it finds the next non-empty entry * in the current table and walks the list in that entry * looking for entries that can be removed. * * An entry gets removed if: * - The expiry is before current time * - The last_refresh time is before the flush_time for that cache * * later we might drop old entries with non-NEVER expiry if that table * is getting 'full' for some definition of 'full' * * The question of "how often to scan a table" is an interesting one * and is answered in part by the use of the "nextcheck" field in the * cache_detail. * When a scan of a table begins, the nextcheck field is set to a time * that is well into the future. * While scanning, if an expiry time is found that is earlier than the * current nextcheck time, nextcheck is set to that expiry time. * If the flush_time is ever set to a time earlier than the nextcheck * time, the nextcheck time is then set to that flush_time. * * A table is then only scanned if the current time is at least * the nextcheck time. * */ static LIST_HEAD(cache_list);static DEFINE_SPINLOCK(cache_list_lock);static struct cache_detail *current_detail;static int current_index; static void do_cache_clean(struct work_struct *work);static struct delayed_work cache_cleaner; void sunrpc_init_cache_detail(struct cache_detail *cd){	spin_lock_init(&cd->hash_lock);	INIT_LIST_HEAD(&cd->requests);	INIT_LIST_HEAD(&cd->readers);	spin_lock_init(&cd->queue_lock);	init_waitqueue_head(&cd->queue_wait);	cd->next_seqno = 1;	spin_lock(&cache_list_lock);	cd->nextcheck = 0;	cd->entries = 0;	atomic_set(&cd->writers, 0);	cd->last_close = 0;	cd->last_warn = -1;	list_add(&cd->others, &cache_list);	spin_unlock(&cache_list_lock); 	/* start the cleaning process */	queue_delayed_work(system_power_efficient_wq, &cache_cleaner, 0);}EXPORT_SYMBOL_GPL(sunrpc_init_cache_detail); void sunrpc_destroy_cache_detail(struct cache_detail *cd){	cache_purge(cd);	spin_lock(&cache_list_lock);	spin_lock(&cd->hash_lock);	if (current_detail == cd)		current_detail = NULL;	list_del_init(&cd->others);	spin_unlock(&cd->hash_lock);	spin_unlock(&cache_list_lock);	cancel_delayed_work_sync(&cache_cleaner);	if (!list_empty(&cache_list))		queue_delayed_work(system_power_efficient_wq, &cache_cleaner, 0);}EXPORT_SYMBOL_GPL(sunrpc_destroy_cache_detail); /* clean cache tries to find something to clean * and cleans it. * It returns 1 if it cleaned something, *            0 if it didn't find anything this time *           -1 if it fell off the end of the list. */static int cache_clean(void){	int rv = 0;	struct list_head *next; 	spin_lock(&cache_list_lock); 	/* find a suitable table if we don't already have one */	while (current_detail == NULL ||	    current_index >= current_detail->hash_size) {		if (current_detail)			next = current_detail->others.next;		else			next = cache_list.next;		if (next == &cache_list) {			current_detail = NULL;			spin_unlock(&cache_list_lock);			return -1;		}		current_detail = list_entry(next, struct cache_detail, others);		if (current_detail->nextcheck > seconds_since_boot())			current_index = current_detail->hash_size;		else {			current_index = 0;			current_detail->nextcheck = seconds_since_boot()+30*60;		}	} 	spin_lock(&current_detail->hash_lock); 	/* find a non-empty bucket in the table */	while (current_index < current_detail->hash_size &&	       hlist_empty(&current_detail->hash_table[current_index]))		current_index++; 	/* find a cleanable entry in the bucket and clean it, or set to next bucket */	if (current_index < current_detail->hash_size) {		struct cache_head *ch = NULL;		struct cache_detail *d;		struct hlist_head *head;		struct hlist_node *tmp; 		/* Ok, now to clean this strand */		head = &current_detail->hash_table[current_index];		hlist_for_each_entry_safe(ch, tmp, head, cache_list) {			if (current_detail->nextcheck > ch->expiry_time)				current_detail->nextcheck = ch->expiry_time+1;			if (!cache_is_expired(current_detail, ch))				continue; 			sunrpc_begin_cache_remove_entry(ch, current_detail);			trace_cache_entry_expired(current_detail, ch);			rv = 1;			break;		} 

Lines 1–500 of 2008.