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