/*
 * The "user cache".
 *
 * (C) Copyright 1991-2000 Linus Torvalds
 *
 * We have a per-user structure to keep track of how many
 * processes, files etc the user has claimed, in order to be
 * able to have per-user limits for system resources. 
 *
 * For the vserver project, the key is extended from UID to (SC,UID),
 * with SC being the security context ID. Thus, each security context
 * has independant per-UID resource usage counters.
 *
 * As a consequence, even if two UIDs are the same, the 'struct user *'
 * in their task_struct could be different. I don't think any code cares.
 *
 * (vserver modifications done Sun Jan 13 08:48:45 CET 2002 by bof@bof.de)
 *
 * NOTE: For now, the hash function is unmodified: the same uid in several
 * security contexts, will always sit on the same hash chain. This could
 * be changed easily.
 */

#include <linux/init.h>
#include <linux/sched.h>
#include <linux/slab.h>

/*
 * UID task count cache, to get fast user lookup in "alloc_uid"
 * when changing user ID's (ie setuid() and friends).
 */
#define UIDHASH_BITS		8
#define UIDHASH_SZ		(1 << UIDHASH_BITS)
#define UIDHASH_MASK		(UIDHASH_SZ - 1)

/*
 * hash function borrowed from Chuck Lever's paper
 * The effects of this replacement have not been measured.
 * -- wli
 */
#define __uidhashfn(uid) \
	(((2654435761UL*(uid)) >> (BITS_PER_LONG-UIDHASH_BITS)) & UIDHASH_MASK)
#define uidhashentry(uid)	(uidhash_table + __uidhashfn(uid))

static kmem_cache_t *uid_cachep;
static struct user_struct *uidhash_table[UIDHASH_SZ];
static spinlock_t uidhash_lock = SPIN_LOCK_UNLOCKED;

struct user_struct root_user = {
	__count:	ATOMIC_INIT(1),
	processes:	ATOMIC_INIT(1),
	files:		ATOMIC_INIT(0)
};

/*
 * These routines must be called with the uidhash spinlock held!
 */
static inline void uid_hash_insert(struct user_struct *up, struct user_struct **hashent)
{
	struct user_struct *next = *hashent;

	up->next = next;
	if (next)
		next->pprev = &up->next;
	up->pprev = hashent;
	*hashent = up;
}

static inline void uid_hash_remove(struct user_struct *up)
{
	struct user_struct *next = up->next;
	struct user_struct **pprev = up->pprev;

	if (next)
		next->pprev = pprev;
	*pprev = next;
}

#ifdef CONFIG_SCONTEXTS
static inline struct user_struct *uid_hash_find(int s_context, uid_t uid, struct user_struct **hashent)
#else
static inline struct user_struct *uid_hash_find(uid_t uid, struct user_struct **hashent)
#endif /* CONFIG_SCONTEXTS */
{
	struct user_struct *next;

	next = *hashent;
	for (;;) {
		struct user_struct *up = next;
		if (next) {
			next = up->next;
#ifdef CONFIG_SCONTEXTS
			if (up->uid != uid || up->s_context != s_context)
#else
			if (up->uid != uid)
#endif /* CONFIG_SCONTEXTS */
				continue;
			atomic_inc(&up->__count);
		}
		return up;
	}
}

void free_uid(struct user_struct *up)
{
	if (up && atomic_dec_and_lock(&up->__count, &uidhash_lock)) {
		uid_hash_remove(up);
		kmem_cache_free(uid_cachep, up);
		spin_unlock(&uidhash_lock);
	}
}

#ifdef CONFIG_SCONTEXTS
struct user_struct * alloc_uid(int s_context, uid_t uid)
#else
struct user_struct * alloc_uid(uid_t uid)
#endif /* CONFIG_SCONTEXTS */
{
	struct user_struct **hashent = uidhashentry(uid);
	struct user_struct *up;

	spin_lock(&uidhash_lock);
#ifdef CONFIG_SCONTEXTS
	up = uid_hash_find(s_context, uid, hashent);
#else
	up = uid_hash_find(uid, hashent);
#endif /* CONFIG_SCONTEXTS */
	spin_unlock(&uidhash_lock);

	if (!up) {
		struct user_struct *new;

		new = kmem_cache_alloc(uid_cachep, SLAB_KERNEL);
		if (!new)
			return NULL;
		new->uid = uid;
#ifdef CONFIG_SCONTEXTS
		new->s_context = s_context;
#endif /* CONFIG_SCONTEXTS */
		atomic_set(&new->__count, 1);
		atomic_set(&new->processes, 0);
		atomic_set(&new->files, 0);

		/*
		 * Before adding this, check whether we raced
		 * on adding the same user already..
		 */
		spin_lock(&uidhash_lock);
#ifdef CONFIG_SCONTEXTS
		up = uid_hash_find(s_context, uid, hashent);
#else
		up = uid_hash_find(uid, hashent);
#endif /* CONFIG_SCONTEXTS */
		if (up) {
			kmem_cache_free(uid_cachep, new);
		} else {
			uid_hash_insert(new, hashent);
			up = new;
		}
		spin_unlock(&uidhash_lock);

	}
	return up;
}

void switch_uid(struct user_struct *new_user)
{
 struct user_struct *old_user;

 /* What if a process setreuid()'s and this brings the
 * new uid over his NPROC rlimit? We can check this now
 * cheaply with the new uid cache, so if it matters
 * we should be checking for it. -DaveM
 */
 old_user = current->user;
 atomic_inc(&new_user->__count);
 atomic_inc(&new_user->processes);
 atomic_dec(&old_user->processes);
 current->user = new_user;
 free_uid(old_user);
}


static int __init uid_cache_init(void)
{
	uid_cachep = kmem_cache_create("uid_cache", sizeof(struct user_struct),
				       0,
				       SLAB_HWCACHE_ALIGN, NULL, NULL);
	if(!uid_cachep)
		panic("Cannot create uid taskcount SLAB cache\n");

	/* Insert the root user immediately - init already runs with this */
	uid_hash_insert(&root_user, uidhashentry(0));
	return 0;
}

module_init(uid_cache_init);
