OpenSolaris_b135/cmd/nscd/cache.c

Compare this file to the similar file:
Show the results in this format:

/*
 * 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 2007 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

#pragma ident	"%Z%%M%	%I%	%E% SMI"

/*
 * Cache routines for nscd
 */
#include <assert.h>
#include <errno.h>
#include <memory.h>
#include <signal.h>
#include <stdlib.h>
#include <stddef.h>
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <ucred.h>
#include <nss_common.h>
#include <locale.h>
#include <ctype.h>
#include <strings.h>
#include <string.h>
#include <umem.h>
#include <fcntl.h>
#include "cache.h"
#include "nscd_door.h"
#include "nscd_log.h"
#include "nscd_config.h"
#include "nscd_frontend.h"
#include "nscd_switch.h"

#define	SUCCESS		0
#define	NOTFOUND	-1
#define	SERVERERROR	-2
#define	NOSERVER	-3
#define	CONTINUE	-4

static nsc_db_t *nsc_get_db(nsc_ctx_t *, int);
static nscd_rc_t lookup_cache(nsc_lookup_args_t *, nscd_cfg_cache_t *,
		nss_XbyY_args_t *, char *, nsc_entry_t **);
static uint_t reap_cache(nsc_ctx_t *, uint_t, uint_t);
static void delete_entry(nsc_db_t *, nsc_ctx_t *, nsc_entry_t *);
static void print_stats(nscd_cfg_stat_cache_t *);
static void print_cfg(nscd_cfg_cache_t *);
static int lookup_int(nsc_lookup_args_t *, int);

#ifdef	NSCD_DEBUG
static void print_entry(nsc_db_t *, time_t, nsc_entry_t *);
static void avl_dump(nsc_db_t *, time_t);
static void hash_dump(nsc_db_t *, time_t);
#endif	/* NSCD_DEBUG */
static nsc_entry_t *hash_find(nsc_db_t *, nsc_entry_t *, uint_t *, nscd_bool_t);

static void queue_adjust(nsc_db_t *, nsc_entry_t *);
static void queue_remove(nsc_db_t *, nsc_entry_t *);
#ifdef	NSCD_DEBUG
static void queue_dump(nsc_db_t *, time_t);
#endif	/* NSCD_DEBUG */

static int launch_update(nsc_lookup_args_t *);
static void do_update(nsc_lookup_args_t *);
static void getxy_keepalive(nsc_ctx_t *, nsc_db_t *, int, int);

static void ctx_info(nsc_ctx_t *);
static void ctx_info_nolock(nsc_ctx_t *);
static void ctx_invalidate(nsc_ctx_t *);

static void nsc_db_str_key_getlogstr(char *, char *, size_t, nss_XbyY_args_t *);
static void nsc_db_int_key_getlogstr(char *, char *, size_t, nss_XbyY_args_t *);
static void nsc_db_any_key_getlogstr(char *, char *, size_t, nss_XbyY_args_t *);

static int nsc_db_cis_key_compar(const void *, const void *);
static int nsc_db_ces_key_compar(const void *, const void *);
static int nsc_db_int_key_compar(const void *, const void *);

static uint_t nsc_db_cis_key_gethash(nss_XbyY_key_t *, int);
static uint_t nsc_db_ces_key_gethash(nss_XbyY_key_t *, int);
static uint_t nsc_db_int_key_gethash(nss_XbyY_key_t *, int);

static umem_cache_t	*nsc_entry_cache;

static nsc_ctx_t *init_cache_ctx(int);
static void reaper(nsc_ctx_t *);
static void revalidate(nsc_ctx_t *);

static nss_status_t
dup_packed_buffer(void *src, void *dst) {
	nsc_lookup_args_t	*s = (nsc_lookup_args_t *)src;
	nsc_entry_t		*d = (nsc_entry_t *)dst;
	nss_pheader_t		*sphdr = (nss_pheader_t *)s->buffer;
	nss_pheader_t		*dphdr = (nss_pheader_t *)d->buffer;
	int			slen, new_pbufsiz = 0;

	if (NSCD_GET_STATUS(sphdr) != NSS_SUCCESS) {

		/* no result, copy header only (status, errno, etc) */
		slen = sphdr->data_off;
	} else {
		/*
		 * lookup result returned, data to copy is the packed
		 * header plus result (add 1 for the terminating NULL
		 * just in case)
		 */
		slen = sphdr->data_off + sphdr->data_len + 1;
	}

	/* allocate cache packed buffer */
	if (dphdr != NULL && d->bufsize <= slen && d->bufsize != 0) {
		/* old buffer too small, free it */
		free(dphdr);
		d->buffer = NULL;
		d->bufsize = 0;
		dphdr = NULL;
	}
	if (dphdr == NULL) {
		/* get new buffer */
		dphdr = calloc(1, slen + 1);
		if (dphdr == NULL)
			return (NSS_ERROR);
		d->buffer = dphdr;
		d->bufsize = slen + 1;
		new_pbufsiz = slen + 1;
	}

	(void) memcpy(dphdr, sphdr, slen);
	if (new_pbufsiz != 0)
		dphdr->pbufsiz = new_pbufsiz;

	return (NSS_SUCCESS);
}

char *cache_name[CACHE_CTX_COUNT] = {
	NSS_DBNAM_PASSWD,
	NSS_DBNAM_GROUP,
	NSS_DBNAM_HOSTS,
	NSS_DBNAM_IPNODES,
	NSS_DBNAM_EXECATTR,
	NSS_DBNAM_PROFATTR,
	NSS_DBNAM_USERATTR,
	NSS_DBNAM_ETHERS,
	NSS_DBNAM_RPC,
	NSS_DBNAM_PROTOCOLS,
	NSS_DBNAM_NETWORKS,
	NSS_DBNAM_BOOTPARAMS,
	NSS_DBNAM_AUDITUSER,
	NSS_DBNAM_AUTHATTR,
	NSS_DBNAM_SERVICES,
	NSS_DBNAM_NETMASKS,
	NSS_DBNAM_PRINTERS,
	NSS_DBNAM_PROJECT,
	NSS_DBNAM_TSOL_TP,
	NSS_DBNAM_TSOL_RH
};

typedef void (*cache_init_ctx_t)(nsc_ctx_t *);
static cache_init_ctx_t cache_init_ctx[CACHE_CTX_COUNT] = {
	passwd_init_ctx,
	group_init_ctx,
	host_init_ctx,
	ipnode_init_ctx,
	exec_init_ctx,
	prof_init_ctx,
	user_init_ctx,
	ether_init_ctx,
	rpc_init_ctx,
	proto_init_ctx,
	net_init_ctx,
	bootp_init_ctx,
	auuser_init_ctx,
	auth_init_ctx,
	serv_init_ctx,
	netmask_init_ctx,
	printer_init_ctx,
	project_init_ctx,
	tnrhtp_init_ctx,
	tnrhdb_init_ctx
};

nsc_ctx_t *cache_ctx_p[CACHE_CTX_COUNT] = { 0 };
static nscd_cfg_stat_cache_t	null_stats = { 0 };
static nscd_cfg_global_cache_t	global_cfg;

/*
 * Given database name 'dbname' find cache index
 */
int
get_cache_idx(char *dbname) {
	int	i;
	char	*nsc_name;

	for (i = 0; i < CACHE_CTX_COUNT; i++) {
		nsc_name = cache_name[i];
		if (strcmp(nsc_name, dbname) == 0)
			return (i);
	}
	return (-1);
}

/*
 * Given database name 'dbname' retrieve cache context,
 * if not created yet, allocate and initialize it.
 */
static nscd_rc_t
get_cache_ctx(char *dbname, nsc_ctx_t **ctx) {
	int	i;

	*ctx = NULL;

	i = get_cache_idx(dbname);
	if (i == -1)
		return (NSCD_INVALID_ARGUMENT);
	if ((*ctx = cache_ctx_p[i]) == NULL) {
		*ctx = init_cache_ctx(i);
		if (*ctx == NULL)
			return (NSCD_NO_MEMORY);
	}

	return (NSCD_SUCCESS);
}

/*
 * Generate a log string to identify backend operation in debug logs
 */
static void
nsc_db_str_key_getlogstr(char *name, char *whoami, size_t len,
		nss_XbyY_args_t *argp) {
	(void) snprintf(whoami, len, "%s [key=%s]", name, argp->key.name);
}


static void
nsc_db_int_key_getlogstr(char *name, char *whoami, size_t len,
		nss_XbyY_args_t *argp) {
	(void) snprintf(whoami, len, "%s [key=%d]", name, argp->key.number);
}

/*ARGSUSED*/
static void
nsc_db_any_key_getlogstr(char *name, char *whoami, size_t len,
		nss_XbyY_args_t *argp) {
	(void) snprintf(whoami, len, "%s", name);
}


/*
 * Returns cache based on dbop
 */
static nsc_db_t *
nsc_get_db(nsc_ctx_t *ctx, int dbop) {
	int	i;

	for (i = 0; i < ctx->db_count; i++) {
		if (ctx->nsc_db[i] && dbop == ctx->nsc_db[i]->dbop)
			return (ctx->nsc_db[i]);
	}
	return (NULL);
}


/*
 * integer compare routine for _NSC_DB_INT_KEY
 */
static int
nsc_db_int_key_compar(const void *n1, const void *n2) {
	nsc_entry_t	*e1, *e2;

	e1 = (nsc_entry_t *)n1;
	e2 = (nsc_entry_t *)n2;
	return (_NSC_INT_KEY_CMP(e1->key.number, e2->key.number));
}


/*
 * case sensitive name compare routine for _NSC_DB_CES_KEY
 */
static int
nsc_db_ces_key_compar(const void *n1, const void *n2) {
	nsc_entry_t	*e1, *e2;
	int		res, l1, l2;

	e1 = (nsc_entry_t *)n1;
	e2 = (nsc_entry_t *)n2;
	l1 = strlen(e1->key.name);
	l2 = strlen(e2->key.name);
	res = strncmp(e1->key.name, e2->key.name, (l1 > l2)?l1:l2);
	return (_NSC_INT_KEY_CMP(res, 0));
}


/*
 * case insensitive name compare routine _NSC_DB_CIS_KEY
 */
static int
nsc_db_cis_key_compar(const void *n1, const void *n2) {
	nsc_entry_t	*e1, *e2;
	int		res, l1, l2;

	e1 = (nsc_entry_t *)n1;
	e2 = (nsc_entry_t *)n2;
	l1 = strlen(e1->key.name);
	l2 = strlen(e2->key.name);
	res = strncasecmp(e1->key.name, e2->key.name, (l1 > l2)?l1:l2);
	return (_NSC_INT_KEY_CMP(res, 0));
}

/*
 * macro used to generate elf hashes for strings
 */
#define	_NSC_ELF_STR_GETHASH(func, str, htsize, hval) \
	hval = 0; \
	while (*str) { \
		uint_t  g; \
		hval = (hval << 4) + func(*str++); \
		if ((g = (hval & 0xf0000000)) != 0) \
			hval ^= g >> 24; \
		hval &= ~g; \
	} \
	hval %= htsize;


/*
 * cis hash function
 */
uint_t
cis_gethash(const char *key, int htsize) {
	uint_t	hval;
	if (key == NULL)
		return (0);
	_NSC_ELF_STR_GETHASH(tolower, key, htsize, hval);
	return (hval);
}


/*
 * ces hash function
 */
uint_t
ces_gethash(const char *key, int htsize) {
	uint_t	hval;
	if (key == NULL)
		return (0);
	_NSC_ELF_STR_GETHASH(, key, htsize, hval);
	return (hval);
}


/*
 * one-at-a-time hash function
 */
uint_t
db_gethash(const void *key, int len, int htsize) {
	uint_t	hval, i;
	const char *str = key;

	if (str == NULL)
		return (0);

	for (hval = 0, i = 0; i < len; i++) {
		hval += str[i];
		hval += (hval << 10);
		hval ^= (hval >> 6);
	}
	hval += (hval << 3);
	hval ^= (hval >> 11);
	hval += (hval << 15);
	return (hval % htsize);
}


/*
 * case insensitive name gethash routine _NSC_DB_CIS_KEY
 */
static uint_t
nsc_db_cis_key_gethash(nss_XbyY_key_t *key, int htsize) {
	return (cis_gethash(key->name, htsize));
}


/*
 * case sensitive name gethash routine _NSC_DB_CES_KEY
 */
static uint_t
nsc_db_ces_key_gethash(nss_XbyY_key_t *key, int htsize) {
	return (ces_gethash(key->name, htsize));
}


/*
 * integer gethash routine _NSC_DB_INT_KEY
 */
static uint_t
nsc_db_int_key_gethash(nss_XbyY_key_t *key, int htsize) {
	return (db_gethash(&key->number, sizeof (key->number), htsize));
}


/*
 * Find entry in the hash table
 * if cmp == nscd_true)
 *	return entry only if the keys match
 * else
 *	return entry in the hash location without checking the keys
 *
 */
static nsc_entry_t *
hash_find(nsc_db_t *nscdb, nsc_entry_t *entry, uint_t *hash,
			nscd_bool_t cmp) {

	nsc_entry_t	*hashentry;

	if (nscdb->gethash)
		*hash = nscdb->gethash(&entry->key, nscdb->htsize);
	else
		return (NULL);

	hashentry = nscdb->htable[*hash];
	if (cmp == nscd_false || hashentry == NULL)
		return (hashentry);
	if (nscdb->compar) {
		if (nscdb->compar(entry, hashentry) == 0)
			return (hashentry);
	}
	return (NULL);
}


#define	HASH_REMOVE(nscdb, entry, hash, cmp) \
	if (nscdb->htable) { \
		if (entry == hash_find(nscdb, entry, &hash, cmp)) \
			nscdb->htable[hash] = NULL; \
	}


#define	HASH_INSERT(nscdb, entry, hash, cmp) \
	if (nscdb->htable) { \
		(void) hash_find(nscdb, entry, &hash, cmp); \
		nscdb->htable[hash] = entry; \
	}


#ifdef	NSCD_DEBUG
static void
print_entry(nsc_db_t *nscdb, time_t now, nsc_entry_t *entry) {
	nss_XbyY_args_t args;
	char		whoami[512];

	switch (entry->stats.status) {
	case ST_NEW_ENTRY:
		(void) fprintf(stdout, gettext("\t status: new entry\n"));
		return;
	case ST_UPDATE_PENDING:
		(void) fprintf(stdout, gettext("\t status: update pending\n"));
		return;
	case ST_LOOKUP_PENDING:
		(void) fprintf(stdout, gettext("\t status: lookup pending\n"));
		return;
	case ST_DISCARD:
		(void) fprintf(stdout, gettext("\t status: discarded entry\n"));
		return;
	default:
		if (entry->stats.timestamp < now)
			(void) fprintf(stdout,
			gettext("\t status: expired (%d seconds ago)\n"),
			now - entry->stats.timestamp);
		else
			(void) fprintf(stdout,
			gettext("\t status: valid (expiry in %d seconds)\n"),
			entry->stats.timestamp - now);
		break;
	}
	(void) fprintf(stdout, gettext("\t hits: %u\n"), entry->stats.hits);
	args.key = entry->key;
	(void) nscdb->getlogstr(nscdb->name, whoami, sizeof (whoami), &args);
	(void) fprintf(stdout, "\t %s\n", whoami);
}
#endif	/* NSCD_DEBUG */

static void
print_stats(nscd_cfg_stat_cache_t *statsp) {

	(void) fprintf(stdout, gettext("\n\t STATISTICS:\n"));
	(void) fprintf(stdout, gettext("\t positive hits: %lu\n"),
			statsp->pos_hits);
	(void) fprintf(stdout, gettext("\t negative hits: %lu\n"),
			statsp->neg_hits);
	(void) fprintf(stdout, gettext("\t positive misses: %lu\n"),
			statsp->pos_misses);
	(void) fprintf(stdout, gettext("\t negative misses: %lu\n"),
			statsp->neg_misses);
	(void) fprintf(stdout, gettext("\t total entries: %lu\n"),
			statsp->entries);
	(void) fprintf(stdout, gettext("\t queries queued: %lu\n"),
			statsp->wait_count);
	(void) fprintf(stdout, gettext("\t queries dropped: %lu\n"),
			statsp->drop_count);
	(void) fprintf(stdout, gettext("\t cache invalidations: %lu\n"),
			statsp->invalidate_count);

	_NSC_GET_HITRATE(statsp);
	(void) fprintf(stdout, gettext("\t cache hit rate: %10.1f\n"),
			statsp->hitrate);
}


static void
print_cfg(nscd_cfg_cache_t *cfgp) {
	(void) fprintf(stdout, gettext("\n\t CONFIG:\n"));
	(void) fprintf(stdout, gettext("\t enabled: %s\n"),
			yes_no(cfgp->enable));
	(void) fprintf(stdout, gettext("\t per user cache: %s\n"),
			yes_no(cfgp->per_user));
	(void) fprintf(stdout, gettext("\t avoid name service: %s\n"),
			yes_no(cfgp->avoid_ns));
	(void) fprintf(stdout, gettext("\t check file: %s\n"),
			yes_no(cfgp->check_files));
	(void) fprintf(stdout, gettext("\t check file interval: %d\n"),
			cfgp->check_interval);
	(void) fprintf(stdout, gettext("\t positive ttl: %d\n"),
			cfgp->pos_ttl);
	(void) fprintf(stdout, gettext("\t negative ttl: %d\n"),
			cfgp->neg_ttl);
	(void) fprintf(stdout, gettext("\t keep hot count: %d\n"),
			cfgp->keephot);
	(void) fprintf(stdout, gettext("\t hint size: %d\n"),
			cfgp->hint_size);
	(void) fprintf(stdout, gettext("\t max entries: %lu%s"),
			cfgp->maxentries,
			cfgp->maxentries?"\n":" (unlimited)\n");
}


#ifdef	NSCD_DEBUG
static void
hash_dump(nsc_db_t *nscdb, time_t now) {
	nsc_entry_t	*entry;
	int		i;

	(void) fprintf(stdout, gettext("\n\nHASH TABLE:\n"));
	for (i = 0; i < nscdb->htsize; i++) {
		if ((entry = nscdb->htable[i]) != NULL) {
			(void) fprintf(stdout, "hash[%d]:\n", i);
			print_entry(nscdb, now, entry);
		}
	}
}
#endif	/* NSCD_DEBUG */


#ifdef	NSCD_DEBUG
static void
avl_dump(nsc_db_t *nscdb, time_t now) {
	nsc_entry_t	*entry;
	int		i;

	(void) fprintf(stdout, gettext("\n\nAVL TREE:\n"));
	for (entry = avl_first(&nscdb->tree), i = 0; entry != NULL;
			entry = avl_walk(&nscdb->tree, entry, AVL_AFTER)) {
		(void) fprintf(stdout, "avl node[%d]:\n", i++);
		print_entry(nscdb, now, entry);
	}
}
#endif	/* NSCD_DEBUG */


#ifdef	NSCD_DEBUG
static void
queue_dump(nsc_db_t *nscdb, time_t now) {
	nsc_entry_t	*entry;
	int		i;

	(void) fprintf(stdout,
		gettext("\n\nCACHE [name=%s, nodes=%lu]:\n"),
		nscdb->name, avl_numnodes(&nscdb->tree));

	(void) fprintf(stdout,
		gettext("Starting with the most recently accessed:\n"));

	for (entry = nscdb->qtail, i = 0; entry; entry = entry->qnext) {
		(void) fprintf(stdout, "entry[%d]:\n", i++);
		print_entry(nscdb, now, entry);
	}
}
#endif	/* NSCD_DEBUG */

static void
queue_remove(nsc_db_t *nscdb, nsc_entry_t *entry) {

	if (nscdb->qtail == entry)
		nscdb->qtail = entry->qnext;
	else
		entry->qprev->qnext = entry->qnext;

	if (nscdb->qhead == entry)
		nscdb->qhead = entry->qprev;
	else
		entry->qnext->qprev = entry->qprev;

	if (nscdb->reap_node == entry)
		nscdb->reap_node = entry->qnext;
	entry->qnext = entry->qprev = NULL;
}


static void
queue_adjust(nsc_db_t *nscdb, nsc_entry_t *entry) {

#ifdef NSCD_DEBUG
	assert(nscdb->qtail || entry->qnext == NULL &&
			entry->qprev == NULL);

	assert(nscdb->qtail && nscdb->qhead ||
		nscdb->qtail == NULL && nscdb->qhead == NULL);

	assert(entry->qprev || entry->qnext == NULL ||
		nscdb->qtail == entry);
#endif /* NSCD_DEBUG */

	/* already in the desired position */
	if (nscdb->qtail == entry)
		return;

	/* new queue */
	if (nscdb->qtail == NULL) {
		nscdb->qhead = nscdb->qtail = entry;
		return;
	}

	/* new entry (prev == NULL AND tail != entry) */
	if (entry->qprev == NULL) {
		nscdb->qtail->qprev = entry;
		entry->qnext = nscdb->qtail;
		nscdb->qtail = entry;
		return;
	}

	/* existing entry */
	if (nscdb->reap_node == entry)
		nscdb->reap_node = entry->qnext;
	if (nscdb->qhead == entry)
		nscdb->qhead = entry->qprev;
	else
		entry->qnext->qprev = entry->qprev;
	entry->qprev->qnext = entry->qnext;
	entry->qprev = NULL;
	entry->qnext = nscdb->qtail;
	nscdb->qtail->qprev = entry;
	nscdb->qtail = entry;
}


/*
 * Init cache
 */
nscd_rc_t
init_cache(int debug_level) {
	int cflags;

	cflags = (debug_level > 0)?0:UMC_NODEBUG;
	nsc_entry_cache = umem_cache_create("nsc_entry_cache",
				sizeof (nsc_entry_t), 0, NULL, NULL, NULL,
				NULL, NULL, cflags);
	if (nsc_entry_cache == NULL)
		return (NSCD_NO_MEMORY);
	return (NSCD_SUCCESS);
}


/*
 * Create cache
 */
nsc_db_t *
make_cache(enum db_type dbtype, int dbop, char *name,
		int (*compar) (const void *, const void *),
		void (*getlogstr)(char *, char *, size_t, nss_XbyY_args_t *),
		uint_t (*gethash)(nss_XbyY_key_t *, int),
		enum hash_type httype, int htsize) {

	nsc_db_t	*nscdb;
	char		*me = "make_cache";

	nscdb = (nsc_db_t *)malloc(sizeof (*nscdb));
	if (nscdb == NULL) {
		_NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_ERROR)
		(me, "%s: memory allocation failure\n", name);
		goto out;
	}
	(void) memset(nscdb, 0, sizeof (*nscdb));

	nscdb->dbop = dbop;
	nscdb->name = name;
	nscdb->db_type = dbtype;

	/* Assign compare routine */
	if (compar == NULL) {
		if (_NSC_DB_CES_KEY(nscdb))
			nscdb->compar = nsc_db_ces_key_compar;
		else if (_NSC_DB_CIS_KEY(nscdb))
			nscdb->compar = nsc_db_cis_key_compar;
		else if (_NSC_DB_INT_KEY(nscdb))
			nscdb->compar = nsc_db_int_key_compar;
		else
			assert(0);
	} else {
		nscdb->compar = compar;
	}

	/* The cache is an AVL tree */
	avl_create(&nscdb->tree, nscdb->compar, sizeof (nsc_entry_t),
			offsetof(nsc_entry_t, avl_link));

	/* Assign log routine */
	if (getlogstr == NULL) {
		if (_NSC_DB_STR_KEY(nscdb))
			nscdb->getlogstr = nsc_db_str_key_getlogstr;
		else if (_NSC_DB_INT_KEY(nscdb))
			nscdb->getlogstr = nsc_db_int_key_getlogstr;
		else
			nscdb->getlogstr = nsc_db_any_key_getlogstr;
	} else {
		nscdb->getlogstr = getlogstr;
	}

	/* The AVL tree based cache uses a hash table for quick access */
	if (htsize != 0) {
		/* Determine hash table size based on type */
		nscdb->hash_type = httype;
		if (htsize < 0) {
			switch (httype) {
			case nsc_ht_power2:
				htsize = _NSC_INIT_HTSIZE_POWER2;
				break;
			case nsc_ht_prime:
			case nsc_ht_default:
			default:
				htsize = _NSC_INIT_HTSIZE_PRIME;
			}
		}
		nscdb->htsize = htsize;

		/* Create the hash table */
		nscdb->htable = calloc(htsize, sizeof (*(nscdb->htable)));
		if (nscdb->htable == NULL) {
			_NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_ERROR)
			(me, "%s: memory allocation failure\n", name);
			goto out;
		}

		/* Assign gethash routine */
		if (gethash == NULL) {
			if (_NSC_DB_CES_KEY(nscdb))
				nscdb->gethash = nsc_db_ces_key_gethash;
			else if (_NSC_DB_CIS_KEY(nscdb))
				nscdb->gethash = nsc_db_cis_key_gethash;
			else if (_NSC_DB_INT_KEY(nscdb))
				nscdb->gethash = nsc_db_int_key_gethash;
			else
				assert(0);
		} else {
			nscdb->gethash = gethash;
		}
	}

	(void) mutex_init(&nscdb->db_mutex, USYNC_THREAD, NULL);
	return (nscdb);

out:
	if (nscdb->htable)
		free(nscdb->htable);
	if (nscdb)
		free(nscdb);
	return (NULL);
}


/*
 * verify
 */
/* ARGSUSED */
nscd_rc_t
_nscd_cfg_cache_verify(
	void				*data,
	struct nscd_cfg_param_desc	*pdesc,
	nscd_cfg_id_t			*nswdb,
	nscd_cfg_flag_t			dflag,
	nscd_cfg_error_t		**errorp,
	void				**cookie)
{

	return (NSCD_SUCCESS);
}

/*
 * notify
 */
/* ARGSUSED */
nscd_rc_t
_nscd_cfg_cache_notify(
	void				*data,
	struct nscd_cfg_param_desc	*pdesc,
	nscd_cfg_id_t			*nswdb,
	nscd_cfg_flag_t			dflag,
	nscd_cfg_error_t		**errorp,
	void				**cookie)
{
	nsc_ctx_t	*ctx;
	void		*dp;
	int		i;

	/* group data */
	if (_nscd_cfg_flag_is_set(dflag, NSCD_CFG_DFLAG_GROUP)) {
		if (_nscd_cfg_flag_is_set(pdesc->pflag,
		    NSCD_CFG_PFLAG_GLOBAL)) {
			/* global config */
			global_cfg = *(nscd_cfg_global_cache_t *)data;
		} else if (_nscd_cfg_flag_is_set(dflag,
		    NSCD_CFG_DFLAG_SET_ALL_DB)) {
			/* non-global config for all dbs */
			for (i = 0; i < CACHE_CTX_COUNT; i++) {
				ctx = cache_ctx_p[i];
				if (ctx == NULL)
					return (NSCD_CTX_NOT_FOUND);
				(void) rw_wrlock(&ctx->cfg_rwlp);
				ctx->cfg = *(nscd_cfg_cache_t *)data;
				ctx->cfg_mtime = time(NULL);
				(void) rw_unlock(&ctx->cfg_rwlp);
			}
		} else {
			/* non-global config for a specific db */

			/* ignore non-caching databases */
			if (get_cache_ctx(nswdb->name, &ctx) != NSCD_SUCCESS)
				return (NSCD_SUCCESS);
			(void) rw_wrlock(&ctx->cfg_rwlp);
			ctx->cfg = *(nscd_cfg_cache_t *)data;
			ctx->cfg_mtime = time(NULL);
			(void) rw_unlock(&ctx->cfg_rwlp);
		}
		return (NSCD_SUCCESS);
	}

	/* individual data */
	if (_nscd_cfg_flag_is_set(pdesc->pflag, NSCD_CFG_PFLAG_GLOBAL)) {
		/* global config */
		dp = (char *)&global_cfg + pdesc->p_offset;
		(void) memcpy(dp, data, pdesc->p_size);
	} else if (_nscd_cfg_flag_is_set(dflag,
	    NSCD_CFG_DFLAG_SET_ALL_DB)) {
		/* non-global config for all dbs */
		for (i = 0; i < CACHE_CTX_COUNT; i++) {
			ctx = cache_ctx_p[i];
			if (ctx == NULL)
				return (NSCD_CTX_NOT_FOUND);
			dp = (char *)&ctx->cfg + pdesc->p_offset;
			(void) rw_wrlock(&ctx->cfg_rwlp);
			(void) memcpy(dp, data, pdesc->p_size);
			ctx->cfg_mtime = time(NULL);
			(void) rw_unlock(&ctx->cfg_rwlp);
		}
	} else {
		/* non-global config for a specific db */

		/* ignore non-caching databases */
		if (get_cache_ctx(nswdb->name, &ctx) != NSCD_SUCCESS)
			return (NSCD_SUCCESS);
		dp = (char *)&ctx->cfg + pdesc->p_offset;
		(void) rw_wrlock(&ctx->cfg_rwlp);
		(void) memcpy(dp, data, pdesc->p_size);
		ctx->cfg_mtime = time(NULL);
		(void) rw_unlock(&ctx->cfg_rwlp);
	}
	return (NSCD_SUCCESS);
}


/*
 * get stat
 */
/* ARGSUSED */
nscd_rc_t
_nscd_cfg_cache_get_stat(
	void				**stat,
	struct nscd_cfg_stat_desc	*sdesc,
	nscd_cfg_id_t			*nswdb,
	nscd_cfg_flag_t			*dflag,
	void				(**free_stat)(void *stat),
	nscd_cfg_error_t		**errorp)
{
	nscd_cfg_stat_cache_t	*statsp, stats;
	nsc_ctx_t		*ctx;
	int			i;
	nscd_rc_t		rc;

	statsp = calloc(1, sizeof (*statsp));
	if (statsp == NULL)
		return (NSCD_NO_MEMORY);

	if (_nscd_cfg_flag_is_set(sdesc->sflag, NSCD_CFG_SFLAG_GLOBAL)) {
		for (i = 0; i < CACHE_CTX_COUNT; i++) {
			if (cache_ctx_p[i] == NULL)
				stats = null_stats;
			else {
				(void) mutex_lock(&cache_ctx_p[i]->stats_mutex);
				stats = cache_ctx_p[i]->stats;
				(void) mutex_unlock(
				    &cache_ctx_p[i]->stats_mutex);
			}
			statsp->pos_hits += stats.pos_hits;
			statsp->neg_hits += stats.neg_hits;
			statsp->pos_misses += stats.pos_misses;
			statsp->neg_misses += stats.neg_misses;
			statsp->entries += stats.entries;
			statsp->drop_count += stats.drop_count;
			statsp->wait_count += stats.wait_count;
			statsp->invalidate_count +=
			    stats.invalidate_count;
		}
	} else {
		if ((rc = get_cache_ctx(nswdb->name, &ctx)) != NSCD_SUCCESS) {
			free(statsp);
			return (rc);
		}
		(void) mutex_lock(&ctx->stats_mutex);
		*statsp = ctx->stats;
		(void) mutex_unlock(&ctx->stats_mutex);
	}

	_NSC_GET_HITRATE(statsp);
	*stat = statsp;
	return (NSCD_SUCCESS);
}

/*
 * This function should only be called when nscd is
 * not a daemon.
 */
void
nsc_info(nsc_ctx_t *ctx, char *dbname, nscd_cfg_cache_t cfg[],
	nscd_cfg_stat_cache_t stats[])
{
	int		i;
	char		*me = "nsc_info";
	nsc_ctx_t	*ctx1;
	nsc_ctx_t	ctx2;
	nscd_rc_t	rc;

	if (ctx) {
		ctx_info(ctx);
		return;
	}

	if (dbname) {
		rc = get_cache_ctx(dbname, &ctx1);
		if (rc == NSCD_INVALID_ARGUMENT) {
			_NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_WARNING)
			(me, "%s: no cache context found\n", dbname);
			return;
		} else if (rc == NSCD_NO_MEMORY) {
			_NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_WARNING)
	(me, "%s: unable to create cache context - no memory\n",
	    dbname);
			return;
		}
		ctx_info(ctx1);
		return;
	}

	if (cfg == NULL || stats == NULL)
		return;

	for (i = 0; i < CACHE_CTX_COUNT; i++) {

		ctx2.dbname = cache_name[i];
		ctx2.cfg = cfg[i];
		ctx2.stats = stats[i];
		ctx_info_nolock(&ctx2);
	}
}

static void
ctx_info_nolock(nsc_ctx_t *ctx) {
	nscd_cfg_cache_t	cfg;
	nscd_cfg_stat_cache_t	stats;

	cfg = ctx->cfg;
	(void) fprintf(stdout, gettext("\n\nCACHE: %s\n"), ctx->dbname);
	(void) print_cfg(&cfg);

	if (cfg.enable == nscd_false)
		return;

	stats = ctx->stats;
	(void) print_stats(&stats);
}

static void
ctx_info(nsc_ctx_t *ctx) {
	nscd_cfg_cache_t	cfg;
	nscd_cfg_stat_cache_t	stats;

	(void) rw_rdlock(&ctx->cfg_rwlp);
	cfg = ctx->cfg;
	(void) rw_unlock(&ctx->cfg_rwlp);
	(void) fprintf(stdout, gettext("\n\nCACHE: %s\n"), ctx->dbname);
	(void) print_cfg(&cfg);

	if (cfg.enable == nscd_false)
		return;

	(void) mutex_lock(&ctx->stats_mutex);
	stats = ctx->stats;
	(void) mutex_unlock(&ctx->stats_mutex);
	(void) print_stats(&stats);
}

#ifdef	NSCD_DEBUG
/*
 * This function should only be called when nscd is
 * not a daemon.
 */
int
nsc_dump(char *dbname, int dbop) {
	nsc_ctx_t	*ctx;
	nsc_db_t	*nscdb;
	nscd_bool_t	enabled;
	time_t		now;
	char		*me = "nsc_dump";
	int		i;

	if ((i = get_cache_idx(dbname)) == -1) {
		(void) fprintf(stdout, gettext("invalid cache name\n"));

		_NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_WARNING)
		(me, "%s: invalid cache name\n", dbname);
		return (NSCD_CACHE_INVALID_CACHE_NAME);
	}

	if ((ctx = cache_ctx_p[i]) == NULL)  {
		(void) fprintf(stdout, gettext("no cache context\n"));

		_NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_WARNING)
		(me, "%s: no cache context\n", dbname);
		return (NSCD_CACHE_NO_CACHE_CTX);
	}

	now = time(NULL);
	(void) rw_rdlock(&ctx->cfg_rwlp);
	enabled = ctx->cfg.enable;
	(void) rw_unlock(&ctx->cfg_rwlp);

	if (enabled == nscd_false)
		return (NSCD_CACHE_DISABLED);

	nscdb = nsc_get_db(ctx, dbop);
	if (nscdb == NULL) {
		_NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_WARNING)
		(me, "%s:%d: no cache found\n", dbname, dbop);
		return (NSCD_CACHE_NO_CACHE_FOUND);
	}

	(void) mutex_lock(&nscdb->db_mutex);
	(void) queue_dump(nscdb, now);
	(void) hash_dump(nscdb, now);
	(void) avl_dump(nscdb, now);
	(void) mutex_unlock(&nscdb->db_mutex);
	return (NSCD_SUCCESS);
}
#endif	/* NSCD_DEBUG */

/*
 * These macros are for exclusive use of nsc_lookup
 */
#define	NSC_LOOKUP_RETURN(retcode, loglevel, fmt) \
	(void) mutex_unlock(&nscdb->db_mutex); \
	_NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_##loglevel) \
		(me, fmt, whoami); \
	return (retcode);

#define	NSC_LOOKUP_NO_CACHE(str) \
	_NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_DEBUG) \
		(me, "%s: name service lookup (bypassing cache\n", \
		str); \
	nss_psearch(largs->buffer, largs->bufsize); \
	status = NSCD_GET_STATUS(largs->buffer); \
	_NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_DEBUG) \
		(me, "%s: name service lookup status = %d\n", \
		str, status); \
	if (status == NSS_SUCCESS) { \
		return (SUCCESS); \
	} else if (status == NSS_NOTFOUND) \
		return (NOTFOUND); \
	else \
		return (SERVERERROR);

/*
 * This function starts the revalidation and reaper threads
 * for a cache
 */
static void
start_threads(nsc_ctx_t *ctx) {

	int	errnum;
	char	*me = "start_threads";

	/*
	 *  kick off the revalidate thread (if necessary)
	 */
	if (ctx->revalidate_on != nscd_true) {
		if (thr_create(NULL, NULL, (void *(*)(void *))revalidate,
			ctx, 0, NULL) != 0) {
			errnum = errno;
			_NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_ERROR)
		(me, "thr_create (revalidate thread for %s): %s\n",
			ctx->dbname, strerror(errnum));
			exit(1);
		}
		ctx->revalidate_on = nscd_true;
	}

	/*
	 *  kick off the reaper thread (if necessary)
	 */
	if (ctx->reaper_on != nscd_true) {
		if (thr_create(NULL, NULL, (void *(*)(void *))reaper,
			ctx, 0, NULL) != 0) {
			errnum = errno;
			_NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_ERROR)
		(me, "thr_create (reaper thread for %s): %s\n",
			ctx->dbname, strerror(errnum));
			exit(1);
		}
		ctx->reaper_on = nscd_true;
	}
}

/*
 * Examine the packed buffer, see if the front-end parameters
 * indicate that the caller specified nsswitch config should be
 * used for the lookup. Return 1 if yes, otherwise 0.
 */
static int
nsw_config_in_phdr(void *buf)
{
	nss_pheader_t		*pbuf = (nss_pheader_t *)buf;
	nssuint_t		off;
	nss_dbd_t		*pdbd;
	char			*me = "nsw_config_in_phdr";

	off = pbuf->dbd_off;
	if (off == 0)
		return (0);
	pdbd = (nss_dbd_t *)((void *)((char *)pbuf + off));
	if (pdbd->o_default_config == 0)
		return (0);

	if ((enum nss_dbp_flags)pdbd->flags & NSS_USE_DEFAULT_CONFIG) {
		_NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_DEBUG)
		(me, "use caller specified nsswitch config\n");
		return (1);
	} else
		return (0);
}

static nss_status_t
copy_result(void *rbuf, void *cbuf)
{
	nss_pheader_t	*rphdr = (nss_pheader_t *)rbuf;
	nss_pheader_t	*cphdr = (nss_pheader_t *)cbuf;
	char		*me = "copy_result";

	/* return NSS_ERROR if not enough room to copy result */
	if (cphdr->data_len + 1 > rphdr->data_len) {
		NSCD_SET_STATUS(rphdr, NSS_ERROR, ERANGE);
		return (NSS_ERROR);
	} else {
		char	*dst;

		if (cphdr->data_len == 0)
			return (NSS_SUCCESS);

		dst = (char *)rphdr + rphdr->data_off;
		(void) memcpy(dst, (char *)cphdr + cphdr->data_off,
		    cphdr->data_len);
		rphdr->data_len = cphdr->data_len;
		/* some frontend code expects a terminating NULL char */
		*(dst + rphdr->data_len) = '\0';

		_NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_DEBUG)
		(me, "cache data (len = %lld): >>%s<<\n",
		    cphdr->data_len, (char *)cphdr + cphdr->data_off);

		return (NSS_SUCCESS);
	}
}

static int
get_dns_ttl(void *pbuf, char *dbname)
{
	nss_pheader_t	*phdr = (nss_pheader_t *)pbuf;
	int		ttl;
	char		*me = "get_dns_ttl";

	/* if returned, dns ttl is stored in the extended data area */
	if (phdr->ext_off == 0)
		return (-1);

	if (strcmp(dbname, NSS_DBNAM_HOSTS) != 0 &&
	    strcmp(dbname, NSS_DBNAM_IPNODES) != 0)
		return (-1);

	ttl = *(nssuint_t *)((void *)((char *)pbuf + phdr->ext_off));

	_NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_DEBUG)
	(me, "dns ttl is %d seconds\n", ttl);

	return (ttl);
}

static int
check_config(nsc_lookup_args_t *largs, nscd_cfg_cache_t *cfgp,
	char *whoami, int flag)
{
	nsc_db_t	*nscdb;
	nsc_ctx_t	*ctx;
	nss_status_t	status;
	char		*me = "check_config";

	ctx = largs->ctx;
	nscdb = largs->nscdb;

	/* see if the cached config needs update */
	if (nscdb->cfg_mtime != ctx->cfg_mtime) {
		(void) rw_rdlock(&ctx->cfg_rwlp);
		nscdb->cfg = ctx->cfg;
		nscdb->cfg_mtime = ctx->cfg_mtime;
		(void) rw_unlock(&ctx->cfg_rwlp);
		_NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_DEBUG)
		(me, "config for context %s, database %s updated\n",
		    ctx->dbname, nscdb->name);
	}
	*cfgp = nscdb->cfg;

	if (cfgp->enable == nscd_false) {
		_NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_DEBUG)
			(me, "%s: cache disabled\n", ctx->dbname);

		if (UPDATEBIT & flag)
			return (NOTFOUND);
		else {
			NSC_LOOKUP_NO_CACHE(whoami);
		}
	}

	/*
	 * if caller requests lookup using his
	 * own nsswitch config, bypass cache
	 */
	if (nsw_config_in_phdr(largs->buffer)) {
		NSC_LOOKUP_NO_CACHE(whoami);
	}

	/* no need of cache if we are dealing with 0 ttls */
	if (cfgp->pos_ttl <= 0 && cfgp->neg_ttl <= 0) {
		if (flag & UPDATEBIT)
			return (NOTFOUND);
		else if (cfgp->avoid_ns == nscd_true)
			return (SERVERERROR);
		NSC_LOOKUP_NO_CACHE(whoami);
	}

	return (CONTINUE);
}

/*
 * Invalidate cache if database file has been modified.
 * See check_files config param for details.
 */
static void
check_db_file(nsc_ctx_t *ctx, nscd_cfg_cache_t cfg,
	char *whoami, time_t now)
{
	struct stat	buf;
	nscd_bool_t	file_modified = nscd_false;
	char		*me = "check_db_file";

	if (cfg.check_interval != 0 &&
	    (now - ctx->file_chktime) < cfg.check_interval)
		return;

	ctx->file_chktime = now;
	if (stat(ctx->file_name, &buf) == 0) {
		if (ctx->file_mtime == 0) {
			(void) mutex_lock(&ctx->file_mutex);
			if (ctx->file_mtime == 0) {
				ctx->file_mtime = buf.st_mtime;
				ctx->file_size = buf.st_size;
				ctx->file_ino = buf.st_ino;
			}
			(void) mutex_unlock(&ctx->file_mutex);
		} else if (ctx->file_mtime < buf.st_mtime ||
		    ctx->file_size != buf.st_size ||
		    ctx->file_ino != buf.st_ino) {
			(void) mutex_lock(&ctx->file_mutex);
			if (ctx->file_mtime < buf.st_mtime ||
			    ctx->file_size != buf.st_size ||
			    ctx->file_ino != buf.st_ino) {
				file_modified = nscd_true;
				ctx->file_mtime = buf.st_mtime;
				ctx->file_size = buf.st_size;
				ctx->file_ino = buf.st_ino;
			}
			(void) mutex_unlock(&ctx->file_mutex);
		}
	}

	if (file_modified == nscd_true) {
		_NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_DEBUG)
		(me, "%s: file %s has been modified - invalidating cache\n",
		    whoami, ctx->file_name);
		ctx_invalidate(ctx);
	}
}

static int
lookup_int(nsc_lookup_args_t *largs, int flag) {

	nsc_ctx_t		*ctx;
	nsc_db_t		*nscdb;
	nscd_cfg_cache_t	cfg;
	nsc_entry_t		*this_entry;
	nsc_entry_stat_t	*this_stats;
	nsc_action_t		next_action;
	nss_status_t		status;
	nscd_bool_t		delete;
	nscd_rc_t		rc;
	char			*dbname;
	int			dbop, errnum;
	int			cfg_rc;
	nss_XbyY_args_t		args;
	char			whoami[128];
	time_t			now = time(NULL); /* current time */
	char			*me = "lookup_int";

	/* extract dbop, dbname, key and cred */
	status = nss_packed_getkey(largs->buffer, largs->bufsize, &dbname,
				&dbop, &args);
	if (status != NSS_SUCCESS) {
		_NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_ERROR)
			(me, "nss_packed_getkey failure (%d)\n", status);
		return (SERVERERROR);
	}

	/* get the cache context */
	if (largs->ctx == NULL) {
		if (get_cache_ctx(dbname, &largs->ctx) != NSCD_SUCCESS) {
			_NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_WARNING)
				(me, "%s: no cache context found\n", dbname);

			if (UPDATEBIT & flag)
				return (NOTFOUND);
			else {
				NSC_LOOKUP_NO_CACHE(dbname);
			}
		}
	}
	ctx = largs->ctx;

	if (largs->nscdb == NULL) {
		if ((largs->nscdb = nsc_get_db(ctx, dbop)) == NULL) {
			_NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_WARNING)
				(me, "%s:%d: no cache found\n",
				dbname, dbop);

			if (UPDATEBIT & flag)
				return (NOTFOUND);
			else {
				NSC_LOOKUP_NO_CACHE(dbname);
			}
		}
	}

	nscdb = largs->nscdb;

	_NSCD_LOG_IF(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_ALL) {
		(void) nscdb->getlogstr(nscdb->name, whoami,
			sizeof (whoami), &args);
	}

	if (UPDATEBIT & flag) {
		_NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_DEBUG)
			(me, "%s: refresh start\n", whoami);
	} else {
		_NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_DEBUG)
			(me, "%s: lookup start\n", whoami);
	}

	cfg_rc = check_config(largs, &cfg, whoami, flag);
	if (cfg_rc != CONTINUE)
		return (cfg_rc);

	/*
	 * Invalidate cache if file has been modified.
	 */
	if (cfg.check_files == nscd_true)
		check_db_file(ctx, cfg, whoami, now);

	(void) mutex_lock(&nscdb->db_mutex);

	/* Lookup the cache table */
	for (;;) {
		delete = nscd_false;
		rc = lookup_cache(largs, &cfg, &args, whoami, &this_entry);
		if (rc != NSCD_SUCCESS) {
			(void) mutex_unlock(&nscdb->db_mutex);

			/* Either no entry and avoid name service */
			if (rc == NSCD_DB_ENTRY_NOT_FOUND ||
					rc == NSCD_INVALID_ARGUMENT)
				return (NOTFOUND);

			/* OR memory error */
			return (SERVERERROR);
		}

		/* get the stats from the entry */
		this_stats = &this_entry->stats;

		/*
		 * What should we do next ?
		 */
		switch (this_stats->status) {
		case ST_NEW_ENTRY:
			delete = nscd_true;
			next_action = _NSC_NSLOOKUP;
			break;
		case ST_UPDATE_PENDING:
			if (flag & UPDATEBIT) {
				(void) mutex_unlock(&nscdb->db_mutex);
				return (NOTFOUND);
			} else if (this_stats->timestamp < now)
				next_action = _NSC_WAIT;
			else
				next_action = _NSC_USECACHED;
			break;
		case ST_LOOKUP_PENDING:
			if (flag & UPDATEBIT) {
				(void) mutex_unlock(&nscdb->db_mutex);
				return (NOTFOUND);
			}
			next_action = _NSC_WAIT;
			break;
		case ST_DISCARD:
			if (cfg.avoid_ns == nscd_true) {
				(void) mutex_unlock(&nscdb->db_mutex);
				return (NOTFOUND);
			}
			/* otherwise reuse the entry */
			(void) memset(this_stats, 0, sizeof (*this_stats));
			next_action = _NSC_NSLOOKUP;
			break;
		default:
			if (cfg.avoid_ns == nscd_true)
				next_action = _NSC_USECACHED;
			else if ((flag & UPDATEBIT) ||
					(this_stats->timestamp < now)) {
				_NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_DEBUG)
			(me, "%s: cached entry needs to be updated\n",
				whoami);
				next_action = _NSC_NSLOOKUP;
			} else
				next_action = _NSC_USECACHED;
			break;
		}

		if (next_action == _NSC_WAIT) {
			_NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_DEBUG)
			(me, "%s: need to wait\n", whoami);

			/* do we have clearance ? */
			if (_nscd_get_clearance(&ctx->throttle_sema) != 0) {
				/* nope. quit */
				(void) mutex_lock(&ctx->stats_mutex);
				ctx->stats.drop_count++;
				(void) mutex_unlock(&ctx->stats_mutex);
				_NSCD_LOG(NSCD_LOG_CACHE,
				    NSCD_LOG_LEVEL_DEBUG_6)
				(me, "%s: throttling load\n", whoami);
				NSC_LOOKUP_RETURN(NOSERVER, WARNING,
				"%s: no clearance to wait\n");
			}
			/* yes can wait */
			(void) nscd_wait(ctx, nscdb, this_entry);
			(void) _nscd_release_clearance(&ctx->throttle_sema);
			continue;
		}

		break;
	}


	if (!(UPDATEBIT & flag))
		this_stats->hits++;		/* update hit count */

	if (next_action == _NSC_NSLOOKUP) {

		_NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_DEBUG)
		(me, "%s: name service lookup required\n", whoami);

		if (_nscd_get_clearance(&ctx->throttle_sema) != 0) {
			if (delete == nscd_true)
				delete_entry(nscdb, ctx, this_entry);
			else
				this_stats->status = ST_DISCARD;
			(void) mutex_lock(&ctx->stats_mutex);
			ctx->stats.drop_count++;
			(void) mutex_unlock(&ctx->stats_mutex);
			NSC_LOOKUP_RETURN(NOSERVER, WARNING,
			"%s: no clearance for lookup\n");
		}

		/* block any threads accessing this entry */
		this_stats->status = (flag & UPDATEBIT)?
				ST_UPDATE_PENDING:ST_LOOKUP_PENDING;

		/* release lock and do name service lookup */
		(void) mutex_unlock(&nscdb->db_mutex);
		nss_psearch(largs->buffer, largs->bufsize);
		status = NSCD_GET_STATUS(largs->buffer);
		(void) mutex_lock(&nscdb->db_mutex);
		this_stats->status = 0;
		(void) _nscd_release_clearance(&ctx->throttle_sema);

		/* signal waiting threads */
		(void) nscd_signal(ctx, nscdb, this_entry);

		_NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_DEBUG)
		(me, "%s: name service lookup status = %d\n",
			whoami, status);

		if (status == NSS_SUCCESS) {
			int ttl;

			/*
			 * data found in name service
			 * update cache
			 */
			status = dup_packed_buffer(largs, this_entry);
			if (status != NSS_SUCCESS) {
				delete_entry(nscdb, ctx, this_entry);
				NSC_LOOKUP_RETURN(SERVERERROR, ERROR,
				"%s: failed to update cache\n");
			}

			/*
			 * store unpacked key in cache
			 */
			status = nss_packed_getkey(this_entry->buffer,
					this_entry->bufsize,
					&dbname, &dbop, &args);
			if (status != NSS_SUCCESS) {
				delete_entry(nscdb, ctx, this_entry);
				NSC_LOOKUP_RETURN(SERVERERROR, ERROR,
				"%s: failed to extract key\n");
			}
			this_entry->key = args.key; /* struct copy */

			/* update +ve miss count */
			if (!(UPDATEBIT & flag)) {
				(void) mutex_lock(&ctx->stats_mutex);
				ctx->stats.pos_misses++;
				(void) mutex_unlock(&ctx->stats_mutex);
			}

			/* update +ve ttl */
			ttl = get_dns_ttl(largs->buffer, dbname);
			/* honor the dns ttl less than postive ttl */
			if (ttl < 0 || ttl > cfg.pos_ttl)
				ttl = cfg.pos_ttl;
			this_stats->timestamp = time(NULL) + ttl;

			/*
			 * start the revalidation and reaper threads
			 * if not already started
			 */
			start_threads(ctx);

			NSC_LOOKUP_RETURN(SUCCESS, DEBUG,
			"%s: cache updated with positive entry\n");
		} else if (status == NSS_NOTFOUND) {
			/*
			 * data not found in name service
			 * update cache
			 */
			_NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_DEBUG_6)
			(me, "%s: name service lookup failed\n", whoami);

			if (NSCD_GET_ERRNO(largs->buffer) == ERANGE) {
				delete_entry(nscdb, ctx, this_entry);
				NSC_LOOKUP_RETURN(NOTFOUND, DEBUG,
		"%s: ERANGE, cache not updated with negative entry\n");
			}

			status = dup_packed_buffer(largs, this_entry);
			if (status != NSS_SUCCESS) {
				delete_entry(nscdb, ctx, this_entry);
				NSC_LOOKUP_RETURN(SERVERERROR, ERROR,
				"%s: failed to update cache\n");
			}

			/* store unpacked key in cache */
			status = nss_packed_getkey(this_entry->buffer,
					this_entry->bufsize,
					&dbname, &dbop, &args);
			if (status != NSS_SUCCESS) {
				delete_entry(nscdb, ctx, this_entry);
				NSC_LOOKUP_RETURN(SERVERERROR, ERROR,
				"%s: failed to extract key\n");
			}
			this_entry->key = args.key; /* struct copy */

			/* update -ve ttl */
			this_stats->timestamp = time(NULL) + cfg.neg_ttl;

			/* update -ve miss count */
			if (!(UPDATEBIT & flag)) {
				(void) mutex_lock(&ctx->stats_mutex);
				ctx->stats.neg_misses++;
				(void) mutex_unlock(&ctx->stats_mutex);
			}

			/*
			 * start the revalidation and reaper threads
			 * if not already started
			 */
			start_threads(ctx);

			NSC_LOOKUP_RETURN(NOTFOUND, DEBUG,
			"%s: cache updated with negative entry\n");
		} else {
			/*
			 * name service lookup failed
			 */
			_NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_DEBUG_6)
			(me, "%s: name service lookup failed\n", whoami);

			errnum = NSCD_GET_ERRNO(largs->buffer);
			if (delete == nscd_true)
				delete_entry(nscdb, ctx, this_entry);
			else
				this_stats->status = ST_DISCARD;

			(void) mutex_unlock(&nscdb->db_mutex);
			_NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_WARNING)
	(me, "%s: name service lookup failed (status=%d, errno=%d)\n",
				whoami, status, errnum);

			return (SERVERERROR);
		}
	} else if (next_action == _NSC_USECACHED) {
		/*
		 * found entry in cache
		 */
		if (UPDATEBIT & flag) {
			NSC_LOOKUP_RETURN(SUCCESS, DEBUG,
			"%s: no need to update\n");
		}

		if (NSCD_GET_STATUS((nss_pheader_t *)this_entry->buffer) ==
			NSS_SUCCESS) {
			/* positive hit */
			(void) mutex_lock(&ctx->stats_mutex);
			ctx->stats.pos_hits++;
			(void) mutex_unlock(&ctx->stats_mutex);

			/* update response buffer */
			if (copy_result(largs->buffer,
				this_entry->buffer) != NSS_SUCCESS) {
				NSC_LOOKUP_RETURN(SERVERERROR, ERROR,
				"%s: response buffer insufficient\n");
			}

			NSC_LOOKUP_RETURN(SUCCESS, DEBUG,
			"%s: positive entry in cache\n");
		} else {
			/* negative hit */
			(void) mutex_lock(&ctx->stats_mutex);
			ctx->stats.neg_hits++;
			(void) mutex_unlock(&ctx->stats_mutex);

			NSCD_SET_STATUS((nss_pheader_t *)largs->buffer,
				NSCD_GET_STATUS(this_entry->buffer),
				NSCD_GET_ERRNO(this_entry->buffer));
			NSCD_SET_HERRNO((nss_pheader_t *)largs->buffer,
				NSCD_GET_HERRNO(this_entry->buffer));

			NSC_LOOKUP_RETURN(NOTFOUND, DEBUG,
			"%s: negative entry in cache\n");
		}
	}

	NSC_LOOKUP_RETURN(SERVERERROR, ERROR,
	"%s: cache backend failure\n");
}

/*
 * NSCD cache backend lookup function
 */
/*ARGSUSED*/
void
nsc_lookup(nsc_lookup_args_t *largs, int flag) {

	nss_pheader_t	*phdr = (nss_pheader_t *)largs->buffer;
	int		rc;

	rc = lookup_int(largs, 0);

	if (NSCD_GET_STATUS(phdr) == NSS_TRYLOCAL)
		return;

	switch (rc) {

	case SUCCESS:
		NSCD_RETURN_STATUS(phdr, NSS_SUCCESS, 0);
		break;

	case NOTFOUND:
		NSCD_RETURN_STATUS(phdr, NSS_NOTFOUND, -1);
		break;

	case SERVERERROR:
		/*
		 * status and errno should have been set in the phdr,
		 * if not, set status to NSS_ERROR
		 */
		if (NSCD_STATUS_IS_OK(phdr)) {
			NSCD_SET_STATUS(phdr, NSS_ERROR, 0);
		}
		break;

	case NOSERVER:
		NSCD_RETURN_STATUS(phdr, NSS_TRYLOCAL, -1);
		break;
	}
}


static nsc_ctx_t *
init_cache_ctx(int i) {
	nsc_ctx_t	*ctx;

	ctx = calloc(1, sizeof (nsc_ctx_t));
	if (ctx == NULL)
		return (NULL);

	/* init locks and semaphores */
	(void) mutex_init(&ctx->file_mutex, USYNC_THREAD, NULL);
	(void) rwlock_init(&ctx->cfg_rwlp, USYNC_THREAD, NULL);
	(void) mutex_init(&ctx->stats_mutex, USYNC_THREAD, NULL);
	(void) _nscd_init_cache_sema(&ctx->throttle_sema, cache_name[i]);
	cache_init_ctx[i](ctx);
	cache_ctx_p[i] = ctx;

	return (ctx);
}


static void
revalidate(nsc_ctx_t *ctx)
{
	for (;;) {
		int 		i, slp, interval, count;

		(void) rw_rdlock(&ctx->cfg_rwlp);
		slp = ctx->cfg.pos_ttl;
		count = ctx->cfg.keephot;
		(void) rw_unlock(&ctx->cfg_rwlp);

		if (slp < 60)
			slp = 60;
		if (count != 0) {
			interval = (slp/2)/count;
			if (interval == 0)
				interval = 1;
			(void) sleep(slp*2/3);
			for (i = 0; i < ctx->db_count; i++) {
				getxy_keepalive(ctx, ctx->nsc_db[i],
				    count, interval);
			}
		} else {
			(void) sleep(slp);
		}
	}
}


static void
getxy_keepalive(nsc_ctx_t *ctx, nsc_db_t *nscdb, int keep, int interval)
{
	nsc_keephot_t		*table;
	nsc_entry_t		*entry, *ptr;
	int			i;
	nsc_lookup_args_t	*largs;
	nss_pheader_t		*phdr;
	int			bufsiz;
	char			*me = "getxy_keepalive";

	/* we won't be here if keep == 0 so need to check that */

	_NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_DEBUG)
	(me, "%s: keep alive\n", nscdb->name);

	if ((table = maken(keep)) == NULL) {
		_NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_ERROR)
			(me, "memory allocation failure\n");
		exit(1);
	}

	(void) mutex_lock(&nscdb->db_mutex);
	entry = nscdb->qtail;
	while (entry != NULL) {
		/* leave pending calls alone */
		if (!(entry->stats.status & ST_PENDING)) {
			/* do_revalidate */
			(void) insertn(table, entry->stats.hits, entry);
		}
		entry = entry->qnext;
	}
	for (i = 1; i <= keep; i++) {
		if (table[i].ptr == NULL)
			continue;
		ptr = (nsc_entry_t *)table[i].ptr;
		phdr = (nss_pheader_t *)ptr->buffer;
		if (NSCD_GET_STATUS(phdr) == NSS_SUCCESS)
			/*
			 * for positive cache, in addition to the packed
			 * header size, allocate twice the size of the
			 * existing result (in case the result grows
			 * larger) plus 2K (for the file/compat backend to
			 * process a possible large entry in the /etc files)
			 */
			bufsiz = phdr->data_off + 2 * phdr->data_len + 2048;
		else
			/*
			 * for negative cache, allocate 8K buffer to
			 * hold result in case the next lookup may
			 * return something (in addition to the
			 * packed header size)
			 */
			bufsiz = phdr->data_off + 8096;
		table[i].ptr = malloc(bufsiz);
		if (table[i].ptr == NULL) {
			(void) mutex_unlock(&nscdb->db_mutex);
			_NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_ERROR)
				(me, "memory allocation failure\n");
			exit(1);
		}
		(void) memcpy(table[i].ptr, ptr->buffer,  ptr->bufsize);
		((nss_pheader_t *)table[i].ptr)->pbufsiz = bufsiz;
		table[i].num = bufsiz;
	}
	(void) mutex_unlock(&nscdb->db_mutex);

	/* launch update thread for each keep hot entry */
	for (i = keep; i > 0; i--) {
		if (table[i].ptr == NULL)
			continue; /* unused slot in table */
		_NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_DEBUG)
		(me, "%s: launching update\n", nscdb->name);
		largs = (nsc_lookup_args_t *)malloc(sizeof (*largs));
		if (largs == NULL) {
			_NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_ERROR)
				(me, "memory allocation failure\n");
			exit(1);
		}
		largs->buffer = table[i].ptr;
		largs->bufsize = table[i].num;
		largs->ctx = ctx;
		largs->nscdb = nscdb;
		if (launch_update(largs) < 0)
			exit(1);
		(void) sleep(interval);
	}

	/*
	 * The update thread will handle freeing of buffer and largs.
	 * Free the table here.
	 */
	free(table);
}


static int
launch_update(nsc_lookup_args_t *in)
{
	char	*me = "launch_update";
	int	errnum;

	errnum = thr_create(NULL, NULL, (void *(*)(void*))do_update,
	    in, 0|THR_DETACHED, NULL);
	if (errnum != 0) {
		_NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_ERROR)
		(me, "%s: thread creation failure (%d)\n",
		    in->nscdb->name, errnum);
		return (-1);
	}
	return (0);
}


static void
do_update(nsc_lookup_args_t *in) {
	nss_pheader_t	*phdr = (nss_pheader_t *)in->buffer;

	/* update the length of the data buffer */
	phdr->data_len = phdr->pbufsiz - phdr->data_off;

	(void) lookup_int(in, UPDATEBIT);
	if (in->buffer)
		free(in->buffer);
	free(in);
}


/*
 * Invalidate cache
 */
void
nsc_invalidate(nsc_ctx_t *ctx, char *dbname, nsc_ctx_t **ctxs) {
	int	i;
	char	*me = "nsc_invalidate";

	if (ctx) {
		ctx_invalidate(ctx);
		return;
	}

	if (dbname) {
		if ((i = get_cache_idx(dbname)) == -1) {
			_NSCD_LOG(NSCD_LOG_CACHE,
				NSCD_LOG_LEVEL_WARNING)
			(me, "%s: invalid cache name\n", dbname);
			return;
		}
		if ((ctx = cache_ctx_p[i]) == NULL)  {
			_NSCD_LOG(NSCD_LOG_CACHE,
				NSCD_LOG_LEVEL_WARNING)
			(me, "%s: no cache context found\n",
				dbname);
			return;
		}
		ctx_invalidate(ctx);
		return;
	}

	if (ctxs == NULL)
		ctxs =  cache_ctx_p;

	for (i = 0; i < CACHE_CTX_COUNT; i++) {
		if (ctxs[i] != NULL)
		ctx_invalidate(ctxs[i]);
	}
}


/*
 * Invalidate cache by context
 */
static void
ctx_invalidate(nsc_ctx_t *ctx) {
	int 		i;
	nsc_entry_t	*entry;
	char		*me = "ctx_invalidate";

	_NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_DEBUG)
	(me, "%s: invalidate cache\n", ctx->dbname);

	for (i = 0; i < ctx->db_count; i++) {
		if (ctx->nsc_db[i] == NULL)
			continue;
		(void) mutex_lock(&ctx->nsc_db[i]->db_mutex);
		entry = ctx->nsc_db[i]->qtail;
		while (entry != NULL) {
			/* leave pending calls alone */
			if (!(entry->stats.status & ST_PENDING))
				entry->stats.status = ST_DISCARD;
			entry = entry->qnext;
		}
		(void) mutex_unlock(&ctx->nsc_db[i]->db_mutex);
	}

	(void) mutex_lock(&ctx->stats_mutex);
	ctx->stats.invalidate_count++;
	(void) mutex_unlock(&ctx->stats_mutex);
}


/*
 * Free nsc_entry_t
 *
 * Pre-reqs:
 * nscdb->db_mutex lock must be held before calling this function
 */
static void
delete_entry(nsc_db_t *nscdb, nsc_ctx_t *ctx, nsc_entry_t *entry) {
	uint_t		hash;

	avl_remove(&nscdb->tree, entry);
	HASH_REMOVE(nscdb, entry, hash, nscd_false);
	queue_remove(nscdb, entry);
	if (entry->buffer != NULL) {
		free(entry->buffer);
		entry->buffer = NULL;
	}
	umem_cache_free(nsc_entry_cache, entry);
	(void) mutex_lock(&ctx->stats_mutex);
	ctx->stats.entries--;
	(void) mutex_unlock(&ctx->stats_mutex);
}


static nscd_rc_t
lookup_cache(nsc_lookup_args_t *largs, nscd_cfg_cache_t *cfgp,
	nss_XbyY_args_t *argp, char *whoami, nsc_entry_t **entry) {

	nsc_db_t	*nscdb;
	nsc_ctx_t	*ctx;
	uint_t		hash;
	avl_index_t	pos;
	ulong_t		nentries;
	nsc_entry_t	find_entry, *node;
	char		*me = "lookup_cache";

	ctx = largs->ctx;
	nscdb = largs->nscdb;

	/* set the search key */
	find_entry.key = argp->key;	/* struct copy (not deep) */

	/* lookup the hash table ==> O(1) */
	if (nscdb->htable) {
		*entry = hash_find(nscdb, &find_entry, &hash, nscd_true);
		if (*entry != NULL) {
			(void) queue_adjust(nscdb, *entry);
			return (NSCD_SUCCESS);
		}
	}

	/* if not found, lookup the AVL tree ==> O(log n) */
	*entry = (nsc_entry_t *)avl_find(&nscdb->tree, &find_entry, &pos);
	if (*entry != NULL) {
		(void) queue_adjust(nscdb, *entry);
		/* move it to the hash table */
		if (nscdb->htable) {
			if (nscdb->htable[hash] == NULL ||
					(*entry)->stats.hits >=
					nscdb->htable[hash]->stats.hits) {
				nscdb->htable[hash] = *entry;
			}
		}
		return (NSCD_SUCCESS);
	}

	/* entry not found in the cache */
	_NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_DEBUG)
		(me, "%s: cache miss\n", whoami);

	if (cfgp->avoid_ns == nscd_true) {
		_NSCD_LOG(NSCD_LOG_CACHE,
			NSCD_LOG_LEVEL_DEBUG)
			(me, "%s: avoid name service\n", whoami);
		return (NSCD_DB_ENTRY_NOT_FOUND);
	}

	/* allocate memory for new entry (stub) */
	*entry = (nsc_entry_t *)umem_cache_alloc(nsc_entry_cache,
		UMEM_DEFAULT);
	if (*entry == NULL) {
		_NSCD_LOG(NSCD_LOG_CACHE,
			NSCD_LOG_LEVEL_ERROR)
			(me, "%s: memory allocation failure\n", whoami);
		return (NSCD_NO_MEMORY);
	}
	(void) memset(*entry, 0, sizeof (**entry));

	/*
	 * Note that the actual data for the key is stored within
	 * the largs->buffer (input buffer to nsc_lookup).
	 * find_entry.key only contains pointers to this data.
	 *
	 * If largs->buffer will be re-allocated by nss_psearch
	 * then (*entry)->key will have dangling pointers.
	 * In such case, the following assignment needs to be
	 * replaced by code that duplicates the key.
	 */
	(*entry)->key = find_entry.key;

	/*
	 * Add the entry to the cache.
	 */
	avl_insert(&nscdb->tree, *entry, pos);	/* O(log n) */
	(void) queue_adjust(nscdb, *entry);	/* constant */
	if (nscdb->htable)			/* constant */
		nscdb->htable[hash] = *entry;
	(*entry)->stats.status = ST_NEW_ENTRY;

	(void) mutex_lock(&ctx->stats_mutex);
	nentries = ++(ctx->stats.entries);
	(void) mutex_unlock(&ctx->stats_mutex);

	/* Have we exceeded max entries ? */
	if (cfgp->maxentries > 0 && nentries > cfgp->maxentries) {
		_NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_DEBUG)
			(me, "%s: maximum entries exceeded -- "
				"deleting least recently used entry\n",
			whoami);

		node = nscdb->qhead;
		while (node != NULL && node != *entry) {
			if (node->stats.status == ST_DISCARD ||
					!(node->stats.status & ST_PENDING)) {
				delete_entry(nscdb, ctx, node);
				break;
			}
			node = node->qprev;
		}

		/*
		 * It's okay if we were not able to find one to delete.
		 * The reaper (when invoked) will return the cache to a
		 * safe level.
		 */
	}

	return (NSCD_SUCCESS);
}

static void
reaper(nsc_ctx_t *ctx) {
	uint_t		ttl, extra_sleep, total_sleep, intervals;
	uint_t		nodes_per_interval, seconds_per_interval;
	ulong_t		nsc_entries;
	char		*me = "reaper";

	for (;;) {
		(void) mutex_lock(&ctx->stats_mutex);
		nsc_entries = ctx->stats.entries;
		(void) mutex_unlock(&ctx->stats_mutex);

		(void) rw_rdlock(&ctx->cfg_rwlp);
		ttl = ctx->cfg.pos_ttl;
		(void) rw_unlock(&ctx->cfg_rwlp);

		if (nsc_entries == 0) {
			_NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_DEBUG)
				(me, "%s: nothing to reap\n", ctx->dbname);

			/* sleep for atleast 60 seconds */
			if (ttl < 60)
				ttl = 60;
			_NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_DEBUG)
			(me, "%s: sleep %d\n", ctx->dbname, ttl);
			(void) sleep(ttl);
			continue;
		}

		if (ttl < 32) ttl = 32;
		if (ttl > (1<<28)) ttl = 1<<28;

		/*
		 * minimum nodes_per_interval = 256 or 1<<8
		 * maximum nodes_per_interval = nsc_entries
		 * minimum seconds_per_interval = 32 or 1<<5
		 * maximum_seconds_per_interval = ttl
		 */
		if (nsc_entries <= ttl) {
			intervals = (nsc_entries >> 8) + 1;
			seconds_per_interval = ttl / intervals;
			nodes_per_interval = 256;
		} else {
			intervals = (ttl >> 5) + 1;
			seconds_per_interval = 32;
			nodes_per_interval = nsc_entries / intervals;
			if (nodes_per_interval < 256)
				nodes_per_interval = 256;
		}

		_NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_DEBUG)
			(me, "%s: total entries = %d, "
			"seconds per interval = %d, "
			"nodes per interval = %d\n",
			ctx->dbname, nsc_entries, seconds_per_interval,
			nodes_per_interval);
		total_sleep = reap_cache(ctx, nodes_per_interval,
				seconds_per_interval);
		extra_sleep = 1 + ttl - total_sleep;
		if (extra_sleep > 0)
			(void) sleep(extra_sleep);
	}
}


static uint_t
reap_cache(nsc_ctx_t *ctx, uint_t nodes_per_interval,
		uint_t seconds_per_interval) {
	uint_t		nodes_togo, total_sleep;
	time_t		now;
	nsc_entry_t	*node, *next_node;
	nsc_db_t	*nscdb;
	uint_t		primes[] = {_NSC_HTSIZE_PRIMES};
	ulong_t		count, nentries, maxentries;
	int		i, slot, value, newhtsize;
	char		*me = "reap_cache";

	count = 0;
	total_sleep = 0;
	nodes_togo = nodes_per_interval;
	now = time(NULL);

	for (i = 0; i < ctx->db_count; i++) {
		nscdb = ctx->nsc_db[i];
		(void) mutex_lock(&nscdb->db_mutex);
		nscdb->reap_node = nscdb->qtail;
		while (nscdb->reap_node != NULL) {
			if (nodes_togo == 0) {
				(void) mutex_unlock(&nscdb->db_mutex);
				(void) sleep(seconds_per_interval);
				total_sleep += seconds_per_interval;
				nodes_togo = nodes_per_interval;
				now = time(NULL);
				(void) mutex_lock(&nscdb->db_mutex);
			}
			/* delete ST_DISCARD and expired nodes */
			if ((node = nscdb->reap_node) == NULL)
				break;
			if (node->stats.status == ST_DISCARD ||
					(!(node->stats.status & ST_PENDING) &&
					node->stats.timestamp < now)) {
				/*
				 * Delete entry if its discard flag is
				 * set OR if it has expired. Entries
				 * with pending updates are not
				 * deleted.
				 * nscdb->reap_node will be adjusted
				 * by delete_entry()
				 */
				delete_entry(nscdb, ctx, node);
				count++;
			} else {
				nscdb->reap_node = node->qnext;
			}
			nodes_togo--;
		}

		if (nscdb->htsize == 0) {
			(void) mutex_unlock(&nscdb->db_mutex);
			continue;
		}

		/*
		 * Dynamic adjustment of hash table size.
		 *
		 * Hash table size is roughly 1/8th of the
		 * total entries. However the size is changed
		 * only when the number of entries double or
		 * reduced by half
		 */
		nentries = avl_numnodes(&nscdb->tree);
		for (slot = 0, value = _NSC_INIT_HTSIZE_SLOT_VALUE;
			slot < _NSC_HTSIZE_NUM_SLOTS && nentries > value;
			value = (value << 1) + 1, slot++);
		if (nscdb->hash_type == nsc_ht_power2)
			newhtsize = _NSC_INIT_HTSIZE_POWER2 << slot;
		else
			newhtsize = primes[slot];

		/* Recommended size is same as the current size. Done */
		if (nscdb->htsize == newhtsize) {
			(void) mutex_unlock(&nscdb->db_mutex);
			continue;
		}

		_NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_DEBUG)
			(me, "%s: resizing hash table from %d to %d\n",
			nscdb->name, nscdb->htsize, newhtsize);

		/*
		 * Dump old hashes because it would be time
		 * consuming to rehash them.
		 */
		(void) free(nscdb->htable);
		nscdb->htable = calloc(newhtsize, sizeof (*(nscdb->htable)));
		if (nscdb->htable == NULL) {
			_NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_ERROR)
				(me,
				"%s: memory allocation failure\n",
				nscdb->name);
			/* -1 to try later */
			nscdb->htsize = -1;
		} else {
			nscdb->htsize = newhtsize;
		}
		(void) mutex_unlock(&nscdb->db_mutex);
	}

	_NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_DEBUG)
		(me, "%s: reaped %lu entries\n", ctx->dbname, count);

	/*
	 * if cache is almost full then reduce it to a safe level by
	 * evicting LRU entries
	 */

	(void) rw_rdlock(&ctx->cfg_rwlp);
	maxentries = ctx->cfg.maxentries;
	(void) rw_unlock(&ctx->cfg_rwlp);

	/* No limit on number of entries. Done */
	if (maxentries == 0)
		goto out;

	(void) mutex_lock(&ctx->stats_mutex);
	nentries = ctx->stats.entries;
	(void) mutex_unlock(&ctx->stats_mutex);

	/* what is the percentage of cache used ? */
	value = (nentries * 100) / maxentries;
	if (value < _NSC_EVICTION_START_LEVEL)
		goto out;

	/*
	 * cache needs to be reduced to a safe level
	 */
	value -= _NSC_EVICTION_SAFE_LEVEL;
	for (i = 0, count = 0; i < ctx->db_count; i++) {
		/*
		 * Reduce each subcache by 'value' percent
		 */
		nscdb = ctx->nsc_db[i];
		(void) mutex_lock(&nscdb->db_mutex);
		nodes_togo = (value * avl_numnodes(&nscdb->tree)) / 100;

		/* Start from LRU entry i.e queue head */
		next_node = nscdb->qhead;
		while (nodes_togo > 0 && next_node != NULL) {
			node = next_node;
			next_node = next_node->qprev;
			if (node->stats.status == ST_DISCARD ||
					!(node->stats.status & ST_PENDING)) {
				/* Leave nodes with pending updates alone  */
				delete_entry(nscdb, ctx, node);
				count++;
				nodes_togo--;
			}
		}
		(void) mutex_unlock(&nscdb->db_mutex);
	}

	_NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_DEBUG)
		(me, "%s: evicted %lu LRU entries\n", ctx->dbname, count);

out:
	return (total_sleep);
}