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