/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */

/*
 * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

/*
 * Windows to Solaris Identity Mapping kernel API
 * This module provides the kernel cache.
 */


#include <sys/types.h>
#include <sys/avl.h>
#include <sys/systm.h>
#include <sys/sysmacros.h>
#include <sys/ksynch.h>
#include <sys/kidmap.h>
#include <rpcsvc/idmap_prot.h>
#include "kidmap_priv.h"


/*
 * External functions
 */
extern	uintptr_t	space_fetch(char *key);
extern	int		space_store(char *key, uintptr_t ptr);


/*
 * Internal definitions and functions
 */

#define	CACHE_UID_TRIGGER_SIZE	4096
#define	CACHE_GID_TRIGGER_SIZE	2048
#define	CACHE_PID_TRIGGER_SIZE \
	(CACHE_UID_TRIGGER_SIZE + CACHE_GID_TRIGGER_SIZE)


#define	UNDEF_UID	((uid_t)-1)
#define	UNDEF_GID	((gid_t)-1)
#define	UNDEF_ISUSER	(-1)

#define	CACHE_PURGE_INTERVAL	(60 * 3)
#define	CACHE_TTL		(60 * 10)



#define	list_insert(head, ele)\
	do {\
		(ele)->flink = (head)->flink;\
		(head)->flink = (ele);\
		(ele)->blink = (ele)->flink->blink;\
		(ele)->flink->blink = (ele);\
	} while (0)



#define	list_remove(ele)\
	do {\
		(ele)->flink->blink = (ele)->blink;\
		(ele)->blink->flink = (ele)->flink;\
	} while (0)


#define	list_move(head, ele) \
	do {\
		if ((head)->flink != (ele)) {\
			list_remove(ele);\
			list_insert(head, ele);\
		}\
	} while (0)


typedef struct sid_prefix_node {
	avl_node_t	avl_link;
	const char 	*sid_prefix;
} sid_prefix_node_t;


typedef int (*avl_comp_fn)(const void*, const void*);


struct sid_prefix_store {
	struct avl_tree	tree;
	krwlock_t	lock;
};

struct sid_prefix_store *kidmap_sid_prefix_store = NULL;



static void
kidmap_purge_sid2pid_cache(idmap_sid2pid_cache_t *cache, size_t limit);

static void
kidmap_purge_pid2sid_cache(idmap_pid2sid_cache_t *cache, size_t limit);


/*
 * kidmap_strdup() copied from uts/common/fs/sockfs/nl7c.c
 */
static char *
kidmap_strdup(const char *s)
{
	int	len = strlen(s) + 1;
	char	*ret = kmem_alloc(len, KM_SLEEP);

	bcopy(s, ret, len);
	return (ret);
}


static int
kidmap_compare_sid(const sid2pid_t *entry1, const sid2pid_t *entry2)
{
	int64_t comp = ((int64_t)entry2->rid) - ((int64_t)entry1->rid);

	if (comp == 0)
		comp = strcmp(entry2->sid_prefix, entry1->sid_prefix);

	if (comp < 0)
		comp = -1;
	else if (comp > 0)
		comp = 1;

	return ((int)comp);
}


static int
kidmap_compare_pid(const pid2sid_t *entry1, const pid2sid_t *entry2)
{
	if (entry2->pid > entry1->pid)
		return (1);
	if (entry2->pid < entry1->pid)
		return (-1);
	return (0);
}


static int
kidmap_compare_sid_prefix(const sid_prefix_node_t *entry1,
			const sid_prefix_node_t *entry2)
{
	int comp;

	comp = strcmp(entry2->sid_prefix, entry1->sid_prefix);

	if (comp < 0)
		comp = -1;
	else if (comp > 0)
		comp = 1;

	return (comp);
}


void
kidmap_cache_create(idmap_cache_t *cache)
{
	avl_create(&cache->sid2pid.tree, (avl_comp_fn)kidmap_compare_sid,
	    sizeof (sid2pid_t), offsetof(sid2pid_t, avl_link));
	mutex_init(&cache->sid2pid.mutex, NULL, MUTEX_DEFAULT, NULL);
	cache->sid2pid.purge_time = 0;
	cache->sid2pid.head.flink = &cache->sid2pid.head;
	cache->sid2pid.head.blink = &cache->sid2pid.head;
	cache->sid2pid.uid_num = 0;
	cache->sid2pid.gid_num = 0;
	cache->sid2pid.pid_num = 0;

	avl_create(&cache->uid2sid.tree, (avl_comp_fn)kidmap_compare_pid,
	    sizeof (pid2sid_t), offsetof(pid2sid_t, avl_link));
	mutex_init(&cache->uid2sid.mutex, NULL, MUTEX_DEFAULT, NULL);
	cache->uid2sid.purge_time = 0;
	cache->uid2sid.head.flink = &cache->uid2sid.head;
	cache->uid2sid.head.blink = &cache->uid2sid.head;

	avl_create(&cache->gid2sid.tree, (avl_comp_fn)kidmap_compare_pid,
	    sizeof (pid2sid_t), offsetof(pid2sid_t, avl_link));
	mutex_init(&cache->gid2sid.mutex, NULL, MUTEX_DEFAULT, NULL);
	cache->gid2sid.purge_time = 0;
	cache->gid2sid.head.flink = &cache->gid2sid.head;
	cache->gid2sid.head.blink = &cache->gid2sid.head;
}


void
kidmap_cache_delete(idmap_cache_t *cache)
{
	sid2pid_t *sid2pid;
	pid2sid_t *pid2sid;
	void *cookie;

	cookie = NULL;
	while ((sid2pid = avl_destroy_nodes(&cache->sid2pid.tree, &cookie))
	    != NULL) {
		kmem_free(sid2pid, sizeof (sid2pid_t));
	}
	avl_destroy(&cache->sid2pid.tree);
	mutex_destroy(&cache->sid2pid.mutex);


	cookie = NULL;
	while ((pid2sid = avl_destroy_nodes(&cache->uid2sid.tree, &cookie))
	    != NULL) {
		kmem_free(pid2sid, sizeof (pid2sid_t));
	}
	avl_destroy(&cache->uid2sid.tree);
	mutex_destroy(&cache->uid2sid.mutex);


	cookie = NULL;
	while ((pid2sid = avl_destroy_nodes(&cache->gid2sid.tree, &cookie))
	    != NULL) {
		kmem_free(pid2sid, sizeof (pid2sid_t));
	}
	avl_destroy(&cache->gid2sid.tree);
	mutex_destroy(&cache->gid2sid.mutex);
}


void
kidmap_cache_get_data(idmap_cache_t *cache, size_t *uidbysid, size_t *gidbysid,
	size_t *pidbysid, size_t *sidbyuid, size_t *sidbygid)
{
	mutex_enter(&cache->sid2pid.mutex);
	*uidbysid = cache->sid2pid.uid_num;
	*gidbysid = cache->sid2pid.gid_num;
	*pidbysid = cache->sid2pid.pid_num;
	mutex_exit(&cache->sid2pid.mutex);

	mutex_enter(&cache->uid2sid.mutex);
	*sidbyuid = avl_numnodes(&cache->uid2sid.tree);
	mutex_exit(&cache->uid2sid.mutex);

	mutex_enter(&cache->gid2sid.mutex);
	*sidbygid = avl_numnodes(&cache->gid2sid.tree);
	mutex_exit(&cache->gid2sid.mutex);
}


void
kidmap_cache_purge(idmap_cache_t *cache)
{
	sid2pid_t *sid2pid;
	pid2sid_t *pid2sid;
	void *cookie;

	mutex_enter(&cache->sid2pid.mutex);
	cookie = NULL;
	while ((sid2pid = avl_destroy_nodes(&cache->sid2pid.tree, &cookie))
	    != NULL) {
		kmem_free(sid2pid, sizeof (sid2pid_t));
	}
	avl_destroy(&cache->sid2pid.tree);
	avl_create(&cache->sid2pid.tree, (avl_comp_fn)kidmap_compare_sid,
	    sizeof (sid2pid_t), offsetof(sid2pid_t, avl_link));
	cache->sid2pid.purge_time = 0;
	cache->sid2pid.head.flink = &cache->sid2pid.head;
	cache->sid2pid.head.blink = &cache->sid2pid.head;
	cache->sid2pid.uid_num = 0;
	cache->sid2pid.gid_num = 0;
	cache->sid2pid.pid_num = 0;
	mutex_exit(&cache->sid2pid.mutex);


	mutex_enter(&cache->uid2sid.mutex);
	cookie = NULL;
	while ((pid2sid = avl_destroy_nodes(&cache->uid2sid.tree, &cookie))
	    != NULL) {
		kmem_free(pid2sid, sizeof (pid2sid_t));
	}
	avl_destroy(&cache->uid2sid.tree);
	avl_create(&cache->uid2sid.tree, (avl_comp_fn)kidmap_compare_pid,
	    sizeof (pid2sid_t), offsetof(pid2sid_t, avl_link));
	cache->uid2sid.purge_time = 0;
	cache->uid2sid.head.flink = &cache->uid2sid.head;
	cache->uid2sid.head.blink = &cache->uid2sid.head;
	mutex_exit(&cache->uid2sid.mutex);


	mutex_enter(&cache->gid2sid.mutex);
	cookie = NULL;
	while ((pid2sid = avl_destroy_nodes(&cache->gid2sid.tree, &cookie))
	    != NULL) {
		kmem_free(pid2sid, sizeof (pid2sid_t));
	}
	avl_destroy(&cache->gid2sid.tree);
	avl_create(&cache->gid2sid.tree, (avl_comp_fn)kidmap_compare_pid,
	    sizeof (pid2sid_t), offsetof(pid2sid_t, avl_link));
	cache->gid2sid.purge_time = 0;
	cache->gid2sid.head.flink = &cache->gid2sid.head;
	cache->gid2sid.head.blink = &cache->gid2sid.head;
	mutex_exit(&cache->gid2sid.mutex);
}


int
kidmap_cache_lookup_uidbysid(idmap_cache_t *cache, const char *sid_prefix,
			uint32_t rid, uid_t *uid)
{
	sid2pid_t	entry;
	sid2pid_t	*result;
	avl_index_t	where;
	int		status = IDMAP_ERR_NOMAPPING;
	time_t		now = gethrestime_sec();

	entry.sid_prefix = sid_prefix;
	entry.rid = rid;

	mutex_enter(&cache->sid2pid.mutex);

	result = avl_find(&cache->sid2pid.tree, &entry, &where);
	if (result != NULL) {
		list_move(&cache->sid2pid.head, result);
		if (result->uid != UNDEF_UID && result->uid_ttl > now) {
			*uid = result->uid;
			status = IDMAP_SUCCESS;
		}
	}

	mutex_exit(&cache->sid2pid.mutex);

	return (status);
}


int
kidmap_cache_lookup_gidbysid(idmap_cache_t *cache, const char *sid_prefix,
			uint32_t rid, gid_t *gid)
{
	sid2pid_t	entry;
	sid2pid_t	*result;
	avl_index_t	where;
	int		status = IDMAP_ERR_NOMAPPING;
	time_t		now = gethrestime_sec();

	entry.sid_prefix = sid_prefix;
	entry.rid = rid;

	mutex_enter(&cache->sid2pid.mutex);

	result = avl_find(&cache->sid2pid.tree, &entry, &where);
	if (result != NULL) {
		list_move(&cache->sid2pid.head, result);
		if (result->gid != UNDEF_GID && result->gid_ttl > now) {
			*gid = result->gid;
			status = IDMAP_SUCCESS;
		}
	}

	mutex_exit(&cache->sid2pid.mutex);

	return (status);
}


int
kidmap_cache_lookup_pidbysid(idmap_cache_t *cache, const char *sid_prefix,
			uint32_t rid, uid_t *pid, int *is_user)
{
	sid2pid_t	entry;
	sid2pid_t	*result;
	avl_index_t	where;
	int		status = IDMAP_ERR_NOMAPPING;
	time_t		now = gethrestime_sec();

	entry.sid_prefix = sid_prefix;
	entry.rid = rid;

	mutex_enter(&cache->sid2pid.mutex);

	result = avl_find(&cache->sid2pid.tree, &entry, &where);
	if (result != NULL) {
		list_move(&cache->sid2pid.head, result);
		if (result->is_user != UNDEF_ISUSER) {
			if (result->is_user && result->uid_ttl > now) {
				*pid = result->uid;
				*is_user = result->is_user;
				status = IDMAP_SUCCESS;
			} else if (!result->is_user && result->gid_ttl > now) {
				*pid = result->gid;
				*is_user = result->is_user;
				status = IDMAP_SUCCESS;
			}
		}
	}

	mutex_exit(&cache->sid2pid.mutex);

	return (status);
}



int
kidmap_cache_lookup_sidbyuid(idmap_cache_t *cache, const char **sid_prefix,
			uint32_t *rid, uid_t uid)
{
	pid2sid_t	entry;
	pid2sid_t	*result;
	avl_index_t	where;
	int		status = IDMAP_ERR_NOMAPPING;
	time_t		now = gethrestime_sec();

	entry.pid = uid;

	mutex_enter(&cache->uid2sid.mutex);

	result = avl_find(&cache->uid2sid.tree, &entry, &where);
	if (result != NULL) {
		list_move(&cache->uid2sid.head, result);
		if (result->ttl > now) {
			*sid_prefix = result->sid_prefix;
			*rid = result->rid;
			status = IDMAP_SUCCESS;
		}
	}

	mutex_exit(&cache->uid2sid.mutex);

	return (status);
}


int
kidmap_cache_lookup_sidbygid(idmap_cache_t *cache, const char **sid_prefix,
			uint32_t *rid, gid_t gid)
{
	pid2sid_t	entry;
	pid2sid_t	*result;
	avl_index_t	where;
	int		status = IDMAP_ERR_NOMAPPING;
	time_t		now = gethrestime_sec();

	entry.pid = gid;

	mutex_enter(&cache->gid2sid.mutex);

	result = avl_find(&cache->gid2sid.tree, &entry, &where);
	if (result != NULL) {
		list_move(&cache->gid2sid.head, result);
		if (result->ttl > now) {
			*sid_prefix = result->sid_prefix;
			*rid = result->rid;
			status = IDMAP_SUCCESS;
		}
	}

	mutex_exit(&cache->gid2sid.mutex);

	return (status);
}


void
kidmap_cache_add_sid2uid(idmap_cache_t *cache, const char *sid_prefix,
			uint32_t rid, uid_t uid, int direction)

{
	avl_index_t	where;
	time_t		ttl = CACHE_TTL + gethrestime_sec();


	if (direction == IDMAP_DIRECTION_BI ||
	    direction == IDMAP_DIRECTION_W2U) {
		sid2pid_t	find;
		sid2pid_t	*result;
		sid2pid_t	*new;

		find.sid_prefix = sid_prefix;
		find.rid = rid;

		mutex_enter(&cache->sid2pid.mutex);

		result = avl_find(&cache->sid2pid.tree, &find, &where);
		if (result) {
			if (result->uid == UNDEF_UID)
				cache->sid2pid.uid_num++;
			result->uid = uid;
			result->uid_ttl = ttl;
		} else {
			new = kmem_alloc(sizeof (sid2pid_t), KM_SLEEP);
			new->sid_prefix = sid_prefix;
			new->rid = rid;
			new->uid = uid;
			new->uid_ttl = ttl;
			new->gid = UNDEF_GID;
			new->gid_ttl = 0;
			new->is_user = UNDEF_ISUSER; /* Unknown */
			cache->sid2pid.uid_num++;

			list_insert(&cache->sid2pid.head, new);
			avl_insert(&cache->sid2pid.tree, new, where);
		}

		if ((avl_numnodes(&cache->sid2pid.tree) >
		    CACHE_PID_TRIGGER_SIZE) &&
		    (cache->sid2pid.purge_time + CACHE_PURGE_INTERVAL <
		    gethrestime_sec()))
			kidmap_purge_sid2pid_cache(&cache->sid2pid,
			    CACHE_PID_TRIGGER_SIZE);

		mutex_exit(&cache->sid2pid.mutex);
	}

	if (direction == IDMAP_DIRECTION_BI ||
	    direction == IDMAP_DIRECTION_U2W) {
		pid2sid_t	find;
		pid2sid_t	*result;
		pid2sid_t	*new;

		find.pid = uid;

		mutex_enter(&cache->uid2sid.mutex);

		result = avl_find(&cache->uid2sid.tree, &find, &where);
		if (result) {
			result->sid_prefix = sid_prefix;
			result->rid = rid;
			result->ttl = ttl;
		} else {
			new = kmem_alloc(sizeof (pid2sid_t), KM_SLEEP);
			new->sid_prefix = sid_prefix;
			new->rid = rid;
			new->pid = uid;
			new->ttl = ttl;

			list_insert(&cache->uid2sid.head, new);
			avl_insert(&cache->uid2sid.tree, new, where);
		}

		if ((avl_numnodes(&cache->uid2sid.tree) >
		    CACHE_UID_TRIGGER_SIZE) &&
		    (cache->uid2sid.purge_time + CACHE_PURGE_INTERVAL <
		    gethrestime_sec()))
			kidmap_purge_pid2sid_cache(&cache->uid2sid,
			    CACHE_UID_TRIGGER_SIZE);

		mutex_exit(&cache->uid2sid.mutex);
	}
}



void
kidmap_cache_add_sid2gid(idmap_cache_t *cache, const char *sid_prefix,
			uint32_t rid, gid_t gid, int direction)
{
	avl_index_t	where;
	time_t		ttl = CACHE_TTL + gethrestime_sec();


	if (direction == IDMAP_DIRECTION_BI ||
	    direction == IDMAP_DIRECTION_W2U) {
		sid2pid_t	find;
		sid2pid_t	*result;
		sid2pid_t	*new;

		find.sid_prefix = sid_prefix;
		find.rid = rid;

		mutex_enter(&cache->sid2pid.mutex);

		result = avl_find(&cache->sid2pid.tree, &find, &where);
		if (result) {
			if (result->gid == UNDEF_GID)
				cache->sid2pid.gid_num++;
			result->gid = gid;
			result->gid_ttl = ttl;
		} else {
			new = kmem_alloc(sizeof (sid2pid_t), KM_SLEEP);
			new->sid_prefix = sid_prefix;
			new->rid = rid;
			new->uid = UNDEF_UID;
			new->uid_ttl = 0;
			new->gid = gid;
			new->gid_ttl = ttl;
			new->is_user = UNDEF_ISUSER; /* Unknown */
			cache->sid2pid.gid_num++;

			list_insert(&cache->sid2pid.head, new);
			avl_insert(&cache->sid2pid.tree, new, where);
		}

		if ((avl_numnodes(&cache->sid2pid.tree) >
		    CACHE_PID_TRIGGER_SIZE) &&
		    (cache->sid2pid.purge_time + CACHE_PURGE_INTERVAL <
		    gethrestime_sec()))
			kidmap_purge_sid2pid_cache(&cache->sid2pid,
			    CACHE_PID_TRIGGER_SIZE);

		mutex_exit(&cache->sid2pid.mutex);
	}

	if (direction == IDMAP_DIRECTION_BI ||
	    direction == IDMAP_DIRECTION_U2W) {
		pid2sid_t	find;
		pid2sid_t	*result;
		pid2sid_t	*new;

		find.pid = gid;

		mutex_enter(&cache->gid2sid.mutex);

		result = avl_find(&cache->gid2sid.tree, &find, &where);
		if (result) {
			result->sid_prefix = sid_prefix;
			result->rid = rid;
			result->ttl = ttl;
		} else {
			new = kmem_alloc(sizeof (pid2sid_t), KM_SLEEP);
			new->sid_prefix = sid_prefix;
			new->rid = rid;
			new->pid = gid;
			new->ttl = ttl;

			list_insert(&cache->gid2sid.head, new);
			avl_insert(&cache->gid2sid.tree, new, where);
		}

		if ((avl_numnodes(&cache->gid2sid.tree) >
		    CACHE_GID_TRIGGER_SIZE) &&
		    (cache->gid2sid.purge_time + CACHE_PURGE_INTERVAL <
		    gethrestime_sec()))
			kidmap_purge_pid2sid_cache(&cache->gid2sid,
			    CACHE_GID_TRIGGER_SIZE);

		mutex_exit(&cache->gid2sid.mutex);
	}
}


void
kidmap_cache_add_sid2pid(idmap_cache_t *cache, const char *sid_prefix,
			uint32_t rid, uid_t pid, int is_user, int direction)
{
	avl_index_t	where;
	time_t		ttl = CACHE_TTL + gethrestime_sec();


	if (direction == IDMAP_DIRECTION_BI ||
	    direction == IDMAP_DIRECTION_W2U) {
		sid2pid_t	find;
		sid2pid_t	*result;
		sid2pid_t	*new;

		find.sid_prefix = sid_prefix;
		find.rid = rid;

		mutex_enter(&cache->sid2pid.mutex);

		result = avl_find(&cache->sid2pid.tree, &find, &where);
		if (result) {
			if (result->is_user == UNDEF_ISUSER)
				cache->sid2pid.pid_num++;
			result->is_user = is_user;
			if (is_user) {
				if (result->uid == UNDEF_UID)
					cache->sid2pid.uid_num++;
				result->uid = pid;
				result->uid_ttl = ttl;
			} else {
				if (result->gid == UNDEF_GID)
					cache->sid2pid.gid_num++;
				result->gid = pid;
				result->gid_ttl = ttl;
			}
		} else {
			new = kmem_alloc(sizeof (sid2pid_t), KM_SLEEP);
			new->sid_prefix = sid_prefix;
			new->rid = rid;
			new->is_user = is_user;
			if (is_user) {
				new->uid = pid;
				new->uid_ttl = ttl;
				new->gid = UNDEF_GID;
				new->gid_ttl = 0;
				cache->sid2pid.uid_num++;
			} else {
				new->uid = UNDEF_UID;
				new->uid_ttl = 0;
				new->gid = pid;
				new->gid_ttl = ttl;
				cache->sid2pid.gid_num++;
			}
			cache->sid2pid.pid_num++;

			list_insert(&cache->sid2pid.head, new);
			avl_insert(&cache->sid2pid.tree, new, where);
		}

		if ((avl_numnodes(&cache->sid2pid.tree) >
		    CACHE_PID_TRIGGER_SIZE) &&
		    (cache->sid2pid.purge_time + CACHE_PURGE_INTERVAL <
		    gethrestime_sec()))
			kidmap_purge_sid2pid_cache(&cache->sid2pid,
			    CACHE_PID_TRIGGER_SIZE);

		mutex_exit(&cache->sid2pid.mutex);
	}

	if (direction == IDMAP_DIRECTION_BI ||
	    direction == IDMAP_DIRECTION_U2W) {
		pid2sid_t	find;
		pid2sid_t	*result;
		pid2sid_t	*new;

		find.pid = pid;
		if (is_user) {
			mutex_enter(&cache->uid2sid.mutex);

			result = avl_find(&cache->uid2sid.tree, &find, &where);
			if (result) {
				result->sid_prefix = sid_prefix;
				result->rid = rid;
				result->ttl = ttl;
			} else {
				new = kmem_alloc(sizeof (pid2sid_t), KM_SLEEP);
				new->sid_prefix = sid_prefix;
				new->rid = rid;
				new->pid = pid;
				new->ttl = ttl;

				list_insert(&cache->uid2sid.head, new);
				avl_insert(&cache->uid2sid.tree, new, where);
			}

			if ((avl_numnodes(&cache->uid2sid.tree) >
			    CACHE_UID_TRIGGER_SIZE) &&
			    (cache->uid2sid.purge_time +
			    CACHE_PURGE_INTERVAL <
			    gethrestime_sec()))
				kidmap_purge_pid2sid_cache(&cache->uid2sid,
				    CACHE_UID_TRIGGER_SIZE);

			mutex_exit(&cache->uid2sid.mutex);
		} else {
			mutex_enter(&cache->gid2sid.mutex);

			result = avl_find(&cache->gid2sid.tree, &find, &where);
			if (result) {
				result->sid_prefix = sid_prefix;
				result->rid = rid;
				result->ttl = ttl;
			} else {
				new = kmem_alloc(sizeof (pid2sid_t), KM_SLEEP);
				new->sid_prefix = sid_prefix;
				new->rid = rid;
				new->pid = pid;
				new->ttl = ttl;

				list_insert(&cache->gid2sid.head, new);
				avl_insert(&cache->gid2sid.tree, new, where);
			}

			if ((avl_numnodes(&cache->gid2sid.tree) >
			    CACHE_GID_TRIGGER_SIZE) &&
			    (cache->gid2sid.purge_time +
			    CACHE_PURGE_INTERVAL < gethrestime_sec()))
				kidmap_purge_pid2sid_cache(&cache->gid2sid,
				    CACHE_GID_TRIGGER_SIZE);

			mutex_exit(&cache->gid2sid.mutex);
		}
	}
}





static void
kidmap_purge_sid2pid_cache(idmap_sid2pid_cache_t *cache, size_t limit)
{
	time_t		now = gethrestime_sec();
	sid2pid_t	*item;

	while (avl_numnodes(&cache->tree) > limit) {
		/* Remove least recently used */
		item = cache->head.blink;
		list_remove(item);
		avl_remove(&cache->tree, item);
		if (item->uid != UNDEF_UID)
			cache->uid_num--;
		if (item->gid != UNDEF_GID)
			cache->gid_num--;
		if (item->is_user != UNDEF_ISUSER)
			cache->pid_num--;
		kmem_free(item, sizeof (sid2pid_t));
	}
	cache->purge_time = now;
}


static void
kidmap_purge_pid2sid_cache(idmap_pid2sid_cache_t *cache, size_t limit)
{
	time_t		now = gethrestime_sec();
	pid2sid_t	*item;

	while (avl_numnodes(&cache->tree) > limit) {
		/* Remove least recently used */
		item = cache->head.blink;
		list_remove(item);
		avl_remove(&cache->tree, item);
		kmem_free(item, sizeof (pid2sid_t));
	}
	cache->purge_time = now;
}


void
kidmap_sid_prefix_store_init(void)
{
	kidmap_sid_prefix_store = (struct sid_prefix_store *)
	    space_fetch("SUNW,idmap_sid_prefix");
	if (kidmap_sid_prefix_store == NULL) {
		kidmap_sid_prefix_store = kmem_alloc(
		    sizeof (struct sid_prefix_store), KM_SLEEP);
		rw_init(&kidmap_sid_prefix_store->lock, NULL, RW_DRIVER, NULL);
		avl_create(&kidmap_sid_prefix_store->tree,
		    (avl_comp_fn)kidmap_compare_sid_prefix,
		    sizeof (sid_prefix_node_t),
		    offsetof(sid_prefix_node_t, avl_link));
		(void) space_store("SUNW,idmap_sid_prefix",
		    (uintptr_t)kidmap_sid_prefix_store);
	} else {
		/*
		 * The AVL comparison function must be re-initialised on
		 * re-load because may not be loaded into the same
		 * address space.
		 */
		kidmap_sid_prefix_store->tree.avl_compar =
		    (avl_comp_fn)kidmap_compare_sid_prefix;
	}
}


const char *
kidmap_find_sid_prefix(const char *sid_prefix) {
	sid_prefix_node_t 	find;
	sid_prefix_node_t	*result;
	sid_prefix_node_t 	*new;
	avl_index_t		where;

	if (sid_prefix == NULL || *sid_prefix == '\0')
		return (NULL);

	find.sid_prefix = sid_prefix;

	rw_enter(&kidmap_sid_prefix_store->lock, RW_READER);

	result = avl_find(&kidmap_sid_prefix_store->tree, &find, &where);

	if (result) {
		rw_exit(&kidmap_sid_prefix_store->lock);
		return (result->sid_prefix);
	}

	if (rw_tryupgrade(&kidmap_sid_prefix_store->lock) == 0) {
		/*
		 * Could not upgrade lock so release lock
		 * and acquire the write lock
		 */
		rw_exit(&kidmap_sid_prefix_store->lock);
		rw_enter(&kidmap_sid_prefix_store->lock, RW_WRITER);

		result = avl_find(&kidmap_sid_prefix_store->tree,
			&find, &where);
		if (result) {
			rw_exit(&kidmap_sid_prefix_store->lock);
			return (result->sid_prefix);
		}
	}

	new = kmem_alloc(sizeof (sid_prefix_node_t), KM_SLEEP);
	new->sid_prefix = kidmap_strdup(sid_prefix);
	avl_insert(&kidmap_sid_prefix_store->tree, new, where);
	rw_exit(&kidmap_sid_prefix_store->lock);

	return (new->sid_prefix);
}