xref: /illumos-gate/usr/src/cmd/nscd/cache.c (revision b1e2e3fb17324e9ddf43db264a0c64da7756d9e6)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License (the "License").
6  * You may not use this file except in compliance with the License.
7  *
8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9  * or http://www.opensolaris.org/os/licensing.
10  * See the License for the specific language governing permissions
11  * and limitations under the License.
12  *
13  * When distributing Covered Code, include this CDDL HEADER in each
14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15  * If applicable, add the following below this CDDL HEADER, with the
16  * fields enclosed by brackets "[]" replaced with your own identifying
17  * information: Portions Copyright [yyyy] [name of copyright owner]
18  *
19  * CDDL HEADER END
20  */
21 /*
22  * Copyright (c) 2006, 2010, Oracle and/or its affiliates. All rights reserved.
23  * Copyright 2012 Milan Jurik. All rights reserved.
24  * Copyright (c) 2016 by Delphix. All rights reserved.
25  * Copyright 2018 Joyent, Inc.
26  */
27 
28 /*
29  * Cache routines for nscd
30  */
31 #include <assert.h>
32 #include <errno.h>
33 #include <memory.h>
34 #include <signal.h>
35 #include <stdlib.h>
36 #include <stddef.h>
37 #include <stdio.h>
38 #include <string.h>
39 #include <sys/stat.h>
40 #include <sys/time.h>
41 #include <sys/types.h>
42 #include <sys/wait.h>
43 #include <unistd.h>
44 #include <ucred.h>
45 #include <nss_common.h>
46 #include <locale.h>
47 #include <ctype.h>
48 #include <strings.h>
49 #include <string.h>
50 #include <umem.h>
51 #include <fcntl.h>
52 #include "cache.h"
53 #include "nscd_door.h"
54 #include "nscd_log.h"
55 #include "nscd_config.h"
56 #include "nscd_frontend.h"
57 #include "nscd_switch.h"
58 
59 #define	SUCCESS		0
60 #define	NOTFOUND	-1
61 #define	SERVERERROR	-2
62 #define	NOSERVER	-3
63 #define	CONTINUE	-4
64 
65 static nsc_db_t *nsc_get_db(nsc_ctx_t *, int);
66 static nscd_rc_t lookup_cache(nsc_lookup_args_t *, nscd_cfg_cache_t *,
67 		nss_XbyY_args_t *, char *, nsc_entry_t **);
68 static uint_t reap_cache(nsc_ctx_t *, uint_t, uint_t);
69 static void delete_entry(nsc_db_t *, nsc_ctx_t *, nsc_entry_t *);
70 static void print_stats(nscd_cfg_stat_cache_t *);
71 static void print_cfg(nscd_cfg_cache_t *);
72 static int lookup_int(nsc_lookup_args_t *, int);
73 
74 #ifdef	NSCD_DEBUG
75 static void print_entry(nsc_db_t *, time_t, nsc_entry_t *);
76 static void avl_dump(nsc_db_t *, time_t);
77 static void hash_dump(nsc_db_t *, time_t);
78 #endif	/* NSCD_DEBUG */
79 static nsc_entry_t *hash_find(nsc_db_t *, nsc_entry_t *, uint_t *, nscd_bool_t);
80 
81 static void queue_adjust(nsc_db_t *, nsc_entry_t *);
82 static void queue_remove(nsc_db_t *, nsc_entry_t *);
83 #ifdef	NSCD_DEBUG
84 static void queue_dump(nsc_db_t *, time_t);
85 #endif	/* NSCD_DEBUG */
86 
87 static int launch_update(nsc_lookup_args_t *);
88 static void do_update(nsc_lookup_args_t *);
89 static void getxy_keepalive(nsc_ctx_t *, nsc_db_t *, int, int);
90 
91 static void ctx_info(nsc_ctx_t *);
92 static void ctx_info_nolock(nsc_ctx_t *);
93 static void ctx_invalidate(nsc_ctx_t *);
94 
95 static void nsc_db_str_key_getlogstr(char *, char *, size_t, nss_XbyY_args_t *);
96 static void nsc_db_int_key_getlogstr(char *, char *, size_t, nss_XbyY_args_t *);
97 static void nsc_db_any_key_getlogstr(char *, char *, size_t, nss_XbyY_args_t *);
98 
99 static int nsc_db_cis_key_compar(const void *, const void *);
100 static int nsc_db_ces_key_compar(const void *, const void *);
101 static int nsc_db_int_key_compar(const void *, const void *);
102 
103 static uint_t nsc_db_cis_key_gethash(nss_XbyY_key_t *, int);
104 static uint_t nsc_db_ces_key_gethash(nss_XbyY_key_t *, int);
105 static uint_t nsc_db_int_key_gethash(nss_XbyY_key_t *, int);
106 
107 static umem_cache_t	*nsc_entry_cache;
108 
109 static nsc_ctx_t *init_cache_ctx(int);
110 static void reaper(nsc_ctx_t *);
111 static void revalidate(nsc_ctx_t *);
112 
113 static nss_status_t
114 dup_packed_buffer(void *src, void *dst) {
115 	nsc_lookup_args_t	*s = (nsc_lookup_args_t *)src;
116 	nsc_entry_t		*d = (nsc_entry_t *)dst;
117 	nss_pheader_t		*sphdr = (nss_pheader_t *)s->buffer;
118 	nss_pheader_t		*dphdr = (nss_pheader_t *)d->buffer;
119 	int			slen, new_pbufsiz = 0;
120 
121 	if (NSCD_GET_STATUS(sphdr) != NSS_SUCCESS) {
122 
123 		/* no result, copy header only (status, errno, etc) */
124 		slen = sphdr->data_off;
125 	} else {
126 		/*
127 		 * lookup result returned, data to copy is the packed
128 		 * header plus result (add 1 for the terminating NULL
129 		 * just in case)
130 		 */
131 		slen = sphdr->data_off + sphdr->data_len + 1;
132 	}
133 
134 	/* allocate cache packed buffer */
135 	if (dphdr != NULL && d->bufsize <= slen && d->bufsize != 0) {
136 		/* old buffer too small, free it */
137 		free(dphdr);
138 		d->buffer = NULL;
139 		d->bufsize = 0;
140 		dphdr = NULL;
141 	}
142 	if (dphdr == NULL) {
143 		/* get new buffer */
144 		dphdr = calloc(1, slen + 1);
145 		if (dphdr == NULL)
146 			return (NSS_ERROR);
147 		d->buffer = dphdr;
148 		d->bufsize = slen + 1;
149 		new_pbufsiz = slen + 1;
150 	}
151 
152 	(void) memcpy(dphdr, sphdr, slen);
153 	if (new_pbufsiz != 0)
154 		dphdr->pbufsiz = new_pbufsiz;
155 
156 	return (NSS_SUCCESS);
157 }
158 
159 char *cache_name[CACHE_CTX_COUNT] = {
160 	NSS_DBNAM_PASSWD,
161 	NSS_DBNAM_GROUP,
162 	NSS_DBNAM_HOSTS,
163 	NSS_DBNAM_IPNODES,
164 	NSS_DBNAM_EXECATTR,
165 	NSS_DBNAM_PROFATTR,
166 	NSS_DBNAM_USERATTR,
167 	NSS_DBNAM_ETHERS,
168 	NSS_DBNAM_RPC,
169 	NSS_DBNAM_PROTOCOLS,
170 	NSS_DBNAM_NETWORKS,
171 	NSS_DBNAM_BOOTPARAMS,
172 	NSS_DBNAM_AUTHATTR,
173 	NSS_DBNAM_SERVICES,
174 	NSS_DBNAM_NETMASKS,
175 	NSS_DBNAM_PRINTERS,
176 	NSS_DBNAM_PROJECT,
177 	NSS_DBNAM_TSOL_TP,
178 	NSS_DBNAM_TSOL_RH
179 };
180 
181 typedef void (*cache_init_ctx_t)(nsc_ctx_t *);
182 static cache_init_ctx_t cache_init_ctx[CACHE_CTX_COUNT] = {
183 	passwd_init_ctx,
184 	group_init_ctx,
185 	host_init_ctx,
186 	ipnode_init_ctx,
187 	exec_init_ctx,
188 	prof_init_ctx,
189 	user_init_ctx,
190 	ether_init_ctx,
191 	rpc_init_ctx,
192 	proto_init_ctx,
193 	net_init_ctx,
194 	bootp_init_ctx,
195 	auth_init_ctx,
196 	serv_init_ctx,
197 	netmask_init_ctx,
198 	printer_init_ctx,
199 	project_init_ctx,
200 	tnrhtp_init_ctx,
201 	tnrhdb_init_ctx
202 };
203 
204 nsc_ctx_t *cache_ctx_p[CACHE_CTX_COUNT] = { 0 };
205 static nscd_cfg_stat_cache_t	null_stats = { 0 };
206 static nscd_cfg_global_cache_t	global_cfg;
207 
208 /*
209  * Given database name 'dbname' find cache index
210  */
211 int
212 get_cache_idx(char *dbname) {
213 	int	i;
214 	char	*nsc_name;
215 
216 	for (i = 0; i < CACHE_CTX_COUNT; i++) {
217 		nsc_name = cache_name[i];
218 		if (strcmp(nsc_name, dbname) == 0)
219 			return (i);
220 	}
221 	return (-1);
222 }
223 
224 /*
225  * Given database name 'dbname' retrieve cache context,
226  * if not created yet, allocate and initialize it.
227  */
228 static nscd_rc_t
229 get_cache_ctx(char *dbname, nsc_ctx_t **ctx) {
230 	int	i;
231 
232 	*ctx = NULL;
233 
234 	i = get_cache_idx(dbname);
235 	if (i == -1)
236 		return (NSCD_INVALID_ARGUMENT);
237 	if ((*ctx = cache_ctx_p[i]) == NULL) {
238 		*ctx = init_cache_ctx(i);
239 		if (*ctx == NULL)
240 			return (NSCD_NO_MEMORY);
241 	}
242 
243 	return (NSCD_SUCCESS);
244 }
245 
246 /*
247  * Generate a log string to identify backend operation in debug logs
248  */
249 static void
250 nsc_db_str_key_getlogstr(char *name, char *whoami, size_t len,
251 		nss_XbyY_args_t *argp) {
252 	(void) snprintf(whoami, len, "%s [key=%s]", name, argp->key.name);
253 }
254 
255 
256 static void
257 nsc_db_int_key_getlogstr(char *name, char *whoami, size_t len,
258 		nss_XbyY_args_t *argp) {
259 	(void) snprintf(whoami, len, "%s [key=%d]", name, argp->key.number);
260 }
261 
262 /*ARGSUSED*/
263 static void
264 nsc_db_any_key_getlogstr(char *name, char *whoami, size_t len,
265 		nss_XbyY_args_t *argp) {
266 	(void) snprintf(whoami, len, "%s", name);
267 }
268 
269 
270 /*
271  * Returns cache based on dbop
272  */
273 static nsc_db_t *
274 nsc_get_db(nsc_ctx_t *ctx, int dbop) {
275 	int	i;
276 
277 	for (i = 0; i < ctx->db_count; i++) {
278 		if (ctx->nsc_db[i] && dbop == ctx->nsc_db[i]->dbop)
279 			return (ctx->nsc_db[i]);
280 	}
281 	return (NULL);
282 }
283 
284 
285 /*
286  * integer compare routine for _NSC_DB_INT_KEY
287  */
288 static int
289 nsc_db_int_key_compar(const void *n1, const void *n2) {
290 	nsc_entry_t	*e1, *e2;
291 
292 	e1 = (nsc_entry_t *)n1;
293 	e2 = (nsc_entry_t *)n2;
294 	return (_NSC_INT_KEY_CMP(e1->key.number, e2->key.number));
295 }
296 
297 
298 /*
299  * case sensitive name compare routine for _NSC_DB_CES_KEY
300  */
301 static int
302 nsc_db_ces_key_compar(const void *n1, const void *n2) {
303 	nsc_entry_t	*e1, *e2;
304 	int		res, l1, l2;
305 
306 	e1 = (nsc_entry_t *)n1;
307 	e2 = (nsc_entry_t *)n2;
308 	l1 = strlen(e1->key.name);
309 	l2 = strlen(e2->key.name);
310 	res = strncmp(e1->key.name, e2->key.name, (l1 > l2)?l1:l2);
311 	return (_NSC_INT_KEY_CMP(res, 0));
312 }
313 
314 
315 /*
316  * case insensitive name compare routine _NSC_DB_CIS_KEY
317  */
318 static int
319 nsc_db_cis_key_compar(const void *n1, const void *n2) {
320 	nsc_entry_t	*e1, *e2;
321 	int		res, l1, l2;
322 
323 	e1 = (nsc_entry_t *)n1;
324 	e2 = (nsc_entry_t *)n2;
325 	l1 = strlen(e1->key.name);
326 	l2 = strlen(e2->key.name);
327 	res = strncasecmp(e1->key.name, e2->key.name, (l1 > l2)?l1:l2);
328 	return (_NSC_INT_KEY_CMP(res, 0));
329 }
330 
331 /*
332  * macro used to generate elf hashes for strings
333  */
334 #define	_NSC_ELF_STR_GETHASH(func, str, htsize, hval) \
335 	hval = 0; \
336 	while (*str) { \
337 		uint_t  g; \
338 		hval = (hval << 4) + func(*str++); \
339 		if ((g = (hval & 0xf0000000)) != 0) \
340 			hval ^= g >> 24; \
341 		hval &= ~g; \
342 	} \
343 	hval %= htsize;
344 
345 
346 /*
347  * cis hash function
348  */
349 uint_t
350 cis_gethash(const char *key, int htsize) {
351 	uint_t	hval;
352 	if (key == NULL)
353 		return (0);
354 	_NSC_ELF_STR_GETHASH(tolower, key, htsize, hval);
355 	return (hval);
356 }
357 
358 
359 /*
360  * ces hash function
361  */
362 uint_t
363 ces_gethash(const char *key, int htsize) {
364 	uint_t	hval;
365 	if (key == NULL)
366 		return (0);
367 	_NSC_ELF_STR_GETHASH(, key, htsize, hval);
368 	return (hval);
369 }
370 
371 
372 /*
373  * one-at-a-time hash function
374  */
375 uint_t
376 db_gethash(const void *key, int len, int htsize) {
377 	uint_t	hval, i;
378 	const char *str = key;
379 
380 	if (str == NULL)
381 		return (0);
382 
383 	for (hval = 0, i = 0; i < len; i++) {
384 		hval += str[i];
385 		hval += (hval << 10);
386 		hval ^= (hval >> 6);
387 	}
388 	hval += (hval << 3);
389 	hval ^= (hval >> 11);
390 	hval += (hval << 15);
391 	return (hval % htsize);
392 }
393 
394 
395 /*
396  * case insensitive name gethash routine _NSC_DB_CIS_KEY
397  */
398 static uint_t
399 nsc_db_cis_key_gethash(nss_XbyY_key_t *key, int htsize) {
400 	return (cis_gethash(key->name, htsize));
401 }
402 
403 
404 /*
405  * case sensitive name gethash routine _NSC_DB_CES_KEY
406  */
407 static uint_t
408 nsc_db_ces_key_gethash(nss_XbyY_key_t *key, int htsize) {
409 	return (ces_gethash(key->name, htsize));
410 }
411 
412 
413 /*
414  * integer gethash routine _NSC_DB_INT_KEY
415  */
416 static uint_t
417 nsc_db_int_key_gethash(nss_XbyY_key_t *key, int htsize) {
418 	return (db_gethash(&key->number, sizeof (key->number), htsize));
419 }
420 
421 
422 /*
423  * Find entry in the hash table
424  * if cmp == nscd_true)
425  *	return entry only if the keys match
426  * else
427  *	return entry in the hash location without checking the keys
428  *
429  */
430 static nsc_entry_t *
431 hash_find(nsc_db_t *nscdb, nsc_entry_t *entry, uint_t *hash,
432 			nscd_bool_t cmp) {
433 
434 	nsc_entry_t	*hashentry;
435 
436 	if (nscdb->gethash)
437 		*hash = nscdb->gethash(&entry->key, nscdb->htsize);
438 	else
439 		return (NULL);
440 
441 	hashentry = nscdb->htable[*hash];
442 	if (cmp == nscd_false || hashentry == NULL)
443 		return (hashentry);
444 	if (nscdb->compar) {
445 		if (nscdb->compar(entry, hashentry) == 0)
446 			return (hashentry);
447 	}
448 	return (NULL);
449 }
450 
451 
452 #define	HASH_REMOVE(nscdb, entry, hash, cmp) \
453 	if (nscdb->htable) { \
454 		if (entry == hash_find(nscdb, entry, &hash, cmp)) \
455 			nscdb->htable[hash] = NULL; \
456 	}
457 
458 
459 #define	HASH_INSERT(nscdb, entry, hash, cmp) \
460 	if (nscdb->htable) { \
461 		(void) hash_find(nscdb, entry, &hash, cmp); \
462 		nscdb->htable[hash] = entry; \
463 	}
464 
465 
466 #ifdef	NSCD_DEBUG
467 static void
468 print_entry(nsc_db_t *nscdb, time_t now, nsc_entry_t *entry) {
469 	nss_XbyY_args_t args;
470 	char		whoami[512];
471 
472 	switch (entry->stats.status) {
473 	case ST_NEW_ENTRY:
474 		(void) fprintf(stdout, gettext("\t status: new entry\n"));
475 		return;
476 	case ST_UPDATE_PENDING:
477 		(void) fprintf(stdout, gettext("\t status: update pending\n"));
478 		return;
479 	case ST_LOOKUP_PENDING:
480 		(void) fprintf(stdout, gettext("\t status: lookup pending\n"));
481 		return;
482 	case ST_DISCARD:
483 		(void) fprintf(stdout, gettext("\t status: discarded entry\n"));
484 		return;
485 	default:
486 		if (entry->stats.timestamp < now)
487 			(void) fprintf(stdout,
488 			gettext("\t status: expired (%d seconds ago)\n"),
489 			now - entry->stats.timestamp);
490 		else
491 			(void) fprintf(stdout,
492 			gettext("\t status: valid (expiry in %d seconds)\n"),
493 			entry->stats.timestamp - now);
494 		break;
495 	}
496 	(void) fprintf(stdout, gettext("\t hits: %u\n"), entry->stats.hits);
497 	args.key = entry->key;
498 	(void) nscdb->getlogstr(nscdb->name, whoami, sizeof (whoami), &args);
499 	(void) fprintf(stdout, "\t %s\n", whoami);
500 }
501 #endif	/* NSCD_DEBUG */
502 
503 static void
504 print_stats(nscd_cfg_stat_cache_t *statsp) {
505 
506 	(void) fprintf(stdout, gettext("\n\t STATISTICS:\n"));
507 	(void) fprintf(stdout, gettext("\t positive hits: %lu\n"),
508 			statsp->pos_hits);
509 	(void) fprintf(stdout, gettext("\t negative hits: %lu\n"),
510 			statsp->neg_hits);
511 	(void) fprintf(stdout, gettext("\t positive misses: %lu\n"),
512 			statsp->pos_misses);
513 	(void) fprintf(stdout, gettext("\t negative misses: %lu\n"),
514 			statsp->neg_misses);
515 	(void) fprintf(stdout, gettext("\t total entries: %lu\n"),
516 			statsp->entries);
517 	(void) fprintf(stdout, gettext("\t queries queued: %lu\n"),
518 			statsp->wait_count);
519 	(void) fprintf(stdout, gettext("\t queries dropped: %lu\n"),
520 			statsp->drop_count);
521 	(void) fprintf(stdout, gettext("\t cache invalidations: %lu\n"),
522 			statsp->invalidate_count);
523 
524 	_NSC_GET_HITRATE(statsp);
525 	(void) fprintf(stdout, gettext("\t cache hit rate: %10.1f\n"),
526 			statsp->hitrate);
527 }
528 
529 
530 static void
531 print_cfg(nscd_cfg_cache_t *cfgp) {
532 	(void) fprintf(stdout, gettext("\n\t CONFIG:\n"));
533 	(void) fprintf(stdout, gettext("\t enabled: %s\n"),
534 			yes_no(cfgp->enable));
535 	(void) fprintf(stdout, gettext("\t per user cache: %s\n"),
536 			yes_no(cfgp->per_user));
537 	(void) fprintf(stdout, gettext("\t avoid name service: %s\n"),
538 			yes_no(cfgp->avoid_ns));
539 	(void) fprintf(stdout, gettext("\t check file: %s\n"),
540 			yes_no(cfgp->check_files));
541 	(void) fprintf(stdout, gettext("\t check file interval: %d\n"),
542 			cfgp->check_interval);
543 	(void) fprintf(stdout, gettext("\t positive ttl: %d\n"),
544 			cfgp->pos_ttl);
545 	(void) fprintf(stdout, gettext("\t negative ttl: %d\n"),
546 			cfgp->neg_ttl);
547 	(void) fprintf(stdout, gettext("\t keep hot count: %d\n"),
548 			cfgp->keephot);
549 	(void) fprintf(stdout, gettext("\t hint size: %d\n"),
550 			cfgp->hint_size);
551 	(void) fprintf(stdout, gettext("\t max entries: %lu%s"),
552 			cfgp->maxentries,
553 			cfgp->maxentries?"\n":" (unlimited)\n");
554 }
555 
556 
557 #ifdef	NSCD_DEBUG
558 static void
559 hash_dump(nsc_db_t *nscdb, time_t now) {
560 	nsc_entry_t	*entry;
561 	int		i;
562 
563 	(void) fprintf(stdout, gettext("\n\nHASH TABLE:\n"));
564 	for (i = 0; i < nscdb->htsize; i++) {
565 		if ((entry = nscdb->htable[i]) != NULL) {
566 			(void) fprintf(stdout, "hash[%d]:\n", i);
567 			print_entry(nscdb, now, entry);
568 		}
569 	}
570 }
571 #endif	/* NSCD_DEBUG */
572 
573 
574 #ifdef	NSCD_DEBUG
575 static void
576 avl_dump(nsc_db_t *nscdb, time_t now) {
577 	nsc_entry_t	*entry;
578 	int		i;
579 
580 	(void) fprintf(stdout, gettext("\n\nAVL TREE:\n"));
581 	for (entry = avl_first(&nscdb->tree), i = 0; entry != NULL;
582 			entry = avl_walk(&nscdb->tree, entry, AVL_AFTER)) {
583 		(void) fprintf(stdout, "avl node[%d]:\n", i++);
584 		print_entry(nscdb, now, entry);
585 	}
586 }
587 #endif	/* NSCD_DEBUG */
588 
589 
590 #ifdef	NSCD_DEBUG
591 static void
592 queue_dump(nsc_db_t *nscdb, time_t now) {
593 	nsc_entry_t	*entry;
594 	int		i;
595 
596 	(void) fprintf(stdout,
597 		gettext("\n\nCACHE [name=%s, nodes=%lu]:\n"),
598 		nscdb->name, avl_numnodes(&nscdb->tree));
599 
600 	(void) fprintf(stdout,
601 		gettext("Starting with the most recently accessed:\n"));
602 
603 	for (entry = nscdb->qtail, i = 0; entry; entry = entry->qnext) {
604 		(void) fprintf(stdout, "entry[%d]:\n", i++);
605 		print_entry(nscdb, now, entry);
606 	}
607 }
608 #endif	/* NSCD_DEBUG */
609 
610 static void
611 queue_remove(nsc_db_t *nscdb, nsc_entry_t *entry) {
612 
613 	if (nscdb->qtail == entry)
614 		nscdb->qtail = entry->qnext;
615 	else
616 		entry->qprev->qnext = entry->qnext;
617 
618 	if (nscdb->qhead == entry)
619 		nscdb->qhead = entry->qprev;
620 	else
621 		entry->qnext->qprev = entry->qprev;
622 
623 	if (nscdb->reap_node == entry)
624 		nscdb->reap_node = entry->qnext;
625 	entry->qnext = entry->qprev = NULL;
626 }
627 
628 
629 static void
630 queue_adjust(nsc_db_t *nscdb, nsc_entry_t *entry) {
631 
632 #ifdef NSCD_DEBUG
633 	assert(nscdb->qtail || entry->qnext == NULL &&
634 			entry->qprev == NULL);
635 
636 	assert(nscdb->qtail && nscdb->qhead ||
637 		nscdb->qtail == NULL && nscdb->qhead == NULL);
638 
639 	assert(entry->qprev || entry->qnext == NULL ||
640 		nscdb->qtail == entry);
641 #endif /* NSCD_DEBUG */
642 
643 	/* already in the desired position */
644 	if (nscdb->qtail == entry)
645 		return;
646 
647 	/* new queue */
648 	if (nscdb->qtail == NULL) {
649 		nscdb->qhead = nscdb->qtail = entry;
650 		return;
651 	}
652 
653 	/* new entry (prev == NULL AND tail != entry) */
654 	if (entry->qprev == NULL) {
655 		nscdb->qtail->qprev = entry;
656 		entry->qnext = nscdb->qtail;
657 		nscdb->qtail = entry;
658 		return;
659 	}
660 
661 	/* existing entry */
662 	if (nscdb->reap_node == entry)
663 		nscdb->reap_node = entry->qnext;
664 	if (nscdb->qhead == entry)
665 		nscdb->qhead = entry->qprev;
666 	else
667 		entry->qnext->qprev = entry->qprev;
668 	entry->qprev->qnext = entry->qnext;
669 	entry->qprev = NULL;
670 	entry->qnext = nscdb->qtail;
671 	nscdb->qtail->qprev = entry;
672 	nscdb->qtail = entry;
673 }
674 
675 
676 /*
677  * Init cache
678  */
679 nscd_rc_t
680 init_cache(int debug_level) {
681 	int cflags;
682 
683 	cflags = (debug_level > 0)?0:UMC_NODEBUG;
684 	nsc_entry_cache = umem_cache_create("nsc_entry_cache",
685 				sizeof (nsc_entry_t), 0, NULL, NULL, NULL,
686 				NULL, NULL, cflags);
687 	if (nsc_entry_cache == NULL)
688 		return (NSCD_NO_MEMORY);
689 	return (NSCD_SUCCESS);
690 }
691 
692 
693 /*
694  * Create cache
695  */
696 nsc_db_t *
697 make_cache(enum db_type dbtype, int dbop, char *name,
698     int (*compar) (const void *, const void *),
699     void (*getlogstr)(char *, char *, size_t, nss_XbyY_args_t *),
700     uint_t (*gethash)(nss_XbyY_key_t *, int),
701     enum hash_type httype, int htsize)
702 {
703 	nsc_db_t	*nscdb;
704 	char		*me = "make_cache";
705 
706 	nscdb = (nsc_db_t *)malloc(sizeof (*nscdb));
707 	if (nscdb == NULL) {
708 		_NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_ERROR)
709 		(me, "%s: memory allocation failure\n", name);
710 		goto out;
711 	}
712 	(void) memset(nscdb, 0, sizeof (*nscdb));
713 
714 	nscdb->dbop = dbop;
715 	nscdb->name = name;
716 	nscdb->db_type = dbtype;
717 
718 	/* Assign compare routine */
719 	if (compar == NULL) {
720 		if (_NSC_DB_CES_KEY(nscdb))
721 			nscdb->compar = nsc_db_ces_key_compar;
722 		else if (_NSC_DB_CIS_KEY(nscdb))
723 			nscdb->compar = nsc_db_cis_key_compar;
724 		else if (_NSC_DB_INT_KEY(nscdb))
725 			nscdb->compar = nsc_db_int_key_compar;
726 		else
727 			assert(0);
728 	} else {
729 		nscdb->compar = compar;
730 	}
731 
732 	/* The cache is an AVL tree */
733 	avl_create(&nscdb->tree, nscdb->compar, sizeof (nsc_entry_t),
734 	    offsetof(nsc_entry_t, avl_link));
735 
736 	/* Assign log routine */
737 	if (getlogstr == NULL) {
738 		if (_NSC_DB_STR_KEY(nscdb))
739 			nscdb->getlogstr = nsc_db_str_key_getlogstr;
740 		else if (_NSC_DB_INT_KEY(nscdb))
741 			nscdb->getlogstr = nsc_db_int_key_getlogstr;
742 		else
743 			nscdb->getlogstr = nsc_db_any_key_getlogstr;
744 	} else {
745 		nscdb->getlogstr = getlogstr;
746 	}
747 
748 	/* The AVL tree based cache uses a hash table for quick access */
749 	if (htsize != 0) {
750 		/* Determine hash table size based on type */
751 		nscdb->hash_type = httype;
752 		if (htsize < 0) {
753 			switch (httype) {
754 			case nsc_ht_power2:
755 				htsize = _NSC_INIT_HTSIZE_POWER2;
756 				break;
757 			case nsc_ht_prime:
758 			case nsc_ht_default:
759 			default:
760 				htsize = _NSC_INIT_HTSIZE_PRIME;
761 			}
762 		}
763 		nscdb->htsize = htsize;
764 
765 		/* Create the hash table */
766 		nscdb->htable = calloc(htsize, sizeof (*(nscdb->htable)));
767 		if (nscdb->htable == NULL) {
768 			_NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_ERROR)
769 			(me, "%s: memory allocation failure\n", name);
770 			goto out;
771 		}
772 
773 		/* Assign gethash routine */
774 		if (gethash == NULL) {
775 			if (_NSC_DB_CES_KEY(nscdb))
776 				nscdb->gethash = nsc_db_ces_key_gethash;
777 			else if (_NSC_DB_CIS_KEY(nscdb))
778 				nscdb->gethash = nsc_db_cis_key_gethash;
779 			else if (_NSC_DB_INT_KEY(nscdb))
780 				nscdb->gethash = nsc_db_int_key_gethash;
781 			else
782 				assert(0);
783 		} else {
784 			nscdb->gethash = gethash;
785 		}
786 	}
787 
788 	(void) mutex_init(&nscdb->db_mutex, USYNC_THREAD, NULL);
789 	return (nscdb);
790 
791 out:
792 	if (nscdb->htable)
793 		free(nscdb->htable);
794 	if (nscdb)
795 		free(nscdb);
796 	return (NULL);
797 }
798 
799 
800 /*
801  * verify
802  */
803 /* ARGSUSED */
804 nscd_rc_t
805 _nscd_cfg_cache_verify(
806 	void				*data,
807 	struct nscd_cfg_param_desc	*pdesc,
808 	nscd_cfg_id_t			*nswdb,
809 	nscd_cfg_flag_t			dflag,
810 	nscd_cfg_error_t		**errorp,
811 	void				**cookie)
812 {
813 
814 	return (NSCD_SUCCESS);
815 }
816 
817 /*
818  * notify
819  */
820 /* ARGSUSED */
821 nscd_rc_t
822 _nscd_cfg_cache_notify(
823 	void				*data,
824 	struct nscd_cfg_param_desc	*pdesc,
825 	nscd_cfg_id_t			*nswdb,
826 	nscd_cfg_flag_t			dflag,
827 	nscd_cfg_error_t		**errorp,
828 	void				**cookie)
829 {
830 	nsc_ctx_t	*ctx;
831 	void		*dp;
832 	int		i;
833 
834 	/* group data */
835 	if (_nscd_cfg_flag_is_set(dflag, NSCD_CFG_DFLAG_GROUP)) {
836 		if (_nscd_cfg_flag_is_set(pdesc->pflag,
837 		    NSCD_CFG_PFLAG_GLOBAL)) {
838 			/* global config */
839 			global_cfg = *(nscd_cfg_global_cache_t *)data;
840 		} else if (_nscd_cfg_flag_is_set(dflag,
841 		    NSCD_CFG_DFLAG_SET_ALL_DB)) {
842 			/* non-global config for all dbs */
843 			for (i = 0; i < CACHE_CTX_COUNT; i++) {
844 				ctx = cache_ctx_p[i];
845 				if (ctx == NULL)
846 					return (NSCD_CTX_NOT_FOUND);
847 				(void) rw_wrlock(&ctx->cfg_rwlp);
848 				ctx->cfg = *(nscd_cfg_cache_t *)data;
849 				ctx->cfg_mtime = time(NULL);
850 				(void) rw_unlock(&ctx->cfg_rwlp);
851 			}
852 		} else {
853 			/* non-global config for a specific db */
854 
855 			/* ignore non-caching databases */
856 			if (get_cache_ctx(nswdb->name, &ctx) != NSCD_SUCCESS)
857 				return (NSCD_SUCCESS);
858 			(void) rw_wrlock(&ctx->cfg_rwlp);
859 			ctx->cfg = *(nscd_cfg_cache_t *)data;
860 			ctx->cfg_mtime = time(NULL);
861 			(void) rw_unlock(&ctx->cfg_rwlp);
862 		}
863 		return (NSCD_SUCCESS);
864 	}
865 
866 	/* individual data */
867 	if (_nscd_cfg_flag_is_set(pdesc->pflag, NSCD_CFG_PFLAG_GLOBAL)) {
868 		/* global config */
869 		dp = (char *)&global_cfg + pdesc->p_offset;
870 		(void) memcpy(dp, data, pdesc->p_size);
871 	} else if (_nscd_cfg_flag_is_set(dflag,
872 	    NSCD_CFG_DFLAG_SET_ALL_DB)) {
873 		/* non-global config for all dbs */
874 		for (i = 0; i < CACHE_CTX_COUNT; i++) {
875 			ctx = cache_ctx_p[i];
876 			if (ctx == NULL)
877 				return (NSCD_CTX_NOT_FOUND);
878 			dp = (char *)&ctx->cfg + pdesc->p_offset;
879 			(void) rw_wrlock(&ctx->cfg_rwlp);
880 			(void) memcpy(dp, data, pdesc->p_size);
881 			ctx->cfg_mtime = time(NULL);
882 			(void) rw_unlock(&ctx->cfg_rwlp);
883 		}
884 	} else {
885 		/* non-global config for a specific db */
886 
887 		/* ignore non-caching databases */
888 		if (get_cache_ctx(nswdb->name, &ctx) != NSCD_SUCCESS)
889 			return (NSCD_SUCCESS);
890 		dp = (char *)&ctx->cfg + pdesc->p_offset;
891 		(void) rw_wrlock(&ctx->cfg_rwlp);
892 		(void) memcpy(dp, data, pdesc->p_size);
893 		ctx->cfg_mtime = time(NULL);
894 		(void) rw_unlock(&ctx->cfg_rwlp);
895 	}
896 	return (NSCD_SUCCESS);
897 }
898 
899 
900 /*
901  * get stat
902  */
903 /* ARGSUSED */
904 nscd_rc_t
905 _nscd_cfg_cache_get_stat(
906 	void				**stat,
907 	struct nscd_cfg_stat_desc	*sdesc,
908 	nscd_cfg_id_t			*nswdb,
909 	nscd_cfg_flag_t			*dflag,
910 	void				(**free_stat)(void *stat),
911 	nscd_cfg_error_t		**errorp)
912 {
913 	nscd_cfg_stat_cache_t	*statsp, stats;
914 	nsc_ctx_t		*ctx;
915 	int			i;
916 	nscd_rc_t		rc;
917 
918 	statsp = calloc(1, sizeof (*statsp));
919 	if (statsp == NULL)
920 		return (NSCD_NO_MEMORY);
921 
922 	if (_nscd_cfg_flag_is_set(sdesc->sflag, NSCD_CFG_SFLAG_GLOBAL)) {
923 		for (i = 0; i < CACHE_CTX_COUNT; i++) {
924 			if (cache_ctx_p[i] == NULL)
925 				stats = null_stats;
926 			else {
927 				(void) mutex_lock(&cache_ctx_p[i]->stats_mutex);
928 				stats = cache_ctx_p[i]->stats;
929 				(void) mutex_unlock(
930 				    &cache_ctx_p[i]->stats_mutex);
931 			}
932 			statsp->pos_hits += stats.pos_hits;
933 			statsp->neg_hits += stats.neg_hits;
934 			statsp->pos_misses += stats.pos_misses;
935 			statsp->neg_misses += stats.neg_misses;
936 			statsp->entries += stats.entries;
937 			statsp->drop_count += stats.drop_count;
938 			statsp->wait_count += stats.wait_count;
939 			statsp->invalidate_count +=
940 			    stats.invalidate_count;
941 		}
942 	} else {
943 		if ((rc = get_cache_ctx(nswdb->name, &ctx)) != NSCD_SUCCESS) {
944 			free(statsp);
945 			return (rc);
946 		}
947 		(void) mutex_lock(&ctx->stats_mutex);
948 		*statsp = ctx->stats;
949 		(void) mutex_unlock(&ctx->stats_mutex);
950 	}
951 
952 	_NSC_GET_HITRATE(statsp);
953 	*stat = statsp;
954 	return (NSCD_SUCCESS);
955 }
956 
957 /*
958  * This function should only be called when nscd is
959  * not a daemon.
960  */
961 void
962 nsc_info(nsc_ctx_t *ctx, char *dbname, nscd_cfg_cache_t cfg[],
963     nscd_cfg_stat_cache_t stats[])
964 {
965 	int		i;
966 	char		*me = "nsc_info";
967 	nsc_ctx_t	*ctx1;
968 	nsc_ctx_t	ctx2;
969 	nscd_rc_t	rc;
970 
971 	if (ctx) {
972 		ctx_info(ctx);
973 		return;
974 	}
975 
976 	if (dbname) {
977 		rc = get_cache_ctx(dbname, &ctx1);
978 		if (rc == NSCD_INVALID_ARGUMENT) {
979 			_NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_WARNING)
980 			(me, "%s: no cache context found\n", dbname);
981 			return;
982 		} else if (rc == NSCD_NO_MEMORY) {
983 			_NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_WARNING)
984 	(me, "%s: unable to create cache context - no memory\n",
985 	    dbname);
986 			return;
987 		}
988 		ctx_info(ctx1);
989 		return;
990 	}
991 
992 	if (cfg == NULL || stats == NULL)
993 		return;
994 
995 	for (i = 0; i < CACHE_CTX_COUNT; i++) {
996 
997 		ctx2.dbname = cache_name[i];
998 		ctx2.cfg = cfg[i];
999 		ctx2.stats = stats[i];
1000 		ctx_info_nolock(&ctx2);
1001 	}
1002 }
1003 
1004 static void
1005 ctx_info_nolock(nsc_ctx_t *ctx) {
1006 	nscd_cfg_cache_t	cfg;
1007 	nscd_cfg_stat_cache_t	stats;
1008 
1009 	cfg = ctx->cfg;
1010 	(void) fprintf(stdout, gettext("\n\nCACHE: %s\n"), ctx->dbname);
1011 	(void) print_cfg(&cfg);
1012 
1013 	if (cfg.enable == nscd_false)
1014 		return;
1015 
1016 	stats = ctx->stats;
1017 	(void) print_stats(&stats);
1018 }
1019 
1020 static void
1021 ctx_info(nsc_ctx_t *ctx) {
1022 	nscd_cfg_cache_t	cfg;
1023 	nscd_cfg_stat_cache_t	stats;
1024 
1025 	(void) rw_rdlock(&ctx->cfg_rwlp);
1026 	cfg = ctx->cfg;
1027 	(void) rw_unlock(&ctx->cfg_rwlp);
1028 	(void) fprintf(stdout, gettext("\n\nCACHE: %s\n"), ctx->dbname);
1029 	(void) print_cfg(&cfg);
1030 
1031 	if (cfg.enable == nscd_false)
1032 		return;
1033 
1034 	(void) mutex_lock(&ctx->stats_mutex);
1035 	stats = ctx->stats;
1036 	(void) mutex_unlock(&ctx->stats_mutex);
1037 	(void) print_stats(&stats);
1038 }
1039 
1040 #ifdef	NSCD_DEBUG
1041 /*
1042  * This function should only be called when nscd is
1043  * not a daemon.
1044  */
1045 int
1046 nsc_dump(char *dbname, int dbop)
1047 {
1048 	nsc_ctx_t	*ctx;
1049 	nsc_db_t	*nscdb;
1050 	nscd_bool_t	enabled;
1051 	time_t		now;
1052 	char		*me = "nsc_dump";
1053 	int		i;
1054 
1055 	if ((i = get_cache_idx(dbname)) == -1) {
1056 		(void) fprintf(stdout, gettext("invalid cache name\n"));
1057 
1058 		_NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_WARNING)
1059 		(me, "%s: invalid cache name\n", dbname);
1060 		return (NSCD_CACHE_INVALID_CACHE_NAME);
1061 	}
1062 
1063 	if ((ctx = cache_ctx_p[i]) == NULL)  {
1064 		(void) fprintf(stdout, gettext("no cache context\n"));
1065 
1066 		_NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_WARNING)
1067 		(me, "%s: no cache context\n", dbname);
1068 		return (NSCD_CACHE_NO_CACHE_CTX);
1069 	}
1070 
1071 	now = time(NULL);
1072 	(void) rw_rdlock(&ctx->cfg_rwlp);
1073 	enabled = ctx->cfg.enable;
1074 	(void) rw_unlock(&ctx->cfg_rwlp);
1075 
1076 	if (enabled == nscd_false)
1077 		return (NSCD_CACHE_DISABLED);
1078 
1079 	nscdb = nsc_get_db(ctx, dbop);
1080 	if (nscdb == NULL) {
1081 		_NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_WARNING)
1082 		(me, "%s:%d: no cache found\n", dbname, dbop);
1083 		return (NSCD_CACHE_NO_CACHE_FOUND);
1084 	}
1085 
1086 	(void) mutex_lock(&nscdb->db_mutex);
1087 	(void) queue_dump(nscdb, now);
1088 	(void) hash_dump(nscdb, now);
1089 	(void) avl_dump(nscdb, now);
1090 	(void) mutex_unlock(&nscdb->db_mutex);
1091 	return (NSCD_SUCCESS);
1092 }
1093 #endif	/* NSCD_DEBUG */
1094 
1095 /*
1096  * These macros are for exclusive use of nsc_lookup
1097  */
1098 #define	NSC_LOOKUP_LOG(loglevel, fmt) \
1099 	_NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_##loglevel) \
1100 		(me, fmt, whoami);
1101 
1102 static int
1103 nsc_lookup_no_cache(nsc_lookup_args_t *largs, const char *str)
1104 {
1105 	char *me = "nsc_lookup_no_cache";
1106 	nss_status_t status;
1107 
1108 	_NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_DEBUG)
1109 		(me, "%s: name service lookup (bypassing cache)\n", str);
1110 	nss_psearch(largs->buffer, largs->bufsize);
1111 	status = NSCD_GET_STATUS(largs->buffer);
1112 	_NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_DEBUG)
1113 		(me, "%s: name service lookup status = %d\n", str, status);
1114 	if (status == NSS_SUCCESS) {
1115 		return (SUCCESS);
1116 	} else if (status == NSS_NOTFOUND) {
1117 		return (NOTFOUND);
1118 	} else {
1119 		return (SERVERERROR);
1120 	}
1121 }
1122 
1123 /*
1124  * This function starts the revalidation and reaper threads
1125  * for a cache
1126  */
1127 static void
1128 start_threads(nsc_ctx_t *ctx)
1129 {
1130 	int	errnum;
1131 	char	*me = "start_threads";
1132 
1133 	/*
1134 	 *  kick off the revalidate thread (if necessary)
1135 	 */
1136 	if (ctx->revalidate_on != nscd_true) {
1137 		if (thr_create(NULL, NULL, (void *(*)(void *))revalidate,
1138 		    ctx, 0, NULL) != 0) {
1139 			errnum = errno;
1140 			_NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_ERROR)
1141 			(me, "thr_create (revalidate thread for %s): %s\n",
1142 			    ctx->dbname, strerror(errnum));
1143 			exit(1);
1144 		}
1145 		ctx->revalidate_on = nscd_true;
1146 	}
1147 
1148 	/*
1149 	 *  kick off the reaper thread (if necessary)
1150 	 */
1151 	if (ctx->reaper_on != nscd_true) {
1152 		if (thr_create(NULL, NULL, (void *(*)(void *))reaper,
1153 		    ctx, 0, NULL) != 0) {
1154 			errnum = errno;
1155 			_NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_ERROR)
1156 			(me, "thr_create (reaper thread for %s): %s\n",
1157 			    ctx->dbname, strerror(errnum));
1158 			exit(1);
1159 		}
1160 		ctx->reaper_on = nscd_true;
1161 	}
1162 }
1163 
1164 /*
1165  * Examine the packed buffer, see if the front-end parameters
1166  * indicate that the caller specified nsswitch config should be
1167  * used for the lookup. Return 1 if yes, otherwise 0.
1168  */
1169 static int
1170 nsw_config_in_phdr(void *buf)
1171 {
1172 	nss_pheader_t		*pbuf = (nss_pheader_t *)buf;
1173 	nssuint_t		off;
1174 	nss_dbd_t		*pdbd;
1175 	char			*me = "nsw_config_in_phdr";
1176 
1177 	off = pbuf->dbd_off;
1178 	if (off == 0)
1179 		return (0);
1180 	pdbd = (nss_dbd_t *)((void *)((char *)pbuf + off));
1181 	if (pdbd->o_default_config == 0)
1182 		return (0);
1183 
1184 	if ((enum nss_dbp_flags)pdbd->flags & NSS_USE_DEFAULT_CONFIG) {
1185 		_NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_DEBUG)
1186 		(me, "use caller specified nsswitch config\n");
1187 		return (1);
1188 	} else
1189 		return (0);
1190 }
1191 
1192 static nss_status_t
1193 copy_result(void *rbuf, void *cbuf)
1194 {
1195 	nss_pheader_t	*rphdr = (nss_pheader_t *)rbuf;
1196 	nss_pheader_t	*cphdr = (nss_pheader_t *)cbuf;
1197 	char		*me = "copy_result";
1198 
1199 	/* return NSS_ERROR if not enough room to copy result */
1200 	if (cphdr->data_len + 1 > rphdr->data_len) {
1201 		NSCD_SET_STATUS(rphdr, NSS_ERROR, ERANGE);
1202 		return (NSS_ERROR);
1203 	} else {
1204 		char	*dst;
1205 
1206 		if (cphdr->data_len == 0)
1207 			return (NSS_SUCCESS);
1208 
1209 		dst = (char *)rphdr + rphdr->data_off;
1210 		(void) memcpy(dst, (char *)cphdr + cphdr->data_off,
1211 		    cphdr->data_len);
1212 		rphdr->data_len = cphdr->data_len;
1213 		/* some frontend code expects a terminating NULL char */
1214 		*(dst + rphdr->data_len) = '\0';
1215 
1216 		_NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_DEBUG)
1217 		(me, "cache data (len = %lld): >>%s<<\n",
1218 		    cphdr->data_len, (char *)cphdr + cphdr->data_off);
1219 
1220 		return (NSS_SUCCESS);
1221 	}
1222 }
1223 
1224 static int
1225 get_dns_ttl(void *pbuf, char *dbname)
1226 {
1227 	nss_pheader_t	*phdr = (nss_pheader_t *)pbuf;
1228 	int		ttl;
1229 	char		*me = "get_dns_ttl";
1230 
1231 	/* if returned, dns ttl is stored in the extended data area */
1232 	if (phdr->ext_off == 0)
1233 		return (-1);
1234 
1235 	if (strcmp(dbname, NSS_DBNAM_HOSTS) != 0 &&
1236 	    strcmp(dbname, NSS_DBNAM_IPNODES) != 0)
1237 		return (-1);
1238 
1239 	ttl = *(nssuint_t *)((void *)((char *)pbuf + phdr->ext_off));
1240 
1241 	_NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_DEBUG)
1242 	(me, "dns ttl is %d seconds\n", ttl);
1243 
1244 	return (ttl);
1245 }
1246 
1247 static int
1248 check_config(nsc_lookup_args_t *largs, nscd_cfg_cache_t *cfgp,
1249     char *whoami, int flag)
1250 {
1251 	nsc_db_t	*nscdb;
1252 	nsc_ctx_t	*ctx;
1253 	char		*me = "check_config";
1254 
1255 	ctx = largs->ctx;
1256 	nscdb = largs->nscdb;
1257 
1258 	/* see if the cached config needs update */
1259 	if (nscdb->cfg_mtime != ctx->cfg_mtime) {
1260 		(void) rw_rdlock(&ctx->cfg_rwlp);
1261 		nscdb->cfg = ctx->cfg;
1262 		nscdb->cfg_mtime = ctx->cfg_mtime;
1263 		(void) rw_unlock(&ctx->cfg_rwlp);
1264 		_NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_DEBUG)
1265 		(me, "config for context %s, database %s updated\n",
1266 		    ctx->dbname, nscdb->name);
1267 	}
1268 	*cfgp = nscdb->cfg;
1269 
1270 	if (cfgp->enable == nscd_false) {
1271 		_NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_DEBUG)
1272 			(me, "%s: cache disabled\n", ctx->dbname);
1273 
1274 		if (UPDATEBIT & flag)
1275 			return (NOTFOUND);
1276 		else
1277 			return (nsc_lookup_no_cache(largs, whoami));
1278 	}
1279 
1280 	/*
1281 	 * if caller requests lookup using its
1282 	 * own nsswitch config, bypass cache
1283 	 */
1284 	if (nsw_config_in_phdr(largs->buffer))
1285 		return (nsc_lookup_no_cache(largs, whoami));
1286 
1287 	/* no need of cache if we are dealing with 0 ttls */
1288 	if (cfgp->pos_ttl <= 0 && cfgp->neg_ttl <= 0) {
1289 		if (flag & UPDATEBIT)
1290 			return (NOTFOUND);
1291 		else if (cfgp->avoid_ns == nscd_true)
1292 			return (SERVERERROR);
1293 		return (nsc_lookup_no_cache(largs, whoami));
1294 	}
1295 
1296 	return (CONTINUE);
1297 }
1298 
1299 /*
1300  * Invalidate cache if database file has been modified.
1301  * See check_files config param for details.
1302  */
1303 static void
1304 check_db_file(nsc_ctx_t *ctx, nscd_cfg_cache_t cfg,
1305     char *whoami, time_t now)
1306 {
1307 	struct stat	buf;
1308 	nscd_bool_t	file_modified = nscd_false;
1309 	char		*me = "check_db_file";
1310 
1311 	if (cfg.check_interval != 0 &&
1312 	    (now - ctx->file_chktime) < cfg.check_interval)
1313 		return;
1314 
1315 	ctx->file_chktime = now;
1316 	if (stat(ctx->file_name, &buf) == 0) {
1317 		if (ctx->file_mtime == 0) {
1318 			(void) mutex_lock(&ctx->file_mutex);
1319 			if (ctx->file_mtime == 0) {
1320 				ctx->file_mtime = buf.st_mtime;
1321 				ctx->file_size = buf.st_size;
1322 				ctx->file_ino = buf.st_ino;
1323 			}
1324 			(void) mutex_unlock(&ctx->file_mutex);
1325 		} else if (ctx->file_mtime < buf.st_mtime ||
1326 		    ctx->file_size != buf.st_size ||
1327 		    ctx->file_ino != buf.st_ino) {
1328 			(void) mutex_lock(&ctx->file_mutex);
1329 			if (ctx->file_mtime < buf.st_mtime ||
1330 			    ctx->file_size != buf.st_size ||
1331 			    ctx->file_ino != buf.st_ino) {
1332 				file_modified = nscd_true;
1333 				ctx->file_mtime = buf.st_mtime;
1334 				ctx->file_size = buf.st_size;
1335 				ctx->file_ino = buf.st_ino;
1336 			}
1337 			(void) mutex_unlock(&ctx->file_mutex);
1338 		}
1339 	}
1340 
1341 	if (file_modified == nscd_true) {
1342 		_NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_DEBUG)
1343 		(me, "%s: file %s has been modified - invalidating cache\n",
1344 		    whoami, ctx->file_name);
1345 		ctx_invalidate(ctx);
1346 	}
1347 }
1348 
1349 static int
1350 lookup_int(nsc_lookup_args_t *largs, int flag)
1351 {
1352 	nsc_ctx_t		*ctx;
1353 	nsc_db_t		*nscdb;
1354 	nscd_cfg_cache_t	cfg;
1355 	nsc_entry_t		*this_entry;
1356 	nsc_entry_stat_t	*this_stats;
1357 	nsc_action_t		next_action;
1358 	nss_status_t		status;
1359 	nscd_bool_t		delete;
1360 	nscd_rc_t		rc;
1361 	char			*dbname;
1362 	int			dbop, errnum;
1363 	int			cfg_rc;
1364 	nss_XbyY_args_t		args;
1365 	char			whoami[128];
1366 	time_t			now = time(NULL); /* current time */
1367 	char			*me = "lookup_int";
1368 
1369 	/* extract dbop, dbname, key and cred */
1370 	status = nss_packed_getkey(largs->buffer, largs->bufsize, &dbname,
1371 	    &dbop, &args);
1372 	if (status != NSS_SUCCESS) {
1373 		_NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_ERROR)
1374 			(me, "nss_packed_getkey failure (%d)\n", status);
1375 		return (SERVERERROR);
1376 	}
1377 
1378 	/* get the cache context */
1379 	if (largs->ctx == NULL) {
1380 		if (get_cache_ctx(dbname, &largs->ctx) != NSCD_SUCCESS) {
1381 			_NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_WARNING)
1382 				(me, "%s: no cache context found\n", dbname);
1383 
1384 			if (UPDATEBIT & flag)
1385 				return (NOTFOUND);
1386 			else
1387 				return (nsc_lookup_no_cache(largs, dbname));
1388 		}
1389 	}
1390 	ctx = largs->ctx;
1391 
1392 	if (largs->nscdb == NULL) {
1393 		if ((largs->nscdb = nsc_get_db(ctx, dbop)) == NULL) {
1394 			_NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_WARNING)
1395 				(me, "%s:%d: no cache found\n",
1396 				    dbname, dbop);
1397 
1398 			if (UPDATEBIT & flag)
1399 				return (NOTFOUND);
1400 			else
1401 				return (nsc_lookup_no_cache(largs, dbname));
1402 		}
1403 	}
1404 
1405 	nscdb = largs->nscdb;
1406 
1407 	_NSCD_LOG_IF(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_ALL) {
1408 		(void) nscdb->getlogstr(nscdb->name, whoami,
1409 		    sizeof (whoami), &args);
1410 	}
1411 
1412 	if (UPDATEBIT & flag) {
1413 		_NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_DEBUG)
1414 			(me, "%s: refresh start\n", whoami);
1415 	} else {
1416 		_NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_DEBUG)
1417 			(me, "%s: lookup start\n", whoami);
1418 	}
1419 
1420 	cfg_rc = check_config(largs, &cfg, whoami, flag);
1421 	if (cfg_rc != CONTINUE)
1422 		return (cfg_rc);
1423 
1424 	/*
1425 	 * Invalidate cache if file has been modified.
1426 	 */
1427 	if (cfg.check_files == nscd_true)
1428 		check_db_file(ctx, cfg, whoami, now);
1429 
1430 	(void) mutex_lock(&nscdb->db_mutex);
1431 
1432 	/* Lookup the cache table */
1433 	for (;;) {
1434 		delete = nscd_false;
1435 		rc = lookup_cache(largs, &cfg, &args, whoami, &this_entry);
1436 		if (rc != NSCD_SUCCESS) {
1437 			(void) mutex_unlock(&nscdb->db_mutex);
1438 
1439 			/* Either no entry and avoid name service */
1440 			if (rc == NSCD_DB_ENTRY_NOT_FOUND ||
1441 			    rc == NSCD_INVALID_ARGUMENT)
1442 				return (NOTFOUND);
1443 
1444 			/* OR memory error */
1445 			return (SERVERERROR);
1446 		}
1447 
1448 		/* get the stats from the entry */
1449 		this_stats = &this_entry->stats;
1450 
1451 		/*
1452 		 * What should we do next ?
1453 		 */
1454 		switch (this_stats->status) {
1455 		case ST_NEW_ENTRY:
1456 			delete = nscd_true;
1457 			next_action = _NSC_NSLOOKUP;
1458 			break;
1459 		case ST_UPDATE_PENDING:
1460 			if (flag & UPDATEBIT) {
1461 				(void) mutex_unlock(&nscdb->db_mutex);
1462 				return (NOTFOUND);
1463 			} else if (this_stats->timestamp < now)
1464 				next_action = _NSC_WAIT;
1465 			else
1466 				next_action = _NSC_USECACHED;
1467 			break;
1468 		case ST_LOOKUP_PENDING:
1469 			if (flag & UPDATEBIT) {
1470 				(void) mutex_unlock(&nscdb->db_mutex);
1471 				return (NOTFOUND);
1472 			}
1473 			next_action = _NSC_WAIT;
1474 			break;
1475 		case ST_DISCARD:
1476 			if (cfg.avoid_ns == nscd_true) {
1477 				(void) mutex_unlock(&nscdb->db_mutex);
1478 				return (NOTFOUND);
1479 			}
1480 			/* otherwise reuse the entry */
1481 			(void) memset(this_stats, 0, sizeof (*this_stats));
1482 			next_action = _NSC_NSLOOKUP;
1483 			break;
1484 		default:
1485 			if (cfg.avoid_ns == nscd_true)
1486 				next_action = _NSC_USECACHED;
1487 			else if ((flag & UPDATEBIT) ||
1488 			    (this_stats->timestamp < now)) {
1489 				_NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_DEBUG)
1490 			(me, "%s: cached entry needs to be updated\n",
1491 			    whoami);
1492 				next_action = _NSC_NSLOOKUP;
1493 			} else
1494 				next_action = _NSC_USECACHED;
1495 			break;
1496 		}
1497 
1498 		if (next_action == _NSC_WAIT) {
1499 			_NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_DEBUG)
1500 			(me, "%s: need to wait\n", whoami);
1501 
1502 			/* do we have clearance ? */
1503 			if (_nscd_get_clearance(&ctx->throttle_sema) != 0) {
1504 				/* nope. quit */
1505 				(void) mutex_lock(&ctx->stats_mutex);
1506 				ctx->stats.drop_count++;
1507 				(void) mutex_unlock(&ctx->stats_mutex);
1508 				_NSCD_LOG(NSCD_LOG_CACHE,
1509 				    NSCD_LOG_LEVEL_DEBUG_6)
1510 				(me, "%s: throttling load\n", whoami);
1511 				(void) mutex_unlock(&nscdb->db_mutex);
1512 				NSC_LOOKUP_LOG(WARNING,
1513 				    "%s: no clearance to wait\n");
1514 				return (NOSERVER);
1515 			}
1516 			/* yes can wait */
1517 			(void) nscd_wait(ctx, nscdb, this_entry);
1518 			(void) _nscd_release_clearance(&ctx->throttle_sema);
1519 			continue;
1520 		}
1521 
1522 		break;
1523 	}
1524 
1525 
1526 	if (!(UPDATEBIT & flag))
1527 		this_stats->hits++;		/* update hit count */
1528 
1529 	if (next_action == _NSC_NSLOOKUP) {
1530 
1531 		_NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_DEBUG)
1532 		(me, "%s: name service lookup required\n", whoami);
1533 
1534 		if (_nscd_get_clearance(&ctx->throttle_sema) != 0) {
1535 			if (delete == nscd_true)
1536 				delete_entry(nscdb, ctx, this_entry);
1537 			else
1538 				this_stats->status = ST_DISCARD;
1539 			(void) mutex_lock(&ctx->stats_mutex);
1540 			ctx->stats.drop_count++;
1541 			(void) mutex_unlock(&ctx->stats_mutex);
1542 			(void) mutex_unlock(&nscdb->db_mutex);
1543 			NSC_LOOKUP_LOG(WARNING,
1544 			    "%s: no clearance for lookup\n");
1545 			return (NOSERVER);
1546 		}
1547 
1548 		/* block any threads accessing this entry */
1549 		this_stats->status = (flag & UPDATEBIT) ?
1550 		    ST_UPDATE_PENDING : ST_LOOKUP_PENDING;
1551 
1552 		/* release lock and do name service lookup */
1553 		(void) mutex_unlock(&nscdb->db_mutex);
1554 		nss_psearch(largs->buffer, largs->bufsize);
1555 		status = NSCD_GET_STATUS(largs->buffer);
1556 		(void) mutex_lock(&nscdb->db_mutex);
1557 		this_stats->status = 0;
1558 		(void) _nscd_release_clearance(&ctx->throttle_sema);
1559 
1560 		/* signal waiting threads */
1561 		(void) nscd_signal(ctx, nscdb, this_entry);
1562 
1563 		_NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_DEBUG)
1564 		(me, "%s: name service lookup status = %d\n",
1565 		    whoami, status);
1566 
1567 		if (status == NSS_SUCCESS) {
1568 			int ttl;
1569 
1570 			/*
1571 			 * data found in name service
1572 			 * update cache
1573 			 */
1574 			status = dup_packed_buffer(largs, this_entry);
1575 			if (status != NSS_SUCCESS) {
1576 				delete_entry(nscdb, ctx, this_entry);
1577 				(void) mutex_unlock(&nscdb->db_mutex);
1578 				NSC_LOOKUP_LOG(ERROR,
1579 				    "%s: failed to update cache\n");
1580 				return (SERVERERROR);
1581 			}
1582 
1583 			/*
1584 			 * store unpacked key in cache
1585 			 */
1586 			status = nss_packed_getkey(this_entry->buffer,
1587 			    this_entry->bufsize,
1588 			    &dbname, &dbop, &args);
1589 			if (status != NSS_SUCCESS) {
1590 				delete_entry(nscdb, ctx, this_entry);
1591 				(void) mutex_unlock(&nscdb->db_mutex);
1592 				NSC_LOOKUP_LOG(ERROR,
1593 				    "%s: failed to extract key\n");
1594 				return (SERVERERROR);
1595 			}
1596 			this_entry->key = args.key; /* struct copy */
1597 
1598 			/* update +ve miss count */
1599 			if (!(UPDATEBIT & flag)) {
1600 				(void) mutex_lock(&ctx->stats_mutex);
1601 				ctx->stats.pos_misses++;
1602 				(void) mutex_unlock(&ctx->stats_mutex);
1603 			}
1604 
1605 			/* update +ve ttl */
1606 			ttl = get_dns_ttl(largs->buffer, dbname);
1607 			/* honor the dns ttl less than postive ttl */
1608 			if (ttl < 0 || ttl > cfg.pos_ttl)
1609 				ttl = cfg.pos_ttl;
1610 			this_stats->timestamp = time(NULL) + ttl;
1611 
1612 			/*
1613 			 * start the revalidation and reaper threads
1614 			 * if not already started
1615 			 */
1616 			start_threads(ctx);
1617 
1618 			(void) mutex_unlock(&nscdb->db_mutex);
1619 			NSC_LOOKUP_LOG(DEBUG,
1620 			    "%s: cache updated with positive entry\n");
1621 			return (SUCCESS);
1622 		} else if (status == NSS_NOTFOUND) {
1623 			/*
1624 			 * data not found in name service
1625 			 * update cache
1626 			 */
1627 			_NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_DEBUG_6)
1628 			(me, "%s: name service lookup failed\n", whoami);
1629 
1630 			if (NSCD_GET_ERRNO(largs->buffer) == ERANGE) {
1631 				delete_entry(nscdb, ctx, this_entry);
1632 				(void) mutex_unlock(&nscdb->db_mutex);
1633 				NSC_LOOKUP_LOG(DEBUG,
1634 				    "%s: ERANGE, cache not updated "
1635 				    "with negative entry\n");
1636 				return (NOTFOUND);
1637 			}
1638 
1639 			status = dup_packed_buffer(largs, this_entry);
1640 			if (status != NSS_SUCCESS) {
1641 				delete_entry(nscdb, ctx, this_entry);
1642 				(void) mutex_unlock(&nscdb->db_mutex);
1643 				NSC_LOOKUP_LOG(ERROR,
1644 				    "%s: failed to update cache\n");
1645 				return (SERVERERROR);
1646 			}
1647 
1648 			/* store unpacked key in cache */
1649 			status = nss_packed_getkey(this_entry->buffer,
1650 			    this_entry->bufsize,
1651 			    &dbname, &dbop, &args);
1652 			if (status != NSS_SUCCESS) {
1653 				delete_entry(nscdb, ctx, this_entry);
1654 				(void) mutex_unlock(&nscdb->db_mutex);
1655 				NSC_LOOKUP_LOG(ERROR,
1656 				    "%s: failed to extract key\n");
1657 				return (SERVERERROR);
1658 			}
1659 			this_entry->key = args.key; /* struct copy */
1660 
1661 			/* update -ve ttl */
1662 			this_stats->timestamp = time(NULL) + cfg.neg_ttl;
1663 
1664 			/* update -ve miss count */
1665 			if (!(UPDATEBIT & flag)) {
1666 				(void) mutex_lock(&ctx->stats_mutex);
1667 				ctx->stats.neg_misses++;
1668 				(void) mutex_unlock(&ctx->stats_mutex);
1669 			}
1670 
1671 			/*
1672 			 * start the revalidation and reaper threads
1673 			 * if not already started
1674 			 */
1675 			start_threads(ctx);
1676 
1677 			(void) mutex_unlock(&nscdb->db_mutex);
1678 			NSC_LOOKUP_LOG(DEBUG,
1679 			    "%s: cache updated with negative entry\n");
1680 			return (NOTFOUND);
1681 		} else {
1682 			/*
1683 			 * name service lookup failed
1684 			 */
1685 			_NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_DEBUG_6)
1686 			(me, "%s: name service lookup failed\n", whoami);
1687 
1688 			errnum = NSCD_GET_ERRNO(largs->buffer);
1689 			if (delete == nscd_true)
1690 				delete_entry(nscdb, ctx, this_entry);
1691 			else
1692 				this_stats->status = ST_DISCARD;
1693 
1694 			(void) mutex_unlock(&nscdb->db_mutex);
1695 			_NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_WARNING)
1696 			(me, "%s: name service lookup failed "
1697 			    "(status=%d, errno=%d)\n",
1698 			    whoami, status, errnum);
1699 
1700 			return (SERVERERROR);
1701 		}
1702 	} else if (next_action == _NSC_USECACHED) {
1703 		/*
1704 		 * found entry in cache
1705 		 */
1706 		if (UPDATEBIT & flag) {
1707 			(void) mutex_unlock(&nscdb->db_mutex);
1708 			NSC_LOOKUP_LOG(DEBUG, "%s: no need to update\n");
1709 			return (SUCCESS);
1710 		}
1711 
1712 		if (NSCD_GET_STATUS((nss_pheader_t *)this_entry->buffer) ==
1713 		    NSS_SUCCESS) {
1714 			/* positive hit */
1715 			(void) mutex_lock(&ctx->stats_mutex);
1716 			ctx->stats.pos_hits++;
1717 			(void) mutex_unlock(&ctx->stats_mutex);
1718 
1719 			/* update response buffer */
1720 			if (copy_result(largs->buffer,
1721 			    this_entry->buffer) != NSS_SUCCESS) {
1722 				(void) mutex_unlock(&nscdb->db_mutex);
1723 				NSC_LOOKUP_LOG(ERROR,
1724 				    "%s: response buffer insufficient\n");
1725 				return (SERVERERROR);
1726 			}
1727 
1728 			(void) mutex_unlock(&nscdb->db_mutex);
1729 			NSC_LOOKUP_LOG(DEBUG,
1730 			    "%s: positive entry in cache\n");
1731 			return (SUCCESS);
1732 		} else {
1733 			/* negative hit */
1734 			(void) mutex_lock(&ctx->stats_mutex);
1735 			ctx->stats.neg_hits++;
1736 			(void) mutex_unlock(&ctx->stats_mutex);
1737 
1738 			NSCD_SET_STATUS((nss_pheader_t *)largs->buffer,
1739 			    NSCD_GET_STATUS(this_entry->buffer),
1740 			    NSCD_GET_ERRNO(this_entry->buffer));
1741 			NSCD_SET_HERRNO((nss_pheader_t *)largs->buffer,
1742 			    NSCD_GET_HERRNO(this_entry->buffer));
1743 
1744 			(void) mutex_unlock(&nscdb->db_mutex);
1745 			NSC_LOOKUP_LOG(DEBUG,
1746 			    "%s: negative entry in cache\n");
1747 			return (NOTFOUND);
1748 		}
1749 	}
1750 
1751 	(void) mutex_unlock(&nscdb->db_mutex);
1752 	NSC_LOOKUP_LOG(ERROR, "%s: cache backend failure\n");
1753 	return (SERVERERROR);
1754 }
1755 
1756 /*
1757  * NSCD cache backend lookup function
1758  */
1759 /*ARGSUSED*/
1760 void
1761 nsc_lookup(nsc_lookup_args_t *largs, int flag) {
1762 
1763 	nss_pheader_t	*phdr = (nss_pheader_t *)largs->buffer;
1764 	int		rc;
1765 
1766 	rc = lookup_int(largs, 0);
1767 
1768 	if (NSCD_GET_STATUS(phdr) == NSS_TRYLOCAL)
1769 		return;
1770 
1771 	switch (rc) {
1772 
1773 	case SUCCESS:
1774 		NSCD_SET_STATUS(phdr, NSS_SUCCESS, 0);
1775 		break;
1776 
1777 	case NOTFOUND:
1778 		NSCD_SET_STATUS(phdr, NSS_NOTFOUND, -1);
1779 		break;
1780 
1781 	case SERVERERROR:
1782 		/*
1783 		 * status and errno should have been set in the phdr,
1784 		 * if not, set status to NSS_ERROR
1785 		 */
1786 		if (NSCD_STATUS_IS_OK(phdr)) {
1787 			NSCD_SET_STATUS(phdr, NSS_ERROR, 0);
1788 		}
1789 		break;
1790 
1791 	case NOSERVER:
1792 		NSCD_SET_STATUS(phdr, NSS_TRYLOCAL, -1);
1793 		break;
1794 	}
1795 }
1796 
1797 
1798 static nsc_ctx_t *
1799 init_cache_ctx(int i) {
1800 	nsc_ctx_t	*ctx;
1801 
1802 	ctx = calloc(1, sizeof (nsc_ctx_t));
1803 	if (ctx == NULL)
1804 		return (NULL);
1805 
1806 	/* init locks and semaphores */
1807 	(void) mutex_init(&ctx->file_mutex, USYNC_THREAD, NULL);
1808 	(void) rwlock_init(&ctx->cfg_rwlp, USYNC_THREAD, NULL);
1809 	(void) mutex_init(&ctx->stats_mutex, USYNC_THREAD, NULL);
1810 	(void) _nscd_init_cache_sema(&ctx->throttle_sema, cache_name[i]);
1811 	cache_init_ctx[i](ctx);
1812 	cache_ctx_p[i] = ctx;
1813 
1814 	return (ctx);
1815 }
1816 
1817 
1818 static void
1819 revalidate(nsc_ctx_t *ctx)
1820 {
1821 	(void) thr_setname(thr_self(), "revalidate");
1822 
1823 	for (;;) {
1824 		int i, slp, interval, count;
1825 
1826 		(void) rw_rdlock(&ctx->cfg_rwlp);
1827 		slp = ctx->cfg.pos_ttl;
1828 		count = ctx->cfg.keephot;
1829 		(void) rw_unlock(&ctx->cfg_rwlp);
1830 
1831 		if (slp < 60)
1832 			slp = 60;
1833 		if (count != 0) {
1834 			interval = (slp/2)/count;
1835 			if (interval == 0)
1836 				interval = 1;
1837 			(void) sleep(slp*2/3);
1838 			for (i = 0; i < ctx->db_count; i++) {
1839 				getxy_keepalive(ctx, ctx->nsc_db[i],
1840 				    count, interval);
1841 			}
1842 		} else {
1843 			(void) sleep(slp);
1844 		}
1845 	}
1846 }
1847 
1848 
1849 static void
1850 getxy_keepalive(nsc_ctx_t *ctx, nsc_db_t *nscdb, int keep, int interval)
1851 {
1852 	nsc_keephot_t		*table;
1853 	nsc_entry_t		*entry, *ptr;
1854 	int			i;
1855 	nsc_lookup_args_t	*largs;
1856 	nss_pheader_t		*phdr;
1857 	int			bufsiz;
1858 	char			*me = "getxy_keepalive";
1859 
1860 	/* we won't be here if keep == 0 so need to check that */
1861 
1862 	_NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_DEBUG)
1863 	(me, "%s: keep alive\n", nscdb->name);
1864 
1865 	if ((table = maken(keep)) == NULL) {
1866 		_NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_ERROR)
1867 			(me, "memory allocation failure\n");
1868 		exit(1);
1869 	}
1870 
1871 	(void) mutex_lock(&nscdb->db_mutex);
1872 	entry = nscdb->qtail;
1873 	while (entry != NULL) {
1874 		/* leave pending calls alone */
1875 		if (!(entry->stats.status & ST_PENDING)) {
1876 			/* do_revalidate */
1877 			(void) insertn(table, entry->stats.hits, entry);
1878 		}
1879 		entry = entry->qnext;
1880 	}
1881 	for (i = 1; i <= keep; i++) {
1882 		if (table[i].ptr == NULL)
1883 			continue;
1884 		ptr = (nsc_entry_t *)table[i].ptr;
1885 		phdr = (nss_pheader_t *)ptr->buffer;
1886 		if (NSCD_GET_STATUS(phdr) == NSS_SUCCESS)
1887 			/*
1888 			 * for positive cache, in addition to the packed
1889 			 * header size, allocate twice the size of the
1890 			 * existing result (in case the result grows
1891 			 * larger) plus 2K (for the file/compat backend to
1892 			 * process a possible large entry in the /etc files)
1893 			 */
1894 			bufsiz = phdr->data_off + 2 * phdr->data_len + 2048;
1895 		else
1896 			/*
1897 			 * for negative cache, allocate 8K buffer to
1898 			 * hold result in case the next lookup may
1899 			 * return something (in addition to the
1900 			 * packed header size)
1901 			 */
1902 			bufsiz = phdr->data_off + 8096;
1903 		table[i].ptr = malloc(bufsiz);
1904 		if (table[i].ptr == NULL) {
1905 			(void) mutex_unlock(&nscdb->db_mutex);
1906 			_NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_ERROR)
1907 				(me, "memory allocation failure\n");
1908 			exit(1);
1909 		}
1910 		(void) memcpy(table[i].ptr, ptr->buffer,  ptr->bufsize);
1911 		((nss_pheader_t *)table[i].ptr)->pbufsiz = bufsiz;
1912 		table[i].num = bufsiz;
1913 	}
1914 	(void) mutex_unlock(&nscdb->db_mutex);
1915 
1916 	/* launch update thread for each keep hot entry */
1917 	for (i = keep; i > 0; i--) {
1918 		if (table[i].ptr == NULL)
1919 			continue; /* unused slot in table */
1920 		_NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_DEBUG)
1921 		(me, "%s: launching update\n", nscdb->name);
1922 		largs = (nsc_lookup_args_t *)malloc(sizeof (*largs));
1923 		if (largs == NULL) {
1924 			_NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_ERROR)
1925 				(me, "memory allocation failure\n");
1926 			exit(1);
1927 		}
1928 		largs->buffer = table[i].ptr;
1929 		largs->bufsize = table[i].num;
1930 		largs->ctx = ctx;
1931 		largs->nscdb = nscdb;
1932 		if (launch_update(largs) < 0)
1933 			exit(1);
1934 		(void) sleep(interval);
1935 	}
1936 
1937 	/*
1938 	 * The update thread will handle freeing of buffer and largs.
1939 	 * Free the table here.
1940 	 */
1941 	free(table);
1942 }
1943 
1944 
1945 static int
1946 launch_update(nsc_lookup_args_t *in)
1947 {
1948 	char	*me = "launch_update";
1949 	int	errnum;
1950 
1951 	errnum = thr_create(NULL, NULL, (void *(*)(void*))do_update,
1952 	    in, 0|THR_DETACHED, NULL);
1953 	if (errnum != 0) {
1954 		_NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_ERROR)
1955 		(me, "%s: thread creation failure (%d)\n",
1956 		    in->nscdb->name, errnum);
1957 		return (-1);
1958 	}
1959 	return (0);
1960 }
1961 
1962 
1963 static void
1964 do_update(nsc_lookup_args_t *in) {
1965 	nss_pheader_t	*phdr = (nss_pheader_t *)in->buffer;
1966 
1967 	(void) thr_setname(thr_self(), "do_update");
1968 
1969 	/* update the length of the data buffer */
1970 	phdr->data_len = phdr->pbufsiz - phdr->data_off;
1971 
1972 	(void) lookup_int(in, UPDATEBIT);
1973 	if (in->buffer)
1974 		free(in->buffer);
1975 	free(in);
1976 }
1977 
1978 
1979 /*
1980  * Invalidate cache
1981  */
1982 void
1983 nsc_invalidate(nsc_ctx_t *ctx, char *dbname, nsc_ctx_t **ctxs)
1984 {
1985 	int	i;
1986 	char	*me = "nsc_invalidate";
1987 
1988 	if (ctx) {
1989 		ctx_invalidate(ctx);
1990 		return;
1991 	}
1992 
1993 	if (dbname) {
1994 		if ((i = get_cache_idx(dbname)) == -1) {
1995 			_NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_WARNING)
1996 			(me, "%s: invalid cache name\n", dbname);
1997 			return;
1998 		}
1999 		if ((ctx = cache_ctx_p[i]) == NULL)  {
2000 			_NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_WARNING)
2001 			(me, "%s: no cache context found\n",
2002 			    dbname);
2003 			return;
2004 		}
2005 		ctx_invalidate(ctx);
2006 		return;
2007 	}
2008 
2009 	if (ctxs == NULL)
2010 		ctxs =  cache_ctx_p;
2011 
2012 	for (i = 0; i < CACHE_CTX_COUNT; i++) {
2013 		if (ctxs[i] != NULL)
2014 		ctx_invalidate(ctxs[i]);
2015 	}
2016 }
2017 
2018 
2019 /*
2020  * Invalidate cache by context
2021  */
2022 static void
2023 ctx_invalidate(nsc_ctx_t *ctx)
2024 {
2025 	int		i;
2026 	nsc_entry_t	*entry;
2027 	char		*me = "ctx_invalidate";
2028 
2029 	_NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_DEBUG)
2030 	(me, "%s: invalidate cache\n", ctx->dbname);
2031 
2032 	for (i = 0; i < ctx->db_count; i++) {
2033 		if (ctx->nsc_db[i] == NULL)
2034 			continue;
2035 		(void) mutex_lock(&ctx->nsc_db[i]->db_mutex);
2036 		entry = ctx->nsc_db[i]->qtail;
2037 		while (entry != NULL) {
2038 			/* leave pending calls alone */
2039 			if (!(entry->stats.status & ST_PENDING))
2040 				entry->stats.status = ST_DISCARD;
2041 			entry = entry->qnext;
2042 		}
2043 		(void) mutex_unlock(&ctx->nsc_db[i]->db_mutex);
2044 	}
2045 
2046 	(void) mutex_lock(&ctx->stats_mutex);
2047 	ctx->stats.invalidate_count++;
2048 	(void) mutex_unlock(&ctx->stats_mutex);
2049 }
2050 
2051 
2052 /*
2053  * Free nsc_entry_t
2054  *
2055  * Pre-reqs:
2056  * nscdb->db_mutex lock must be held before calling this function
2057  */
2058 static void
2059 delete_entry(nsc_db_t *nscdb, nsc_ctx_t *ctx, nsc_entry_t *entry) {
2060 	uint_t		hash;
2061 
2062 	avl_remove(&nscdb->tree, entry);
2063 	HASH_REMOVE(nscdb, entry, hash, nscd_false);
2064 	queue_remove(nscdb, entry);
2065 	if (entry->buffer != NULL) {
2066 		free(entry->buffer);
2067 		entry->buffer = NULL;
2068 	}
2069 	umem_cache_free(nsc_entry_cache, entry);
2070 	(void) mutex_lock(&ctx->stats_mutex);
2071 	ctx->stats.entries--;
2072 	(void) mutex_unlock(&ctx->stats_mutex);
2073 }
2074 
2075 
2076 static nscd_rc_t
2077 lookup_cache(nsc_lookup_args_t *largs, nscd_cfg_cache_t *cfgp,
2078     nss_XbyY_args_t *argp, char *whoami, nsc_entry_t **entry)
2079 {
2080 	nsc_db_t	*nscdb;
2081 	nsc_ctx_t	*ctx;
2082 	uint_t		hash;
2083 	avl_index_t	pos;
2084 	ulong_t		nentries;
2085 	nsc_entry_t	find_entry, *node;
2086 	char		*me = "lookup_cache";
2087 
2088 	ctx = largs->ctx;
2089 	nscdb = largs->nscdb;
2090 
2091 	/* set the search key */
2092 	find_entry.key = argp->key;	/* struct copy (not deep) */
2093 
2094 	/* lookup the hash table ==> O(1) */
2095 	if (nscdb->htable) {
2096 		*entry = hash_find(nscdb, &find_entry, &hash, nscd_true);
2097 		if (*entry != NULL) {
2098 			(void) queue_adjust(nscdb, *entry);
2099 			return (NSCD_SUCCESS);
2100 		}
2101 	}
2102 
2103 	/* if not found, lookup the AVL tree ==> O(log n) */
2104 	*entry = (nsc_entry_t *)avl_find(&nscdb->tree, &find_entry, &pos);
2105 	if (*entry != NULL) {
2106 		(void) queue_adjust(nscdb, *entry);
2107 		/* move it to the hash table */
2108 		if (nscdb->htable) {
2109 			if (nscdb->htable[hash] == NULL ||
2110 			    (*entry)->stats.hits >=
2111 			    nscdb->htable[hash]->stats.hits) {
2112 				nscdb->htable[hash] = *entry;
2113 			}
2114 		}
2115 		return (NSCD_SUCCESS);
2116 	}
2117 
2118 	/* entry not found in the cache */
2119 	_NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_DEBUG)
2120 		(me, "%s: cache miss\n", whoami);
2121 
2122 	if (cfgp->avoid_ns == nscd_true) {
2123 		_NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_DEBUG)
2124 			(me, "%s: avoid name service\n", whoami);
2125 		return (NSCD_DB_ENTRY_NOT_FOUND);
2126 	}
2127 
2128 	/* allocate memory for new entry (stub) */
2129 	*entry = (nsc_entry_t *)umem_cache_alloc(nsc_entry_cache,
2130 	    UMEM_DEFAULT);
2131 	if (*entry == NULL) {
2132 		_NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_ERROR)
2133 			(me, "%s: memory allocation failure\n", whoami);
2134 		return (NSCD_NO_MEMORY);
2135 	}
2136 	(void) memset(*entry, 0, sizeof (**entry));
2137 
2138 	/*
2139 	 * Note that the actual data for the key is stored within
2140 	 * the largs->buffer (input buffer to nsc_lookup).
2141 	 * find_entry.key only contains pointers to this data.
2142 	 *
2143 	 * If largs->buffer will be re-allocated by nss_psearch
2144 	 * then (*entry)->key will have dangling pointers.
2145 	 * In such case, the following assignment needs to be
2146 	 * replaced by code that duplicates the key.
2147 	 */
2148 	(*entry)->key = find_entry.key;
2149 
2150 	/*
2151 	 * Add the entry to the cache.
2152 	 */
2153 	avl_insert(&nscdb->tree, *entry, pos);	/* O(log n) */
2154 	(void) queue_adjust(nscdb, *entry);	/* constant */
2155 	if (nscdb->htable)			/* constant */
2156 		nscdb->htable[hash] = *entry;
2157 	(*entry)->stats.status = ST_NEW_ENTRY;
2158 
2159 	(void) mutex_lock(&ctx->stats_mutex);
2160 	nentries = ++(ctx->stats.entries);
2161 	(void) mutex_unlock(&ctx->stats_mutex);
2162 
2163 	/* Have we exceeded max entries ? */
2164 	if (cfgp->maxentries > 0 && nentries > cfgp->maxentries) {
2165 		_NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_DEBUG)
2166 			(me, "%s: maximum entries exceeded -- "
2167 			    "deleting least recently used entry\n",
2168 			    whoami);
2169 
2170 		node = nscdb->qhead;
2171 		while (node != NULL && node != *entry) {
2172 			if (node->stats.status == ST_DISCARD ||
2173 			    !(node->stats.status & ST_PENDING)) {
2174 				delete_entry(nscdb, ctx, node);
2175 				break;
2176 			}
2177 			node = node->qprev;
2178 		}
2179 
2180 		/*
2181 		 * It's okay if we were not able to find one to delete.
2182 		 * The reaper (when invoked) will return the cache to a
2183 		 * safe level.
2184 		 */
2185 	}
2186 
2187 	return (NSCD_SUCCESS);
2188 }
2189 
2190 static void
2191 reaper(nsc_ctx_t *ctx)
2192 {
2193 	uint_t		ttl, extra_sleep, total_sleep, intervals;
2194 	uint_t		nodes_per_interval, seconds_per_interval;
2195 	ulong_t		nsc_entries;
2196 	char		*me = "reaper";
2197 
2198 	(void) thr_setname(thr_self(), me);
2199 
2200 	for (;;) {
2201 		(void) mutex_lock(&ctx->stats_mutex);
2202 		nsc_entries = ctx->stats.entries;
2203 		(void) mutex_unlock(&ctx->stats_mutex);
2204 
2205 		(void) rw_rdlock(&ctx->cfg_rwlp);
2206 		ttl = ctx->cfg.pos_ttl;
2207 		(void) rw_unlock(&ctx->cfg_rwlp);
2208 
2209 		if (nsc_entries == 0) {
2210 			_NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_DEBUG)
2211 				(me, "%s: nothing to reap\n", ctx->dbname);
2212 
2213 			/* sleep for atleast 60 seconds */
2214 			if (ttl < 60)
2215 				ttl = 60;
2216 			_NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_DEBUG)
2217 			(me, "%s: sleep %d\n", ctx->dbname, ttl);
2218 			(void) sleep(ttl);
2219 			continue;
2220 		}
2221 
2222 		if (ttl < 32) ttl = 32;
2223 		if (ttl > (1<<28)) ttl = 1<<28;
2224 
2225 		/*
2226 		 * minimum nodes_per_interval = 256 or 1<<8
2227 		 * maximum nodes_per_interval = nsc_entries
2228 		 * minimum seconds_per_interval = 32 or 1<<5
2229 		 * maximum_seconds_per_interval = ttl
2230 		 */
2231 		if (nsc_entries <= ttl) {
2232 			intervals = (nsc_entries >> 8) + 1;
2233 			seconds_per_interval = ttl / intervals;
2234 			nodes_per_interval = 256;
2235 		} else {
2236 			intervals = (ttl >> 5) + 1;
2237 			seconds_per_interval = 32;
2238 			nodes_per_interval = nsc_entries / intervals;
2239 			if (nodes_per_interval < 256)
2240 				nodes_per_interval = 256;
2241 		}
2242 
2243 		_NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_DEBUG)
2244 			(me, "%s: total entries = %d, "
2245 			    "seconds per interval = %d, "
2246 			    "nodes per interval = %d\n",
2247 			    ctx->dbname, nsc_entries, seconds_per_interval,
2248 			    nodes_per_interval);
2249 		total_sleep = reap_cache(ctx, nodes_per_interval,
2250 		    seconds_per_interval);
2251 		extra_sleep = 1 + ttl - total_sleep;
2252 		if (extra_sleep > 0)
2253 			(void) sleep(extra_sleep);
2254 	}
2255 }
2256 
2257 
2258 static uint_t
2259 reap_cache(nsc_ctx_t *ctx, uint_t nodes_per_interval,
2260     uint_t seconds_per_interval)
2261 {
2262 	uint_t		nodes_togo, total_sleep;
2263 	time_t		now;
2264 	nsc_entry_t	*node, *next_node;
2265 	nsc_db_t	*nscdb;
2266 	uint_t		primes[] = {_NSC_HTSIZE_PRIMES};
2267 	ulong_t		count, nentries, maxentries;
2268 	int		i, slot, value, newhtsize;
2269 	char		*me = "reap_cache";
2270 
2271 	count = 0;
2272 	total_sleep = 0;
2273 	nodes_togo = nodes_per_interval;
2274 	now = time(NULL);
2275 
2276 	for (i = 0; i < ctx->db_count; i++) {
2277 		nscdb = ctx->nsc_db[i];
2278 		(void) mutex_lock(&nscdb->db_mutex);
2279 		nscdb->reap_node = nscdb->qtail;
2280 		while (nscdb->reap_node != NULL) {
2281 			if (nodes_togo == 0) {
2282 				(void) mutex_unlock(&nscdb->db_mutex);
2283 				(void) sleep(seconds_per_interval);
2284 				total_sleep += seconds_per_interval;
2285 				nodes_togo = nodes_per_interval;
2286 				now = time(NULL);
2287 				(void) mutex_lock(&nscdb->db_mutex);
2288 			}
2289 			/* delete ST_DISCARD and expired nodes */
2290 			if ((node = nscdb->reap_node) == NULL)
2291 				break;
2292 			if (node->stats.status == ST_DISCARD ||
2293 			    (!(node->stats.status & ST_PENDING) &&
2294 			    node->stats.timestamp < now)) {
2295 				/*
2296 				 * Delete entry if its discard flag is
2297 				 * set OR if it has expired. Entries
2298 				 * with pending updates are not
2299 				 * deleted.
2300 				 * nscdb->reap_node will be adjusted
2301 				 * by delete_entry()
2302 				 */
2303 				delete_entry(nscdb, ctx, node);
2304 				count++;
2305 			} else {
2306 				nscdb->reap_node = node->qnext;
2307 			}
2308 			nodes_togo--;
2309 		}
2310 
2311 		if (nscdb->htsize == 0) {
2312 			(void) mutex_unlock(&nscdb->db_mutex);
2313 			continue;
2314 		}
2315 
2316 		/*
2317 		 * Dynamic adjustment of hash table size.
2318 		 *
2319 		 * Hash table size is roughly 1/8th of the
2320 		 * total entries. However the size is changed
2321 		 * only when the number of entries double or
2322 		 * reduced by half
2323 		 */
2324 		nentries = avl_numnodes(&nscdb->tree);
2325 		for (slot = 0, value = _NSC_INIT_HTSIZE_SLOT_VALUE;
2326 		    slot < _NSC_HTSIZE_NUM_SLOTS && nentries > value;
2327 		    value = (value << 1) + 1, slot++)
2328 			;
2329 		if (nscdb->hash_type == nsc_ht_power2)
2330 			newhtsize = _NSC_INIT_HTSIZE_POWER2 << slot;
2331 		else
2332 			newhtsize = primes[slot];
2333 
2334 		/* Recommended size is same as the current size. Done */
2335 		if (nscdb->htsize == newhtsize) {
2336 			(void) mutex_unlock(&nscdb->db_mutex);
2337 			continue;
2338 		}
2339 
2340 		_NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_DEBUG)
2341 			(me, "%s: resizing hash table from %d to %d\n",
2342 			    nscdb->name, nscdb->htsize, newhtsize);
2343 
2344 		/*
2345 		 * Dump old hashes because it would be time
2346 		 * consuming to rehash them.
2347 		 */
2348 		(void) free(nscdb->htable);
2349 		nscdb->htable = calloc(newhtsize, sizeof (*(nscdb->htable)));
2350 		if (nscdb->htable == NULL) {
2351 			_NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_ERROR)
2352 				(me, "%s: memory allocation failure\n",
2353 				    nscdb->name);
2354 			/* -1 to try later */
2355 			nscdb->htsize = -1;
2356 		} else {
2357 			nscdb->htsize = newhtsize;
2358 		}
2359 		(void) mutex_unlock(&nscdb->db_mutex);
2360 	}
2361 
2362 	_NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_DEBUG)
2363 		(me, "%s: reaped %lu entries\n", ctx->dbname, count);
2364 
2365 	/*
2366 	 * if cache is almost full then reduce it to a safe level by
2367 	 * evicting LRU entries
2368 	 */
2369 
2370 	(void) rw_rdlock(&ctx->cfg_rwlp);
2371 	maxentries = ctx->cfg.maxentries;
2372 	(void) rw_unlock(&ctx->cfg_rwlp);
2373 
2374 	/* No limit on number of entries. Done */
2375 	if (maxentries == 0)
2376 		goto out;
2377 
2378 	(void) mutex_lock(&ctx->stats_mutex);
2379 	nentries = ctx->stats.entries;
2380 	(void) mutex_unlock(&ctx->stats_mutex);
2381 
2382 	/* what is the percentage of cache used ? */
2383 	value = (nentries * 100) / maxentries;
2384 	if (value < _NSC_EVICTION_START_LEVEL)
2385 		goto out;
2386 
2387 	/*
2388 	 * cache needs to be reduced to a safe level
2389 	 */
2390 	value -= _NSC_EVICTION_SAFE_LEVEL;
2391 	for (i = 0, count = 0; i < ctx->db_count; i++) {
2392 		/*
2393 		 * Reduce each subcache by 'value' percent
2394 		 */
2395 		nscdb = ctx->nsc_db[i];
2396 		(void) mutex_lock(&nscdb->db_mutex);
2397 		nodes_togo = (value * avl_numnodes(&nscdb->tree)) / 100;
2398 
2399 		/* Start from LRU entry i.e queue head */
2400 		next_node = nscdb->qhead;
2401 		while (nodes_togo > 0 && next_node != NULL) {
2402 			node = next_node;
2403 			next_node = next_node->qprev;
2404 			if (node->stats.status == ST_DISCARD ||
2405 			    !(node->stats.status & ST_PENDING)) {
2406 				/* Leave nodes with pending updates alone  */
2407 				delete_entry(nscdb, ctx, node);
2408 				count++;
2409 				nodes_togo--;
2410 			}
2411 		}
2412 		(void) mutex_unlock(&nscdb->db_mutex);
2413 	}
2414 
2415 	_NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_DEBUG)
2416 		(me, "%s: evicted %lu LRU entries\n", ctx->dbname, count);
2417 
2418 out:
2419 	return (total_sleep);
2420 }
2421