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