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