Unleashed v1.4
[unleashed.git] / usr / src / cmd / nscd / cache.c
blob4f4b76c52f0d7524efebe670e4ada2ede524ec66
1 /*
2 * CDDL HEADER START
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.
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.
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]
19 * CDDL HEADER END
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.
28 * Cache routines for nscd
30 #include <assert.h>
31 #include <errno.h>
32 #include <memory.h>
33 #include <signal.h>
34 #include <stdlib.h>
35 #include <stddef.h>
36 #include <stdio.h>
37 #include <string.h>
38 #include <sys/stat.h>
39 #include <sys/time.h>
40 #include <sys/types.h>
41 #include <sys/wait.h>
42 #include <unistd.h>
43 #include <ucred.h>
44 #include <nss_common.h>
45 #include <locale.h>
46 #include <ctype.h>
47 #include <strings.h>
48 #include <string.h>
49 #include <umem.h>
50 #include <fcntl.h>
51 #include "cache.h"
52 #include "nscd_door.h"
53 #include "nscd_log.h"
54 #include "nscd_config.h"
55 #include "nscd_frontend.h"
56 #include "nscd_switch.h"
58 #define SUCCESS 0
59 #define NOTFOUND -1
60 #define SERVERERROR -2
61 #define NOSERVER -3
62 #define CONTINUE -4
64 static nsc_db_t *nsc_get_db(nsc_ctx_t *, int);
65 static nscd_rc_t lookup_cache(nsc_lookup_args_t *, nscd_cfg_cache_t *,
66 nss_XbyY_args_t *, char *, nsc_entry_t **);
67 static uint_t reap_cache(nsc_ctx_t *, uint_t, uint_t);
68 static void delete_entry(nsc_db_t *, nsc_ctx_t *, nsc_entry_t *);
69 static void print_stats(nscd_cfg_stat_cache_t *);
70 static void print_cfg(nscd_cfg_cache_t *);
71 static int lookup_int(nsc_lookup_args_t *, int);
73 #ifdef NSCD_DEBUG
74 static void print_entry(nsc_db_t *, time_t, nsc_entry_t *);
75 static void avl_dump(nsc_db_t *, time_t);
76 static void hash_dump(nsc_db_t *, time_t);
77 #endif /* NSCD_DEBUG */
78 static nsc_entry_t *hash_find(nsc_db_t *, nsc_entry_t *, uint_t *, nscd_bool_t);
80 static void queue_adjust(nsc_db_t *, nsc_entry_t *);
81 static void queue_remove(nsc_db_t *, nsc_entry_t *);
82 #ifdef NSCD_DEBUG
83 static void queue_dump(nsc_db_t *, time_t);
84 #endif /* NSCD_DEBUG */
86 static int launch_update(nsc_lookup_args_t *);
87 static void do_update(nsc_lookup_args_t *);
88 static void getxy_keepalive(nsc_ctx_t *, nsc_db_t *, int, int);
90 static void ctx_info(nsc_ctx_t *);
91 static void ctx_info_nolock(nsc_ctx_t *);
92 static void ctx_invalidate(nsc_ctx_t *);
94 static void nsc_db_str_key_getlogstr(char *, char *, size_t, nss_XbyY_args_t *);
95 static void nsc_db_int_key_getlogstr(char *, char *, size_t, nss_XbyY_args_t *);
96 static void nsc_db_any_key_getlogstr(char *, char *, size_t, nss_XbyY_args_t *);
98 static int nsc_db_cis_key_compar(const void *, const void *);
99 static int nsc_db_ces_key_compar(const void *, const void *);
100 static int nsc_db_int_key_compar(const void *, const void *);
102 static uint_t nsc_db_cis_key_gethash(nss_XbyY_key_t *, int);
103 static uint_t nsc_db_ces_key_gethash(nss_XbyY_key_t *, int);
104 static uint_t nsc_db_int_key_gethash(nss_XbyY_key_t *, int);
106 static umem_cache_t *nsc_entry_cache;
108 static nsc_ctx_t *init_cache_ctx(int);
109 static void reaper(nsc_ctx_t *);
110 static void revalidate(nsc_ctx_t *);
112 static nss_status_t
113 dup_packed_buffer(void *src, void *dst) {
114 nsc_lookup_args_t *s = (nsc_lookup_args_t *)src;
115 nsc_entry_t *d = (nsc_entry_t *)dst;
116 nss_pheader_t *sphdr = (nss_pheader_t *)s->buffer;
117 nss_pheader_t *dphdr = (nss_pheader_t *)d->buffer;
118 int slen, new_pbufsiz = 0;
120 if (NSCD_GET_STATUS(sphdr) != NSS_SUCCESS) {
122 /* no result, copy header only (status, errno, etc) */
123 slen = sphdr->data_off;
124 } else {
126 * lookup result returned, data to copy is the packed
127 * header plus result (add 1 for the terminating NULL
128 * just in case)
130 slen = sphdr->data_off + sphdr->data_len + 1;
133 /* allocate cache packed buffer */
134 if (dphdr != NULL && d->bufsize <= slen && d->bufsize != 0) {
135 /* old buffer too small, free it */
136 free(dphdr);
137 d->buffer = NULL;
138 d->bufsize = 0;
139 dphdr = NULL;
141 if (dphdr == NULL) {
142 /* get new buffer */
143 dphdr = calloc(1, slen + 1);
144 if (dphdr == NULL)
145 return (NSS_ERROR);
146 d->buffer = dphdr;
147 d->bufsize = slen + 1;
148 new_pbufsiz = slen + 1;
151 (void) memcpy(dphdr, sphdr, slen);
152 if (new_pbufsiz != 0)
153 dphdr->pbufsiz = new_pbufsiz;
155 return (NSS_SUCCESS);
158 char *cache_name[CACHE_CTX_COUNT] = {
159 NSS_DBNAM_PASSWD,
160 NSS_DBNAM_GROUP,
161 NSS_DBNAM_HOSTS,
162 NSS_DBNAM_IPNODES,
163 NSS_DBNAM_EXECATTR,
164 NSS_DBNAM_PROFATTR,
165 NSS_DBNAM_USERATTR,
166 NSS_DBNAM_ETHERS,
167 NSS_DBNAM_RPC,
168 NSS_DBNAM_PROTOCOLS,
169 NSS_DBNAM_NETWORKS,
170 NSS_DBNAM_BOOTPARAMS,
171 NSS_DBNAM_AUTHATTR,
172 NSS_DBNAM_SERVICES,
173 NSS_DBNAM_NETMASKS,
174 NSS_DBNAM_PRINTERS,
175 NSS_DBNAM_PROJECT
178 typedef void (*cache_init_ctx_t)(nsc_ctx_t *);
179 static cache_init_ctx_t cache_init_ctx[CACHE_CTX_COUNT] = {
180 passwd_init_ctx,
181 group_init_ctx,
182 host_init_ctx,
183 ipnode_init_ctx,
184 exec_init_ctx,
185 prof_init_ctx,
186 user_init_ctx,
187 ether_init_ctx,
188 rpc_init_ctx,
189 proto_init_ctx,
190 net_init_ctx,
191 bootp_init_ctx,
192 auth_init_ctx,
193 serv_init_ctx,
194 netmask_init_ctx,
195 printer_init_ctx,
196 project_init_ctx,
199 nsc_ctx_t *cache_ctx_p[CACHE_CTX_COUNT] = { 0 };
200 static nscd_cfg_stat_cache_t null_stats = { 0 };
201 static nscd_cfg_global_cache_t global_cfg;
204 * Given database name 'dbname' find cache index
207 get_cache_idx(char *dbname) {
208 int i;
209 char *nsc_name;
211 for (i = 0; i < CACHE_CTX_COUNT; i++) {
212 nsc_name = cache_name[i];
213 if (strcmp(nsc_name, dbname) == 0)
214 return (i);
216 return (-1);
220 * Given database name 'dbname' retrieve cache context,
221 * if not created yet, allocate and initialize it.
223 static nscd_rc_t
224 get_cache_ctx(char *dbname, nsc_ctx_t **ctx) {
225 int i;
227 *ctx = NULL;
229 i = get_cache_idx(dbname);
230 if (i == -1)
231 return (NSCD_INVALID_ARGUMENT);
232 if ((*ctx = cache_ctx_p[i]) == NULL) {
233 *ctx = init_cache_ctx(i);
234 if (*ctx == NULL)
235 return (NSCD_NO_MEMORY);
238 return (NSCD_SUCCESS);
242 * Generate a log string to identify backend operation in debug logs
244 static void
245 nsc_db_str_key_getlogstr(char *name, char *whoami, size_t len,
246 nss_XbyY_args_t *argp) {
247 (void) snprintf(whoami, len, "%s [key=%s]", name, argp->key.name);
251 static void
252 nsc_db_int_key_getlogstr(char *name, char *whoami, size_t len,
253 nss_XbyY_args_t *argp) {
254 (void) snprintf(whoami, len, "%s [key=%d]", name, argp->key.number);
257 /*ARGSUSED*/
258 static void
259 nsc_db_any_key_getlogstr(char *name, char *whoami, size_t len,
260 nss_XbyY_args_t *argp) {
261 (void) snprintf(whoami, len, "%s", name);
266 * Returns cache based on dbop
268 static nsc_db_t *
269 nsc_get_db(nsc_ctx_t *ctx, int dbop) {
270 int i;
272 for (i = 0; i < ctx->db_count; i++) {
273 if (ctx->nsc_db[i] && dbop == ctx->nsc_db[i]->dbop)
274 return (ctx->nsc_db[i]);
276 return (NULL);
281 * integer compare routine for _NSC_DB_INT_KEY
283 static int
284 nsc_db_int_key_compar(const void *n1, const void *n2) {
285 nsc_entry_t *e1, *e2;
287 e1 = (nsc_entry_t *)n1;
288 e2 = (nsc_entry_t *)n2;
289 return (_NSC_INT_KEY_CMP(e1->key.number, e2->key.number));
294 * case sensitive name compare routine for _NSC_DB_CES_KEY
296 static int
297 nsc_db_ces_key_compar(const void *n1, const void *n2) {
298 nsc_entry_t *e1, *e2;
299 int res, l1, l2;
301 e1 = (nsc_entry_t *)n1;
302 e2 = (nsc_entry_t *)n2;
303 l1 = strlen(e1->key.name);
304 l2 = strlen(e2->key.name);
305 res = strncmp(e1->key.name, e2->key.name, (l1 > l2)?l1:l2);
306 return (_NSC_INT_KEY_CMP(res, 0));
311 * case insensitive name compare routine _NSC_DB_CIS_KEY
313 static int
314 nsc_db_cis_key_compar(const void *n1, const void *n2) {
315 nsc_entry_t *e1, *e2;
316 int res, l1, l2;
318 e1 = (nsc_entry_t *)n1;
319 e2 = (nsc_entry_t *)n2;
320 l1 = strlen(e1->key.name);
321 l2 = strlen(e2->key.name);
322 res = strncasecmp(e1->key.name, e2->key.name, (l1 > l2)?l1:l2);
323 return (_NSC_INT_KEY_CMP(res, 0));
327 * macro used to generate elf hashes for strings
329 #define _NSC_ELF_STR_GETHASH(func, str, htsize, hval) \
330 hval = 0; \
331 while (*str) { \
332 uint_t g; \
333 hval = (hval << 4) + func(*str++); \
334 if ((g = (hval & 0xf0000000)) != 0) \
335 hval ^= g >> 24; \
336 hval &= ~g; \
338 hval %= htsize;
342 * cis hash function
344 uint_t
345 cis_gethash(const char *key, int htsize) {
346 uint_t hval;
347 if (key == NULL)
348 return (0);
349 _NSC_ELF_STR_GETHASH(tolower, key, htsize, hval);
350 return (hval);
355 * ces hash function
357 uint_t
358 ces_gethash(const char *key, int htsize) {
359 uint_t hval;
360 if (key == NULL)
361 return (0);
362 _NSC_ELF_STR_GETHASH(, key, htsize, hval);
363 return (hval);
368 * one-at-a-time hash function
370 uint_t
371 db_gethash(const void *key, int len, int htsize) {
372 uint_t hval, i;
373 const char *str = key;
375 if (str == NULL)
376 return (0);
378 for (hval = 0, i = 0; i < len; i++) {
379 hval += str[i];
380 hval += (hval << 10);
381 hval ^= (hval >> 6);
383 hval += (hval << 3);
384 hval ^= (hval >> 11);
385 hval += (hval << 15);
386 return (hval % htsize);
391 * case insensitive name gethash routine _NSC_DB_CIS_KEY
393 static uint_t
394 nsc_db_cis_key_gethash(nss_XbyY_key_t *key, int htsize) {
395 return (cis_gethash(key->name, htsize));
400 * case sensitive name gethash routine _NSC_DB_CES_KEY
402 static uint_t
403 nsc_db_ces_key_gethash(nss_XbyY_key_t *key, int htsize) {
404 return (ces_gethash(key->name, htsize));
409 * integer gethash routine _NSC_DB_INT_KEY
411 static uint_t
412 nsc_db_int_key_gethash(nss_XbyY_key_t *key, int htsize) {
413 return (db_gethash(&key->number, sizeof (key->number), htsize));
418 * Find entry in the hash table
419 * if cmp == nscd_true)
420 * return entry only if the keys match
421 * else
422 * return entry in the hash location without checking the keys
425 static nsc_entry_t *
426 hash_find(nsc_db_t *nscdb, nsc_entry_t *entry, uint_t *hash,
427 nscd_bool_t cmp) {
429 nsc_entry_t *hashentry;
431 if (nscdb->gethash)
432 *hash = nscdb->gethash(&entry->key, nscdb->htsize);
433 else
434 return (NULL);
436 hashentry = nscdb->htable[*hash];
437 if (cmp == nscd_false || hashentry == NULL)
438 return (hashentry);
439 if (nscdb->compar) {
440 if (nscdb->compar(entry, hashentry) == 0)
441 return (hashentry);
443 return (NULL);
447 #define HASH_REMOVE(nscdb, entry, hash, cmp) \
448 if (nscdb->htable) { \
449 if (entry == hash_find(nscdb, entry, &hash, cmp)) \
450 nscdb->htable[hash] = NULL; \
454 #define HASH_INSERT(nscdb, entry, hash, cmp) \
455 if (nscdb->htable) { \
456 (void) hash_find(nscdb, entry, &hash, cmp); \
457 nscdb->htable[hash] = entry; \
461 #ifdef NSCD_DEBUG
462 static void
463 print_entry(nsc_db_t *nscdb, time_t now, nsc_entry_t *entry) {
464 nss_XbyY_args_t args;
465 char whoami[512];
467 switch (entry->stats.status) {
468 case ST_NEW_ENTRY:
469 (void) fprintf(stdout, gettext("\t status: new entry\n"));
470 return;
471 case ST_UPDATE_PENDING:
472 (void) fprintf(stdout, gettext("\t status: update pending\n"));
473 return;
474 case ST_LOOKUP_PENDING:
475 (void) fprintf(stdout, gettext("\t status: lookup pending\n"));
476 return;
477 case ST_DISCARD:
478 (void) fprintf(stdout, gettext("\t status: discarded entry\n"));
479 return;
480 default:
481 if (entry->stats.timestamp < now)
482 (void) fprintf(stdout,
483 gettext("\t status: expired (%d seconds ago)\n"),
484 now - entry->stats.timestamp);
485 else
486 (void) fprintf(stdout,
487 gettext("\t status: valid (expiry in %d seconds)\n"),
488 entry->stats.timestamp - now);
489 break;
491 (void) fprintf(stdout, gettext("\t hits: %u\n"), entry->stats.hits);
492 args.key = entry->key;
493 (void) nscdb->getlogstr(nscdb->name, whoami, sizeof (whoami), &args);
494 (void) fprintf(stdout, "\t %s\n", whoami);
496 #endif /* NSCD_DEBUG */
498 static void
499 print_stats(nscd_cfg_stat_cache_t *statsp) {
501 (void) fprintf(stdout, gettext("\n\t STATISTICS:\n"));
502 (void) fprintf(stdout, gettext("\t positive hits: %lu\n"),
503 statsp->pos_hits);
504 (void) fprintf(stdout, gettext("\t negative hits: %lu\n"),
505 statsp->neg_hits);
506 (void) fprintf(stdout, gettext("\t positive misses: %lu\n"),
507 statsp->pos_misses);
508 (void) fprintf(stdout, gettext("\t negative misses: %lu\n"),
509 statsp->neg_misses);
510 (void) fprintf(stdout, gettext("\t total entries: %lu\n"),
511 statsp->entries);
512 (void) fprintf(stdout, gettext("\t queries queued: %lu\n"),
513 statsp->wait_count);
514 (void) fprintf(stdout, gettext("\t queries dropped: %lu\n"),
515 statsp->drop_count);
516 (void) fprintf(stdout, gettext("\t cache invalidations: %lu\n"),
517 statsp->invalidate_count);
519 _NSC_GET_HITRATE(statsp);
520 (void) fprintf(stdout, gettext("\t cache hit rate: %10.1f\n"),
521 statsp->hitrate);
525 static void
526 print_cfg(nscd_cfg_cache_t *cfgp) {
527 (void) fprintf(stdout, gettext("\n\t CONFIG:\n"));
528 (void) fprintf(stdout, gettext("\t enabled: %s\n"),
529 yes_no(cfgp->enable));
530 (void) fprintf(stdout, gettext("\t per user cache: %s\n"),
531 yes_no(cfgp->per_user));
532 (void) fprintf(stdout, gettext("\t avoid name service: %s\n"),
533 yes_no(cfgp->avoid_ns));
534 (void) fprintf(stdout, gettext("\t check file: %s\n"),
535 yes_no(cfgp->check_files));
536 (void) fprintf(stdout, gettext("\t check file interval: %d\n"),
537 cfgp->check_interval);
538 (void) fprintf(stdout, gettext("\t positive ttl: %d\n"),
539 cfgp->pos_ttl);
540 (void) fprintf(stdout, gettext("\t negative ttl: %d\n"),
541 cfgp->neg_ttl);
542 (void) fprintf(stdout, gettext("\t keep hot count: %d\n"),
543 cfgp->keephot);
544 (void) fprintf(stdout, gettext("\t hint size: %d\n"),
545 cfgp->hint_size);
546 (void) fprintf(stdout, gettext("\t max entries: %lu%s"),
547 cfgp->maxentries,
548 cfgp->maxentries?"\n":" (unlimited)\n");
552 #ifdef NSCD_DEBUG
553 static void
554 hash_dump(nsc_db_t *nscdb, time_t now) {
555 nsc_entry_t *entry;
556 int i;
558 (void) fprintf(stdout, gettext("\n\nHASH TABLE:\n"));
559 for (i = 0; i < nscdb->htsize; i++) {
560 if ((entry = nscdb->htable[i]) != NULL) {
561 (void) fprintf(stdout, "hash[%d]:\n", i);
562 print_entry(nscdb, now, entry);
566 #endif /* NSCD_DEBUG */
569 #ifdef NSCD_DEBUG
570 static void
571 avl_dump(nsc_db_t *nscdb, time_t now) {
572 nsc_entry_t *entry;
573 int i;
575 (void) fprintf(stdout, gettext("\n\nAVL TREE:\n"));
576 for (entry = avl_first(&nscdb->tree), i = 0; entry != NULL;
577 entry = avl_walk(&nscdb->tree, entry, AVL_AFTER)) {
578 (void) fprintf(stdout, "avl node[%d]:\n", i++);
579 print_entry(nscdb, now, entry);
582 #endif /* NSCD_DEBUG */
585 #ifdef NSCD_DEBUG
586 static void
587 queue_dump(nsc_db_t *nscdb, time_t now) {
588 nsc_entry_t *entry;
589 int i;
591 (void) fprintf(stdout,
592 gettext("\n\nCACHE [name=%s, nodes=%lu]:\n"),
593 nscdb->name, avl_numnodes(&nscdb->tree));
595 (void) fprintf(stdout,
596 gettext("Starting with the most recently accessed:\n"));
598 for (entry = nscdb->qtail, i = 0; entry; entry = entry->qnext) {
599 (void) fprintf(stdout, "entry[%d]:\n", i++);
600 print_entry(nscdb, now, entry);
603 #endif /* NSCD_DEBUG */
605 static void
606 queue_remove(nsc_db_t *nscdb, nsc_entry_t *entry) {
608 if (nscdb->qtail == entry)
609 nscdb->qtail = entry->qnext;
610 else
611 entry->qprev->qnext = entry->qnext;
613 if (nscdb->qhead == entry)
614 nscdb->qhead = entry->qprev;
615 else
616 entry->qnext->qprev = entry->qprev;
618 if (nscdb->reap_node == entry)
619 nscdb->reap_node = entry->qnext;
620 entry->qnext = entry->qprev = NULL;
624 static void
625 queue_adjust(nsc_db_t *nscdb, nsc_entry_t *entry) {
627 #ifdef NSCD_DEBUG
628 assert(nscdb->qtail || entry->qnext == NULL &&
629 entry->qprev == NULL);
631 assert(nscdb->qtail && nscdb->qhead ||
632 nscdb->qtail == NULL && nscdb->qhead == NULL);
634 assert(entry->qprev || entry->qnext == NULL ||
635 nscdb->qtail == entry);
636 #endif /* NSCD_DEBUG */
638 /* already in the desired position */
639 if (nscdb->qtail == entry)
640 return;
642 /* new queue */
643 if (nscdb->qtail == NULL) {
644 nscdb->qhead = nscdb->qtail = entry;
645 return;
648 /* new entry (prev == NULL AND tail != entry) */
649 if (entry->qprev == NULL) {
650 nscdb->qtail->qprev = entry;
651 entry->qnext = nscdb->qtail;
652 nscdb->qtail = entry;
653 return;
656 /* existing entry */
657 if (nscdb->reap_node == entry)
658 nscdb->reap_node = entry->qnext;
659 if (nscdb->qhead == entry)
660 nscdb->qhead = entry->qprev;
661 else
662 entry->qnext->qprev = entry->qprev;
663 entry->qprev->qnext = entry->qnext;
664 entry->qprev = NULL;
665 entry->qnext = nscdb->qtail;
666 nscdb->qtail->qprev = entry;
667 nscdb->qtail = entry;
672 * Init cache
674 nscd_rc_t
675 init_cache(int debug_level) {
676 int cflags;
678 cflags = (debug_level > 0)?0:UMC_NODEBUG;
679 nsc_entry_cache = umem_cache_create("nsc_entry_cache",
680 sizeof (nsc_entry_t), 0, NULL, NULL, NULL,
681 NULL, NULL, cflags);
682 if (nsc_entry_cache == NULL)
683 return (NSCD_NO_MEMORY);
684 return (NSCD_SUCCESS);
689 * Create cache
691 nsc_db_t *
692 make_cache(enum db_type dbtype, int dbop, char *name,
693 int (*compar) (const void *, const void *),
694 void (*getlogstr)(char *, char *, size_t, nss_XbyY_args_t *),
695 uint_t (*gethash)(nss_XbyY_key_t *, int),
696 enum hash_type httype, int htsize)
698 nsc_db_t *nscdb;
699 char *me = "make_cache";
701 nscdb = (nsc_db_t *)malloc(sizeof (*nscdb));
702 if (nscdb == NULL) {
703 _NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_ERROR)
704 (me, "%s: memory allocation failure\n", name);
705 goto out;
707 (void) memset(nscdb, 0, sizeof (*nscdb));
709 nscdb->dbop = dbop;
710 nscdb->name = name;
711 nscdb->db_type = dbtype;
713 /* Assign compare routine */
714 if (compar == NULL) {
715 if (_NSC_DB_CES_KEY(nscdb))
716 nscdb->compar = nsc_db_ces_key_compar;
717 else if (_NSC_DB_CIS_KEY(nscdb))
718 nscdb->compar = nsc_db_cis_key_compar;
719 else if (_NSC_DB_INT_KEY(nscdb))
720 nscdb->compar = nsc_db_int_key_compar;
721 else
722 assert(0);
723 } else {
724 nscdb->compar = compar;
727 /* The cache is an AVL tree */
728 avl_create(&nscdb->tree, nscdb->compar, sizeof (nsc_entry_t),
729 offsetof(nsc_entry_t, avl_link));
731 /* Assign log routine */
732 if (getlogstr == NULL) {
733 if (_NSC_DB_STR_KEY(nscdb))
734 nscdb->getlogstr = nsc_db_str_key_getlogstr;
735 else if (_NSC_DB_INT_KEY(nscdb))
736 nscdb->getlogstr = nsc_db_int_key_getlogstr;
737 else
738 nscdb->getlogstr = nsc_db_any_key_getlogstr;
739 } else {
740 nscdb->getlogstr = getlogstr;
743 /* The AVL tree based cache uses a hash table for quick access */
744 if (htsize != 0) {
745 /* Determine hash table size based on type */
746 nscdb->hash_type = httype;
747 if (htsize < 0) {
748 switch (httype) {
749 case nsc_ht_power2:
750 htsize = _NSC_INIT_HTSIZE_POWER2;
751 break;
752 case nsc_ht_prime:
753 case nsc_ht_default:
754 default:
755 htsize = _NSC_INIT_HTSIZE_PRIME;
758 nscdb->htsize = htsize;
760 /* Create the hash table */
761 nscdb->htable = calloc(htsize, sizeof (*(nscdb->htable)));
762 if (nscdb->htable == NULL) {
763 _NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_ERROR)
764 (me, "%s: memory allocation failure\n", name);
765 goto out;
768 /* Assign gethash routine */
769 if (gethash == NULL) {
770 if (_NSC_DB_CES_KEY(nscdb))
771 nscdb->gethash = nsc_db_ces_key_gethash;
772 else if (_NSC_DB_CIS_KEY(nscdb))
773 nscdb->gethash = nsc_db_cis_key_gethash;
774 else if (_NSC_DB_INT_KEY(nscdb))
775 nscdb->gethash = nsc_db_int_key_gethash;
776 else
777 assert(0);
778 } else {
779 nscdb->gethash = gethash;
783 (void) mutex_init(&nscdb->db_mutex, USYNC_THREAD, NULL);
784 return (nscdb);
786 out:
787 free(nscdb->htable);
788 free(nscdb);
789 return (NULL);
794 * verify
796 /* ARGSUSED */
797 nscd_rc_t
798 _nscd_cfg_cache_verify(
799 void *data,
800 struct nscd_cfg_param_desc *pdesc,
801 nscd_cfg_id_t *nswdb,
802 nscd_cfg_flag_t dflag,
803 nscd_cfg_error_t **errorp,
804 void **cookie)
807 return (NSCD_SUCCESS);
811 * notify
813 /* ARGSUSED */
814 nscd_rc_t
815 _nscd_cfg_cache_notify(
816 void *data,
817 struct nscd_cfg_param_desc *pdesc,
818 nscd_cfg_id_t *nswdb,
819 nscd_cfg_flag_t dflag,
820 nscd_cfg_error_t **errorp,
821 void **cookie)
823 nsc_ctx_t *ctx;
824 void *dp;
825 int i;
827 /* group data */
828 if (_nscd_cfg_flag_is_set(dflag, NSCD_CFG_DFLAG_GROUP)) {
829 if (_nscd_cfg_flag_is_set(pdesc->pflag,
830 NSCD_CFG_PFLAG_GLOBAL)) {
831 /* global config */
832 global_cfg = *(nscd_cfg_global_cache_t *)data;
833 } else if (_nscd_cfg_flag_is_set(dflag,
834 NSCD_CFG_DFLAG_SET_ALL_DB)) {
835 /* non-global config for all dbs */
836 for (i = 0; i < CACHE_CTX_COUNT; i++) {
837 ctx = cache_ctx_p[i];
838 if (ctx == NULL)
839 return (NSCD_CTX_NOT_FOUND);
840 (void) rw_wrlock(&ctx->cfg_rwlp);
841 ctx->cfg = *(nscd_cfg_cache_t *)data;
842 ctx->cfg_mtime = time(NULL);
843 (void) rw_unlock(&ctx->cfg_rwlp);
845 } else {
846 /* non-global config for a specific db */
848 /* ignore non-caching databases */
849 if (get_cache_ctx(nswdb->name, &ctx) != NSCD_SUCCESS)
850 return (NSCD_SUCCESS);
851 (void) rw_wrlock(&ctx->cfg_rwlp);
852 ctx->cfg = *(nscd_cfg_cache_t *)data;
853 ctx->cfg_mtime = time(NULL);
854 (void) rw_unlock(&ctx->cfg_rwlp);
856 return (NSCD_SUCCESS);
859 /* individual data */
860 if (_nscd_cfg_flag_is_set(pdesc->pflag, NSCD_CFG_PFLAG_GLOBAL)) {
861 /* global config */
862 dp = (char *)&global_cfg + pdesc->p_offset;
863 (void) memcpy(dp, data, pdesc->p_size);
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 dp = (char *)&ctx->cfg + pdesc->p_offset;
872 (void) rw_wrlock(&ctx->cfg_rwlp);
873 (void) memcpy(dp, data, pdesc->p_size);
874 ctx->cfg_mtime = time(NULL);
875 (void) rw_unlock(&ctx->cfg_rwlp);
877 } else {
878 /* non-global config for a specific db */
880 /* ignore non-caching databases */
881 if (get_cache_ctx(nswdb->name, &ctx) != NSCD_SUCCESS)
882 return (NSCD_SUCCESS);
883 dp = (char *)&ctx->cfg + pdesc->p_offset;
884 (void) rw_wrlock(&ctx->cfg_rwlp);
885 (void) memcpy(dp, data, pdesc->p_size);
886 ctx->cfg_mtime = time(NULL);
887 (void) rw_unlock(&ctx->cfg_rwlp);
889 return (NSCD_SUCCESS);
894 * get stat
896 /* ARGSUSED */
897 nscd_rc_t
898 _nscd_cfg_cache_get_stat(
899 void **stat,
900 struct nscd_cfg_stat_desc *sdesc,
901 nscd_cfg_id_t *nswdb,
902 nscd_cfg_flag_t *dflag,
903 void (**free_stat)(void *stat),
904 nscd_cfg_error_t **errorp)
906 nscd_cfg_stat_cache_t *statsp, stats;
907 nsc_ctx_t *ctx;
908 int i;
909 nscd_rc_t rc;
911 statsp = calloc(1, sizeof (*statsp));
912 if (statsp == NULL)
913 return (NSCD_NO_MEMORY);
915 if (_nscd_cfg_flag_is_set(sdesc->sflag, NSCD_CFG_SFLAG_GLOBAL)) {
916 for (i = 0; i < CACHE_CTX_COUNT; i++) {
917 if (cache_ctx_p[i] == NULL)
918 stats = null_stats;
919 else {
920 (void) mutex_lock(&cache_ctx_p[i]->stats_mutex);
921 stats = cache_ctx_p[i]->stats;
922 (void) mutex_unlock(
923 &cache_ctx_p[i]->stats_mutex);
925 statsp->pos_hits += stats.pos_hits;
926 statsp->neg_hits += stats.neg_hits;
927 statsp->pos_misses += stats.pos_misses;
928 statsp->neg_misses += stats.neg_misses;
929 statsp->entries += stats.entries;
930 statsp->drop_count += stats.drop_count;
931 statsp->wait_count += stats.wait_count;
932 statsp->invalidate_count +=
933 stats.invalidate_count;
935 } else {
936 if ((rc = get_cache_ctx(nswdb->name, &ctx)) != NSCD_SUCCESS) {
937 free(statsp);
938 return (rc);
940 (void) mutex_lock(&ctx->stats_mutex);
941 *statsp = ctx->stats;
942 (void) mutex_unlock(&ctx->stats_mutex);
945 _NSC_GET_HITRATE(statsp);
946 *stat = statsp;
947 return (NSCD_SUCCESS);
951 * This function should only be called when nscd is
952 * not a daemon.
954 void
955 nsc_info(nsc_ctx_t *ctx, char *dbname, nscd_cfg_cache_t cfg[],
956 nscd_cfg_stat_cache_t stats[])
958 int i;
959 char *me = "nsc_info";
960 nsc_ctx_t *ctx1;
961 nsc_ctx_t ctx2;
962 nscd_rc_t rc;
964 if (ctx) {
965 ctx_info(ctx);
966 return;
969 if (dbname) {
970 rc = get_cache_ctx(dbname, &ctx1);
971 if (rc == NSCD_INVALID_ARGUMENT) {
972 _NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_WARNING)
973 (me, "%s: no cache context found\n", dbname);
974 return;
975 } else if (rc == NSCD_NO_MEMORY) {
976 _NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_WARNING)
977 (me, "%s: unable to create cache context - no memory\n",
978 dbname);
979 return;
981 ctx_info(ctx1);
982 return;
985 if (cfg == NULL || stats == NULL)
986 return;
988 for (i = 0; i < CACHE_CTX_COUNT; i++) {
990 ctx2.dbname = cache_name[i];
991 ctx2.cfg = cfg[i];
992 ctx2.stats = stats[i];
993 ctx_info_nolock(&ctx2);
997 static void
998 ctx_info_nolock(nsc_ctx_t *ctx) {
999 nscd_cfg_cache_t cfg;
1000 nscd_cfg_stat_cache_t stats;
1002 cfg = ctx->cfg;
1003 (void) fprintf(stdout, gettext("\n\nCACHE: %s\n"), ctx->dbname);
1004 (void) print_cfg(&cfg);
1006 if (cfg.enable == nscd_false)
1007 return;
1009 stats = ctx->stats;
1010 (void) print_stats(&stats);
1013 static void
1014 ctx_info(nsc_ctx_t *ctx) {
1015 nscd_cfg_cache_t cfg;
1016 nscd_cfg_stat_cache_t stats;
1018 (void) rw_rdlock(&ctx->cfg_rwlp);
1019 cfg = ctx->cfg;
1020 (void) rw_unlock(&ctx->cfg_rwlp);
1021 (void) fprintf(stdout, gettext("\n\nCACHE: %s\n"), ctx->dbname);
1022 (void) print_cfg(&cfg);
1024 if (cfg.enable == nscd_false)
1025 return;
1027 (void) mutex_lock(&ctx->stats_mutex);
1028 stats = ctx->stats;
1029 (void) mutex_unlock(&ctx->stats_mutex);
1030 (void) print_stats(&stats);
1033 #ifdef NSCD_DEBUG
1035 * This function should only be called when nscd is
1036 * not a daemon.
1039 nsc_dump(char *dbname, int dbop)
1041 nsc_ctx_t *ctx;
1042 nsc_db_t *nscdb;
1043 nscd_bool_t enabled;
1044 time_t now;
1045 char *me = "nsc_dump";
1046 int i;
1048 if ((i = get_cache_idx(dbname)) == -1) {
1049 (void) fprintf(stdout, gettext("invalid cache name\n"));
1051 _NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_WARNING)
1052 (me, "%s: invalid cache name\n", dbname);
1053 return (NSCD_CACHE_INVALID_CACHE_NAME);
1056 if ((ctx = cache_ctx_p[i]) == NULL) {
1057 (void) fprintf(stdout, gettext("no cache context\n"));
1059 _NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_WARNING)
1060 (me, "%s: no cache context\n", dbname);
1061 return (NSCD_CACHE_NO_CACHE_CTX);
1064 now = time(NULL);
1065 (void) rw_rdlock(&ctx->cfg_rwlp);
1066 enabled = ctx->cfg.enable;
1067 (void) rw_unlock(&ctx->cfg_rwlp);
1069 if (enabled == nscd_false)
1070 return (NSCD_CACHE_DISABLED);
1072 nscdb = nsc_get_db(ctx, dbop);
1073 if (nscdb == NULL) {
1074 _NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_WARNING)
1075 (me, "%s:%d: no cache found\n", dbname, dbop);
1076 return (NSCD_CACHE_NO_CACHE_FOUND);
1079 (void) mutex_lock(&nscdb->db_mutex);
1080 (void) queue_dump(nscdb, now);
1081 (void) hash_dump(nscdb, now);
1082 (void) avl_dump(nscdb, now);
1083 (void) mutex_unlock(&nscdb->db_mutex);
1084 return (NSCD_SUCCESS);
1086 #endif /* NSCD_DEBUG */
1089 * These macros are for exclusive use of nsc_lookup
1091 #define NSC_LOOKUP_LOG(loglevel, fmt) \
1092 _NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_##loglevel) \
1093 (me, fmt, whoami);
1095 static int
1096 nsc_lookup_no_cache(nsc_lookup_args_t *largs, const char *str)
1098 char *me = "nsc_lookup_no_cache";
1099 nss_status_t status;
1101 _NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_DEBUG)
1102 (me, "%s: name service lookup (bypassing cache)\n", str);
1103 nss_psearch(largs->buffer, largs->bufsize);
1104 status = NSCD_GET_STATUS(largs->buffer);
1105 _NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_DEBUG)
1106 (me, "%s: name service lookup status = %d\n", str, status);
1107 if (status == NSS_SUCCESS) {
1108 return (SUCCESS);
1109 } else if (status == NSS_NOTFOUND) {
1110 return (NOTFOUND);
1111 } else {
1112 return (SERVERERROR);
1117 * This function starts the revalidation and reaper threads
1118 * for a cache
1120 static void
1121 start_threads(nsc_ctx_t *ctx)
1123 int errnum;
1124 char *me = "start_threads";
1127 * kick off the revalidate thread (if necessary)
1129 if (ctx->revalidate_on != nscd_true) {
1130 if (thr_create(NULL, 0, (void *(*)(void *))revalidate,
1131 ctx, 0, NULL) != 0) {
1132 errnum = errno;
1133 _NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_ERROR)
1134 (me, "thr_create (revalidate thread for %s): %s\n",
1135 ctx->dbname, strerror(errnum));
1136 exit(1);
1138 ctx->revalidate_on = nscd_true;
1142 * kick off the reaper thread (if necessary)
1144 if (ctx->reaper_on != nscd_true) {
1145 if (thr_create(NULL, 0, (void *(*)(void *))reaper,
1146 ctx, 0, NULL) != 0) {
1147 errnum = errno;
1148 _NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_ERROR)
1149 (me, "thr_create (reaper thread for %s): %s\n",
1150 ctx->dbname, strerror(errnum));
1151 exit(1);
1153 ctx->reaper_on = nscd_true;
1158 * Examine the packed buffer, see if the front-end parameters
1159 * indicate that the caller specified nsswitch config should be
1160 * used for the lookup. Return 1 if yes, otherwise 0.
1162 static int
1163 nsw_config_in_phdr(void *buf)
1165 nss_pheader_t *pbuf = (nss_pheader_t *)buf;
1166 nssuint_t off;
1167 nss_dbd_t *pdbd;
1168 char *me = "nsw_config_in_phdr";
1170 off = pbuf->dbd_off;
1171 if (off == 0)
1172 return (0);
1173 pdbd = (nss_dbd_t *)((void *)((char *)pbuf + off));
1174 if (pdbd->o_default_config == 0)
1175 return (0);
1177 if ((enum nss_dbp_flags)pdbd->flags & NSS_USE_DEFAULT_CONFIG) {
1178 _NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_DEBUG)
1179 (me, "use caller specified nsswitch config\n");
1180 return (1);
1181 } else
1182 return (0);
1185 static nss_status_t
1186 copy_result(void *rbuf, void *cbuf)
1188 nss_pheader_t *rphdr = (nss_pheader_t *)rbuf;
1189 nss_pheader_t *cphdr = (nss_pheader_t *)cbuf;
1190 char *me = "copy_result";
1192 /* return NSS_ERROR if not enough room to copy result */
1193 if (cphdr->data_len + 1 > rphdr->data_len) {
1194 NSCD_SET_STATUS(rphdr, NSS_ERROR, ERANGE);
1195 return (NSS_ERROR);
1196 } else {
1197 char *dst;
1199 if (cphdr->data_len == 0)
1200 return (NSS_SUCCESS);
1202 dst = (char *)rphdr + rphdr->data_off;
1203 (void) memcpy(dst, (char *)cphdr + cphdr->data_off,
1204 cphdr->data_len);
1205 rphdr->data_len = cphdr->data_len;
1206 /* some frontend code expects a terminating NULL char */
1207 *(dst + rphdr->data_len) = '\0';
1209 _NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_DEBUG)
1210 (me, "cache data (len = %lld): >>%s<<\n",
1211 cphdr->data_len, (char *)cphdr + cphdr->data_off);
1213 return (NSS_SUCCESS);
1217 static int
1218 get_dns_ttl(void *pbuf, char *dbname)
1220 nss_pheader_t *phdr = (nss_pheader_t *)pbuf;
1221 int ttl;
1222 char *me = "get_dns_ttl";
1224 /* if returned, dns ttl is stored in the extended data area */
1225 if (phdr->ext_off == 0)
1226 return (-1);
1228 if (strcmp(dbname, NSS_DBNAM_HOSTS) != 0 &&
1229 strcmp(dbname, NSS_DBNAM_IPNODES) != 0)
1230 return (-1);
1232 ttl = *(nssuint_t *)((void *)((char *)pbuf + phdr->ext_off));
1234 _NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_DEBUG)
1235 (me, "dns ttl is %d seconds\n", ttl);
1237 return (ttl);
1240 static int
1241 check_config(nsc_lookup_args_t *largs, nscd_cfg_cache_t *cfgp,
1242 char *whoami, int flag)
1244 nsc_db_t *nscdb;
1245 nsc_ctx_t *ctx;
1246 char *me = "check_config";
1248 ctx = largs->ctx;
1249 nscdb = largs->nscdb;
1251 /* see if the cached config needs update */
1252 if (nscdb->cfg_mtime != ctx->cfg_mtime) {
1253 (void) rw_rdlock(&ctx->cfg_rwlp);
1254 nscdb->cfg = ctx->cfg;
1255 nscdb->cfg_mtime = ctx->cfg_mtime;
1256 (void) rw_unlock(&ctx->cfg_rwlp);
1257 _NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_DEBUG)
1258 (me, "config for context %s, database %s updated\n",
1259 ctx->dbname, nscdb->name);
1261 *cfgp = nscdb->cfg;
1263 if (cfgp->enable == nscd_false) {
1264 _NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_DEBUG)
1265 (me, "%s: cache disabled\n", ctx->dbname);
1267 if (UPDATEBIT & flag)
1268 return (NOTFOUND);
1269 else
1270 return (nsc_lookup_no_cache(largs, whoami));
1274 * if caller requests lookup using its
1275 * own nsswitch config, bypass cache
1277 if (nsw_config_in_phdr(largs->buffer))
1278 return (nsc_lookup_no_cache(largs, whoami));
1280 /* no need of cache if we are dealing with 0 ttls */
1281 if (cfgp->pos_ttl <= 0 && cfgp->neg_ttl <= 0) {
1282 if (flag & UPDATEBIT)
1283 return (NOTFOUND);
1284 else if (cfgp->avoid_ns == nscd_true)
1285 return (SERVERERROR);
1286 return (nsc_lookup_no_cache(largs, whoami));
1289 return (CONTINUE);
1293 * Invalidate cache if database file has been modified.
1294 * See check_files config param for details.
1296 static void
1297 check_db_file(nsc_ctx_t *ctx, nscd_cfg_cache_t cfg,
1298 char *whoami, time_t now)
1300 struct stat buf;
1301 nscd_bool_t file_modified = nscd_false;
1302 char *me = "check_db_file";
1304 if (cfg.check_interval != 0 &&
1305 (now - ctx->file_chktime) < cfg.check_interval)
1306 return;
1308 ctx->file_chktime = now;
1309 if (stat(ctx->file_name, &buf) == 0) {
1310 if (ctx->file_mtime == 0) {
1311 (void) mutex_lock(&ctx->file_mutex);
1312 if (ctx->file_mtime == 0) {
1313 ctx->file_mtime = buf.st_mtime;
1314 ctx->file_size = buf.st_size;
1315 ctx->file_ino = buf.st_ino;
1317 (void) mutex_unlock(&ctx->file_mutex);
1318 } else if (ctx->file_mtime < buf.st_mtime ||
1319 ctx->file_size != buf.st_size ||
1320 ctx->file_ino != buf.st_ino) {
1321 (void) mutex_lock(&ctx->file_mutex);
1322 if (ctx->file_mtime < buf.st_mtime ||
1323 ctx->file_size != buf.st_size ||
1324 ctx->file_ino != buf.st_ino) {
1325 file_modified = nscd_true;
1326 ctx->file_mtime = buf.st_mtime;
1327 ctx->file_size = buf.st_size;
1328 ctx->file_ino = buf.st_ino;
1330 (void) mutex_unlock(&ctx->file_mutex);
1334 if (file_modified == nscd_true) {
1335 _NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_DEBUG)
1336 (me, "%s: file %s has been modified - invalidating cache\n",
1337 whoami, ctx->file_name);
1338 ctx_invalidate(ctx);
1342 static int
1343 lookup_int(nsc_lookup_args_t *largs, int flag)
1345 nsc_ctx_t *ctx;
1346 nsc_db_t *nscdb;
1347 nscd_cfg_cache_t cfg;
1348 nsc_entry_t *this_entry;
1349 nsc_entry_stat_t *this_stats;
1350 nsc_action_t next_action;
1351 nss_status_t status;
1352 nscd_bool_t delete;
1353 nscd_rc_t rc;
1354 char *dbname;
1355 int dbop, errnum;
1356 int cfg_rc;
1357 nss_XbyY_args_t args;
1358 char whoami[128];
1359 time_t now = time(NULL); /* current time */
1360 char *me = "lookup_int";
1362 /* extract dbop, dbname, key and cred */
1363 status = nss_packed_getkey(largs->buffer, largs->bufsize, &dbname,
1364 &dbop, &args);
1365 if (status != NSS_SUCCESS) {
1366 _NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_ERROR)
1367 (me, "nss_packed_getkey failure (%d)\n", status);
1368 return (SERVERERROR);
1371 /* get the cache context */
1372 if (largs->ctx == NULL) {
1373 if (get_cache_ctx(dbname, &largs->ctx) != NSCD_SUCCESS) {
1374 _NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_WARNING)
1375 (me, "%s: no cache context found\n", dbname);
1377 if (UPDATEBIT & flag)
1378 return (NOTFOUND);
1379 else
1380 return (nsc_lookup_no_cache(largs, dbname));
1383 ctx = largs->ctx;
1385 if (largs->nscdb == NULL) {
1386 if ((largs->nscdb = nsc_get_db(ctx, dbop)) == NULL) {
1387 _NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_WARNING)
1388 (me, "%s:%d: no cache found\n",
1389 dbname, dbop);
1391 if (UPDATEBIT & flag)
1392 return (NOTFOUND);
1393 else
1394 return (nsc_lookup_no_cache(largs, dbname));
1398 nscdb = largs->nscdb;
1400 _NSCD_LOG_IF(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_ALL) {
1401 (void) nscdb->getlogstr(nscdb->name, whoami,
1402 sizeof (whoami), &args);
1405 if (UPDATEBIT & flag) {
1406 _NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_DEBUG)
1407 (me, "%s: refresh start\n", whoami);
1408 } else {
1409 _NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_DEBUG)
1410 (me, "%s: lookup start\n", whoami);
1413 cfg_rc = check_config(largs, &cfg, whoami, flag);
1414 if (cfg_rc != CONTINUE)
1415 return (cfg_rc);
1418 * Invalidate cache if file has been modified.
1420 if (cfg.check_files == nscd_true)
1421 check_db_file(ctx, cfg, whoami, now);
1423 (void) mutex_lock(&nscdb->db_mutex);
1425 /* Lookup the cache table */
1426 for (;;) {
1427 delete = nscd_false;
1428 rc = lookup_cache(largs, &cfg, &args, whoami, &this_entry);
1429 if (rc != NSCD_SUCCESS) {
1430 (void) mutex_unlock(&nscdb->db_mutex);
1432 /* Either no entry and avoid name service */
1433 if (rc == NSCD_DB_ENTRY_NOT_FOUND ||
1434 rc == NSCD_INVALID_ARGUMENT)
1435 return (NOTFOUND);
1437 /* OR memory error */
1438 return (SERVERERROR);
1441 /* get the stats from the entry */
1442 this_stats = &this_entry->stats;
1445 * What should we do next ?
1447 switch (this_stats->status) {
1448 case ST_NEW_ENTRY:
1449 delete = nscd_true;
1450 next_action = _NSC_NSLOOKUP;
1451 break;
1452 case ST_UPDATE_PENDING:
1453 if (flag & UPDATEBIT) {
1454 (void) mutex_unlock(&nscdb->db_mutex);
1455 return (NOTFOUND);
1456 } else if (this_stats->timestamp < now)
1457 next_action = _NSC_WAIT;
1458 else
1459 next_action = _NSC_USECACHED;
1460 break;
1461 case ST_LOOKUP_PENDING:
1462 if (flag & UPDATEBIT) {
1463 (void) mutex_unlock(&nscdb->db_mutex);
1464 return (NOTFOUND);
1466 next_action = _NSC_WAIT;
1467 break;
1468 case ST_DISCARD:
1469 if (cfg.avoid_ns == nscd_true) {
1470 (void) mutex_unlock(&nscdb->db_mutex);
1471 return (NOTFOUND);
1473 /* otherwise reuse the entry */
1474 (void) memset(this_stats, 0, sizeof (*this_stats));
1475 next_action = _NSC_NSLOOKUP;
1476 break;
1477 default:
1478 if (cfg.avoid_ns == nscd_true)
1479 next_action = _NSC_USECACHED;
1480 else if ((flag & UPDATEBIT) ||
1481 (this_stats->timestamp < now)) {
1482 _NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_DEBUG)
1483 (me, "%s: cached entry needs to be updated\n",
1484 whoami);
1485 next_action = _NSC_NSLOOKUP;
1486 } else
1487 next_action = _NSC_USECACHED;
1488 break;
1491 if (next_action == _NSC_WAIT) {
1492 _NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_DEBUG)
1493 (me, "%s: need to wait\n", whoami);
1495 /* do we have clearance ? */
1496 if (_nscd_get_clearance(&ctx->throttle_sema) != 0) {
1497 /* nope. quit */
1498 (void) mutex_lock(&ctx->stats_mutex);
1499 ctx->stats.drop_count++;
1500 (void) mutex_unlock(&ctx->stats_mutex);
1501 _NSCD_LOG(NSCD_LOG_CACHE,
1502 NSCD_LOG_LEVEL_DEBUG_6)
1503 (me, "%s: throttling load\n", whoami);
1504 (void) mutex_unlock(&nscdb->db_mutex);
1505 NSC_LOOKUP_LOG(WARNING,
1506 "%s: no clearance to wait\n");
1507 return (NOSERVER);
1509 /* yes can wait */
1510 (void) nscd_wait(ctx, nscdb, this_entry);
1511 (void) _nscd_release_clearance(&ctx->throttle_sema);
1512 continue;
1515 break;
1519 if (!(UPDATEBIT & flag))
1520 this_stats->hits++; /* update hit count */
1522 if (next_action == _NSC_NSLOOKUP) {
1524 _NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_DEBUG)
1525 (me, "%s: name service lookup required\n", whoami);
1527 if (_nscd_get_clearance(&ctx->throttle_sema) != 0) {
1528 if (delete == nscd_true)
1529 delete_entry(nscdb, ctx, this_entry);
1530 else
1531 this_stats->status = ST_DISCARD;
1532 (void) mutex_lock(&ctx->stats_mutex);
1533 ctx->stats.drop_count++;
1534 (void) mutex_unlock(&ctx->stats_mutex);
1535 (void) mutex_unlock(&nscdb->db_mutex);
1536 NSC_LOOKUP_LOG(WARNING,
1537 "%s: no clearance for lookup\n");
1538 return (NOSERVER);
1541 /* block any threads accessing this entry */
1542 this_stats->status = (flag & UPDATEBIT) ?
1543 ST_UPDATE_PENDING : ST_LOOKUP_PENDING;
1545 /* release lock and do name service lookup */
1546 (void) mutex_unlock(&nscdb->db_mutex);
1547 nss_psearch(largs->buffer, largs->bufsize);
1548 status = NSCD_GET_STATUS(largs->buffer);
1549 (void) mutex_lock(&nscdb->db_mutex);
1550 this_stats->status = 0;
1551 (void) _nscd_release_clearance(&ctx->throttle_sema);
1553 /* signal waiting threads */
1554 (void) nscd_signal(ctx, nscdb, this_entry);
1556 _NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_DEBUG)
1557 (me, "%s: name service lookup status = %d\n",
1558 whoami, status);
1560 if (status == NSS_SUCCESS) {
1561 int ttl;
1564 * data found in name service
1565 * update cache
1567 status = dup_packed_buffer(largs, this_entry);
1568 if (status != NSS_SUCCESS) {
1569 delete_entry(nscdb, ctx, this_entry);
1570 (void) mutex_unlock(&nscdb->db_mutex);
1571 NSC_LOOKUP_LOG(ERROR,
1572 "%s: failed to update cache\n");
1573 return (SERVERERROR);
1577 * store unpacked key in cache
1579 status = nss_packed_getkey(this_entry->buffer,
1580 this_entry->bufsize,
1581 &dbname, &dbop, &args);
1582 if (status != NSS_SUCCESS) {
1583 delete_entry(nscdb, ctx, this_entry);
1584 (void) mutex_unlock(&nscdb->db_mutex);
1585 NSC_LOOKUP_LOG(ERROR,
1586 "%s: failed to extract key\n");
1587 return (SERVERERROR);
1589 this_entry->key = args.key; /* struct copy */
1591 /* update +ve miss count */
1592 if (!(UPDATEBIT & flag)) {
1593 (void) mutex_lock(&ctx->stats_mutex);
1594 ctx->stats.pos_misses++;
1595 (void) mutex_unlock(&ctx->stats_mutex);
1598 /* update +ve ttl */
1599 ttl = get_dns_ttl(largs->buffer, dbname);
1600 /* honor the dns ttl less than postive ttl */
1601 if (ttl < 0 || ttl > cfg.pos_ttl)
1602 ttl = cfg.pos_ttl;
1603 this_stats->timestamp = time(NULL) + ttl;
1606 * start the revalidation and reaper threads
1607 * if not already started
1609 start_threads(ctx);
1611 (void) mutex_unlock(&nscdb->db_mutex);
1612 NSC_LOOKUP_LOG(DEBUG,
1613 "%s: cache updated with positive entry\n");
1614 return (SUCCESS);
1615 } else if (status == NSS_NOTFOUND) {
1617 * data not found in name service
1618 * update cache
1620 _NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_DEBUG_6)
1621 (me, "%s: name service lookup failed\n", whoami);
1623 if (NSCD_GET_ERRNO(largs->buffer) == ERANGE) {
1624 delete_entry(nscdb, ctx, this_entry);
1625 (void) mutex_unlock(&nscdb->db_mutex);
1626 NSC_LOOKUP_LOG(DEBUG,
1627 "%s: ERANGE, cache not updated "
1628 "with negative entry\n");
1629 return (NOTFOUND);
1632 status = dup_packed_buffer(largs, this_entry);
1633 if (status != NSS_SUCCESS) {
1634 delete_entry(nscdb, ctx, this_entry);
1635 (void) mutex_unlock(&nscdb->db_mutex);
1636 NSC_LOOKUP_LOG(ERROR,
1637 "%s: failed to update cache\n");
1638 return (SERVERERROR);
1641 /* store unpacked key in cache */
1642 status = nss_packed_getkey(this_entry->buffer,
1643 this_entry->bufsize,
1644 &dbname, &dbop, &args);
1645 if (status != NSS_SUCCESS) {
1646 delete_entry(nscdb, ctx, this_entry);
1647 (void) mutex_unlock(&nscdb->db_mutex);
1648 NSC_LOOKUP_LOG(ERROR,
1649 "%s: failed to extract key\n");
1650 return (SERVERERROR);
1652 this_entry->key = args.key; /* struct copy */
1654 /* update -ve ttl */
1655 this_stats->timestamp = time(NULL) + cfg.neg_ttl;
1657 /* update -ve miss count */
1658 if (!(UPDATEBIT & flag)) {
1659 (void) mutex_lock(&ctx->stats_mutex);
1660 ctx->stats.neg_misses++;
1661 (void) mutex_unlock(&ctx->stats_mutex);
1665 * start the revalidation and reaper threads
1666 * if not already started
1668 start_threads(ctx);
1670 (void) mutex_unlock(&nscdb->db_mutex);
1671 NSC_LOOKUP_LOG(DEBUG,
1672 "%s: cache updated with negative entry\n");
1673 return (NOTFOUND);
1674 } else {
1676 * name service lookup failed
1678 _NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_DEBUG_6)
1679 (me, "%s: name service lookup failed\n", whoami);
1681 errnum = NSCD_GET_ERRNO(largs->buffer);
1682 if (delete == nscd_true)
1683 delete_entry(nscdb, ctx, this_entry);
1684 else
1685 this_stats->status = ST_DISCARD;
1687 (void) mutex_unlock(&nscdb->db_mutex);
1688 _NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_WARNING)
1689 (me, "%s: name service lookup failed "
1690 "(status=%d, errno=%d)\n",
1691 whoami, status, errnum);
1693 return (SERVERERROR);
1695 } else if (next_action == _NSC_USECACHED) {
1697 * found entry in cache
1699 if (UPDATEBIT & flag) {
1700 (void) mutex_unlock(&nscdb->db_mutex);
1701 NSC_LOOKUP_LOG(DEBUG, "%s: no need to update\n");
1702 return (SUCCESS);
1705 if (NSCD_GET_STATUS((nss_pheader_t *)this_entry->buffer) ==
1706 NSS_SUCCESS) {
1707 /* positive hit */
1708 (void) mutex_lock(&ctx->stats_mutex);
1709 ctx->stats.pos_hits++;
1710 (void) mutex_unlock(&ctx->stats_mutex);
1712 /* update response buffer */
1713 if (copy_result(largs->buffer,
1714 this_entry->buffer) != NSS_SUCCESS) {
1715 (void) mutex_unlock(&nscdb->db_mutex);
1716 NSC_LOOKUP_LOG(ERROR,
1717 "%s: response buffer insufficient\n");
1718 return (SERVERERROR);
1721 (void) mutex_unlock(&nscdb->db_mutex);
1722 NSC_LOOKUP_LOG(DEBUG,
1723 "%s: positive entry in cache\n");
1724 return (SUCCESS);
1725 } else {
1726 /* negative hit */
1727 (void) mutex_lock(&ctx->stats_mutex);
1728 ctx->stats.neg_hits++;
1729 (void) mutex_unlock(&ctx->stats_mutex);
1731 NSCD_SET_STATUS((nss_pheader_t *)largs->buffer,
1732 NSCD_GET_STATUS(this_entry->buffer),
1733 NSCD_GET_ERRNO(this_entry->buffer));
1734 NSCD_SET_HERRNO((nss_pheader_t *)largs->buffer,
1735 NSCD_GET_HERRNO(this_entry->buffer));
1737 (void) mutex_unlock(&nscdb->db_mutex);
1738 NSC_LOOKUP_LOG(DEBUG,
1739 "%s: negative entry in cache\n");
1740 return (NOTFOUND);
1744 (void) mutex_unlock(&nscdb->db_mutex);
1745 NSC_LOOKUP_LOG(ERROR, "%s: cache backend failure\n");
1746 return (SERVERERROR);
1750 * NSCD cache backend lookup function
1752 /*ARGSUSED*/
1753 void
1754 nsc_lookup(nsc_lookup_args_t *largs, int flag) {
1756 nss_pheader_t *phdr = (nss_pheader_t *)largs->buffer;
1757 int rc;
1759 rc = lookup_int(largs, 0);
1761 if (NSCD_GET_STATUS(phdr) == NSS_TRYLOCAL)
1762 return;
1764 switch (rc) {
1766 case SUCCESS:
1767 NSCD_SET_STATUS(phdr, NSS_SUCCESS, 0);
1768 break;
1770 case NOTFOUND:
1771 NSCD_SET_STATUS(phdr, NSS_NOTFOUND, -1);
1772 break;
1774 case SERVERERROR:
1776 * status and errno should have been set in the phdr,
1777 * if not, set status to NSS_ERROR
1779 if (NSCD_STATUS_IS_OK(phdr)) {
1780 NSCD_SET_STATUS(phdr, NSS_ERROR, 0);
1782 break;
1784 case NOSERVER:
1785 NSCD_SET_STATUS(phdr, NSS_TRYLOCAL, -1);
1786 break;
1791 static nsc_ctx_t *
1792 init_cache_ctx(int i) {
1793 nsc_ctx_t *ctx;
1795 ctx = calloc(1, sizeof (nsc_ctx_t));
1796 if (ctx == NULL)
1797 return (NULL);
1799 /* init locks and semaphores */
1800 (void) mutex_init(&ctx->file_mutex, USYNC_THREAD, NULL);
1801 (void) rwlock_init(&ctx->cfg_rwlp, USYNC_THREAD, NULL);
1802 (void) mutex_init(&ctx->stats_mutex, USYNC_THREAD, NULL);
1803 (void) _nscd_init_cache_sema(&ctx->throttle_sema, cache_name[i]);
1804 cache_init_ctx[i](ctx);
1805 cache_ctx_p[i] = ctx;
1807 return (ctx);
1811 static void
1812 revalidate(nsc_ctx_t *ctx)
1814 for (;;) {
1815 int i, slp, interval, count;
1817 (void) rw_rdlock(&ctx->cfg_rwlp);
1818 slp = ctx->cfg.pos_ttl;
1819 count = ctx->cfg.keephot;
1820 (void) rw_unlock(&ctx->cfg_rwlp);
1822 if (slp < 60)
1823 slp = 60;
1824 if (count != 0) {
1825 interval = (slp/2)/count;
1826 if (interval == 0)
1827 interval = 1;
1828 (void) sleep(slp*2/3);
1829 for (i = 0; i < ctx->db_count; i++) {
1830 getxy_keepalive(ctx, ctx->nsc_db[i],
1831 count, interval);
1833 } else {
1834 (void) sleep(slp);
1840 static void
1841 getxy_keepalive(nsc_ctx_t *ctx, nsc_db_t *nscdb, int keep, int interval)
1843 nsc_keephot_t *table;
1844 nsc_entry_t *entry, *ptr;
1845 int i;
1846 nsc_lookup_args_t *largs;
1847 nss_pheader_t *phdr;
1848 int bufsiz;
1849 char *me = "getxy_keepalive";
1851 /* we won't be here if keep == 0 so need to check that */
1853 _NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_DEBUG)
1854 (me, "%s: keep alive\n", nscdb->name);
1856 if ((table = maken(keep)) == NULL) {
1857 _NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_ERROR)
1858 (me, "memory allocation failure\n");
1859 exit(1);
1862 (void) mutex_lock(&nscdb->db_mutex);
1863 entry = nscdb->qtail;
1864 while (entry != NULL) {
1865 /* leave pending calls alone */
1866 if (!(entry->stats.status & ST_PENDING)) {
1867 /* do_revalidate */
1868 (void) insertn(table, entry->stats.hits, entry);
1870 entry = entry->qnext;
1872 for (i = 1; i <= keep; i++) {
1873 if (table[i].ptr == NULL)
1874 continue;
1875 ptr = (nsc_entry_t *)table[i].ptr;
1876 phdr = (nss_pheader_t *)ptr->buffer;
1877 if (NSCD_GET_STATUS(phdr) == NSS_SUCCESS)
1879 * for positive cache, in addition to the packed
1880 * header size, allocate twice the size of the
1881 * existing result (in case the result grows
1882 * larger) plus 2K (for the file/compat backend to
1883 * process a possible large entry in the /etc files)
1885 bufsiz = phdr->data_off + 2 * phdr->data_len + 2048;
1886 else
1888 * for negative cache, allocate 8K buffer to
1889 * hold result in case the next lookup may
1890 * return something (in addition to the
1891 * packed header size)
1893 bufsiz = phdr->data_off + 8096;
1894 table[i].ptr = malloc(bufsiz);
1895 if (table[i].ptr == NULL) {
1896 (void) mutex_unlock(&nscdb->db_mutex);
1897 _NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_ERROR)
1898 (me, "memory allocation failure\n");
1899 exit(1);
1901 (void) memcpy(table[i].ptr, ptr->buffer, ptr->bufsize);
1902 ((nss_pheader_t *)table[i].ptr)->pbufsiz = bufsiz;
1903 table[i].num = bufsiz;
1905 (void) mutex_unlock(&nscdb->db_mutex);
1907 /* launch update thread for each keep hot entry */
1908 for (i = keep; i > 0; i--) {
1909 if (table[i].ptr == NULL)
1910 continue; /* unused slot in table */
1911 _NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_DEBUG)
1912 (me, "%s: launching update\n", nscdb->name);
1913 largs = (nsc_lookup_args_t *)malloc(sizeof (*largs));
1914 if (largs == NULL) {
1915 _NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_ERROR)
1916 (me, "memory allocation failure\n");
1917 exit(1);
1919 largs->buffer = table[i].ptr;
1920 largs->bufsize = table[i].num;
1921 largs->ctx = ctx;
1922 largs->nscdb = nscdb;
1923 if (launch_update(largs) < 0)
1924 exit(1);
1925 (void) sleep(interval);
1929 * The update thread will handle freeing of buffer and largs.
1930 * Free the table here.
1932 free(table);
1936 static int
1937 launch_update(nsc_lookup_args_t *in)
1939 char *me = "launch_update";
1940 int errnum;
1942 errnum = thr_create(NULL, 0, (void *(*)(void*))do_update,
1943 in, 0|THR_DETACHED, NULL);
1944 if (errnum != 0) {
1945 _NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_ERROR)
1946 (me, "%s: thread creation failure (%d)\n",
1947 in->nscdb->name, errnum);
1948 return (-1);
1950 return (0);
1954 static void
1955 do_update(nsc_lookup_args_t *in) {
1956 nss_pheader_t *phdr = (nss_pheader_t *)in->buffer;
1958 /* update the length of the data buffer */
1959 phdr->data_len = phdr->pbufsiz - phdr->data_off;
1961 (void) lookup_int(in, UPDATEBIT);
1962 free(in->buffer);
1963 free(in);
1968 * Invalidate cache
1970 void
1971 nsc_invalidate(nsc_ctx_t *ctx, char *dbname, nsc_ctx_t **ctxs)
1973 int i;
1974 char *me = "nsc_invalidate";
1976 if (ctx) {
1977 ctx_invalidate(ctx);
1978 return;
1981 if (dbname) {
1982 if ((i = get_cache_idx(dbname)) == -1) {
1983 _NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_WARNING)
1984 (me, "%s: invalid cache name\n", dbname);
1985 return;
1987 if ((ctx = cache_ctx_p[i]) == NULL) {
1988 _NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_WARNING)
1989 (me, "%s: no cache context found\n",
1990 dbname);
1991 return;
1993 ctx_invalidate(ctx);
1994 return;
1997 if (ctxs == NULL)
1998 ctxs = cache_ctx_p;
2000 for (i = 0; i < CACHE_CTX_COUNT; i++) {
2001 if (ctxs[i] != NULL)
2002 ctx_invalidate(ctxs[i]);
2008 * Invalidate cache by context
2010 static void
2011 ctx_invalidate(nsc_ctx_t *ctx)
2013 int i;
2014 nsc_entry_t *entry;
2015 char *me = "ctx_invalidate";
2017 _NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_DEBUG)
2018 (me, "%s: invalidate cache\n", ctx->dbname);
2020 for (i = 0; i < ctx->db_count; i++) {
2021 if (ctx->nsc_db[i] == NULL)
2022 continue;
2023 (void) mutex_lock(&ctx->nsc_db[i]->db_mutex);
2024 entry = ctx->nsc_db[i]->qtail;
2025 while (entry != NULL) {
2026 /* leave pending calls alone */
2027 if (!(entry->stats.status & ST_PENDING))
2028 entry->stats.status = ST_DISCARD;
2029 entry = entry->qnext;
2031 (void) mutex_unlock(&ctx->nsc_db[i]->db_mutex);
2034 (void) mutex_lock(&ctx->stats_mutex);
2035 ctx->stats.invalidate_count++;
2036 (void) mutex_unlock(&ctx->stats_mutex);
2041 * Free nsc_entry_t
2043 * Pre-reqs:
2044 * nscdb->db_mutex lock must be held before calling this function
2046 static void
2047 delete_entry(nsc_db_t *nscdb, nsc_ctx_t *ctx, nsc_entry_t *entry) {
2048 uint_t hash;
2050 avl_remove(&nscdb->tree, entry);
2051 HASH_REMOVE(nscdb, entry, hash, nscd_false);
2052 queue_remove(nscdb, entry);
2053 if (entry->buffer != NULL) {
2054 free(entry->buffer);
2055 entry->buffer = NULL;
2057 umem_cache_free(nsc_entry_cache, entry);
2058 (void) mutex_lock(&ctx->stats_mutex);
2059 ctx->stats.entries--;
2060 (void) mutex_unlock(&ctx->stats_mutex);
2064 static nscd_rc_t
2065 lookup_cache(nsc_lookup_args_t *largs, nscd_cfg_cache_t *cfgp,
2066 nss_XbyY_args_t *argp, char *whoami, nsc_entry_t **entry)
2068 nsc_db_t *nscdb;
2069 nsc_ctx_t *ctx;
2070 uint_t hash;
2071 avl_index_t pos;
2072 ulong_t nentries;
2073 nsc_entry_t find_entry, *node;
2074 char *me = "lookup_cache";
2076 ctx = largs->ctx;
2077 nscdb = largs->nscdb;
2079 /* set the search key */
2080 find_entry.key = argp->key; /* struct copy (not deep) */
2082 /* lookup the hash table ==> O(1) */
2083 if (nscdb->htable) {
2084 *entry = hash_find(nscdb, &find_entry, &hash, nscd_true);
2085 if (*entry != NULL) {
2086 (void) queue_adjust(nscdb, *entry);
2087 return (NSCD_SUCCESS);
2091 /* if not found, lookup the AVL tree ==> O(log n) */
2092 *entry = (nsc_entry_t *)avl_find(&nscdb->tree, &find_entry, &pos);
2093 if (*entry != NULL) {
2094 (void) queue_adjust(nscdb, *entry);
2095 /* move it to the hash table */
2096 if (nscdb->htable) {
2097 if (nscdb->htable[hash] == NULL ||
2098 (*entry)->stats.hits >=
2099 nscdb->htable[hash]->stats.hits) {
2100 nscdb->htable[hash] = *entry;
2103 return (NSCD_SUCCESS);
2106 /* entry not found in the cache */
2107 _NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_DEBUG)
2108 (me, "%s: cache miss\n", whoami);
2110 if (cfgp->avoid_ns == nscd_true) {
2111 _NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_DEBUG)
2112 (me, "%s: avoid name service\n", whoami);
2113 return (NSCD_DB_ENTRY_NOT_FOUND);
2116 /* allocate memory for new entry (stub) */
2117 *entry = (nsc_entry_t *)umem_cache_alloc(nsc_entry_cache,
2118 UMEM_DEFAULT);
2119 if (*entry == NULL) {
2120 _NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_ERROR)
2121 (me, "%s: memory allocation failure\n", whoami);
2122 return (NSCD_NO_MEMORY);
2124 (void) memset(*entry, 0, sizeof (**entry));
2127 * Note that the actual data for the key is stored within
2128 * the largs->buffer (input buffer to nsc_lookup).
2129 * find_entry.key only contains pointers to this data.
2131 * If largs->buffer will be re-allocated by nss_psearch
2132 * then (*entry)->key will have dangling pointers.
2133 * In such case, the following assignment needs to be
2134 * replaced by code that duplicates the key.
2136 (*entry)->key = find_entry.key;
2139 * Add the entry to the cache.
2141 avl_insert(&nscdb->tree, *entry, pos); /* O(log n) */
2142 (void) queue_adjust(nscdb, *entry); /* constant */
2143 if (nscdb->htable) /* constant */
2144 nscdb->htable[hash] = *entry;
2145 (*entry)->stats.status = ST_NEW_ENTRY;
2147 (void) mutex_lock(&ctx->stats_mutex);
2148 nentries = ++(ctx->stats.entries);
2149 (void) mutex_unlock(&ctx->stats_mutex);
2151 /* Have we exceeded max entries ? */
2152 if (cfgp->maxentries > 0 && nentries > cfgp->maxentries) {
2153 _NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_DEBUG)
2154 (me, "%s: maximum entries exceeded -- "
2155 "deleting least recently used entry\n",
2156 whoami);
2158 node = nscdb->qhead;
2159 while (node != NULL && node != *entry) {
2160 if (node->stats.status == ST_DISCARD ||
2161 !(node->stats.status & ST_PENDING)) {
2162 delete_entry(nscdb, ctx, node);
2163 break;
2165 node = node->qprev;
2169 * It's okay if we were not able to find one to delete.
2170 * The reaper (when invoked) will return the cache to a
2171 * safe level.
2175 return (NSCD_SUCCESS);
2178 static void
2179 reaper(nsc_ctx_t *ctx)
2181 uint_t ttl, extra_sleep, total_sleep, intervals;
2182 uint_t nodes_per_interval, seconds_per_interval;
2183 ulong_t nsc_entries;
2184 char *me = "reaper";
2186 for (;;) {
2187 (void) mutex_lock(&ctx->stats_mutex);
2188 nsc_entries = ctx->stats.entries;
2189 (void) mutex_unlock(&ctx->stats_mutex);
2191 (void) rw_rdlock(&ctx->cfg_rwlp);
2192 ttl = ctx->cfg.pos_ttl;
2193 (void) rw_unlock(&ctx->cfg_rwlp);
2195 if (nsc_entries == 0) {
2196 _NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_DEBUG)
2197 (me, "%s: nothing to reap\n", ctx->dbname);
2199 /* sleep for atleast 60 seconds */
2200 if (ttl < 60)
2201 ttl = 60;
2202 _NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_DEBUG)
2203 (me, "%s: sleep %d\n", ctx->dbname, ttl);
2204 (void) sleep(ttl);
2205 continue;
2208 if (ttl < 32) ttl = 32;
2209 if (ttl > (1<<28)) ttl = 1<<28;
2212 * minimum nodes_per_interval = 256 or 1<<8
2213 * maximum nodes_per_interval = nsc_entries
2214 * minimum seconds_per_interval = 32 or 1<<5
2215 * maximum_seconds_per_interval = ttl
2217 if (nsc_entries <= ttl) {
2218 intervals = (nsc_entries >> 8) + 1;
2219 seconds_per_interval = ttl / intervals;
2220 nodes_per_interval = 256;
2221 } else {
2222 intervals = (ttl >> 5) + 1;
2223 seconds_per_interval = 32;
2224 nodes_per_interval = nsc_entries / intervals;
2225 if (nodes_per_interval < 256)
2226 nodes_per_interval = 256;
2229 _NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_DEBUG)
2230 (me, "%s: total entries = %d, "
2231 "seconds per interval = %d, "
2232 "nodes per interval = %d\n",
2233 ctx->dbname, nsc_entries, seconds_per_interval,
2234 nodes_per_interval);
2235 total_sleep = reap_cache(ctx, nodes_per_interval,
2236 seconds_per_interval);
2237 extra_sleep = 1 + ttl - total_sleep;
2238 if (extra_sleep > 0)
2239 (void) sleep(extra_sleep);
2244 static uint_t
2245 reap_cache(nsc_ctx_t *ctx, uint_t nodes_per_interval,
2246 uint_t seconds_per_interval)
2248 uint_t nodes_togo, total_sleep;
2249 time_t now;
2250 nsc_entry_t *node, *next_node;
2251 nsc_db_t *nscdb;
2252 uint_t primes[] = {_NSC_HTSIZE_PRIMES};
2253 ulong_t count, nentries, maxentries;
2254 int i, slot, value, newhtsize;
2255 char *me = "reap_cache";
2257 count = 0;
2258 total_sleep = 0;
2259 nodes_togo = nodes_per_interval;
2260 now = time(NULL);
2262 for (i = 0; i < ctx->db_count; i++) {
2263 nscdb = ctx->nsc_db[i];
2264 (void) mutex_lock(&nscdb->db_mutex);
2265 nscdb->reap_node = nscdb->qtail;
2266 while (nscdb->reap_node != NULL) {
2267 if (nodes_togo == 0) {
2268 (void) mutex_unlock(&nscdb->db_mutex);
2269 (void) sleep(seconds_per_interval);
2270 total_sleep += seconds_per_interval;
2271 nodes_togo = nodes_per_interval;
2272 now = time(NULL);
2273 (void) mutex_lock(&nscdb->db_mutex);
2275 /* delete ST_DISCARD and expired nodes */
2276 if ((node = nscdb->reap_node) == NULL)
2277 break;
2278 if (node->stats.status == ST_DISCARD ||
2279 (!(node->stats.status & ST_PENDING) &&
2280 node->stats.timestamp < now)) {
2282 * Delete entry if its discard flag is
2283 * set OR if it has expired. Entries
2284 * with pending updates are not
2285 * deleted.
2286 * nscdb->reap_node will be adjusted
2287 * by delete_entry()
2289 delete_entry(nscdb, ctx, node);
2290 count++;
2291 } else {
2292 nscdb->reap_node = node->qnext;
2294 nodes_togo--;
2297 if (nscdb->htsize == 0) {
2298 (void) mutex_unlock(&nscdb->db_mutex);
2299 continue;
2303 * Dynamic adjustment of hash table size.
2305 * Hash table size is roughly 1/8th of the
2306 * total entries. However the size is changed
2307 * only when the number of entries double or
2308 * reduced by half
2310 nentries = avl_numnodes(&nscdb->tree);
2311 for (slot = 0, value = _NSC_INIT_HTSIZE_SLOT_VALUE;
2312 slot < _NSC_HTSIZE_NUM_SLOTS && nentries > value;
2313 value = (value << 1) + 1, slot++)
2315 if (nscdb->hash_type == nsc_ht_power2)
2316 newhtsize = _NSC_INIT_HTSIZE_POWER2 << slot;
2317 else
2318 newhtsize = primes[slot];
2320 /* Recommended size is same as the current size. Done */
2321 if (nscdb->htsize == newhtsize) {
2322 (void) mutex_unlock(&nscdb->db_mutex);
2323 continue;
2326 _NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_DEBUG)
2327 (me, "%s: resizing hash table from %d to %d\n",
2328 nscdb->name, nscdb->htsize, newhtsize);
2331 * Dump old hashes because it would be time
2332 * consuming to rehash them.
2334 (void) free(nscdb->htable);
2335 nscdb->htable = calloc(newhtsize, sizeof (*(nscdb->htable)));
2336 if (nscdb->htable == NULL) {
2337 _NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_ERROR)
2338 (me, "%s: memory allocation failure\n",
2339 nscdb->name);
2340 /* -1 to try later */
2341 nscdb->htsize = -1;
2342 } else {
2343 nscdb->htsize = newhtsize;
2345 (void) mutex_unlock(&nscdb->db_mutex);
2348 _NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_DEBUG)
2349 (me, "%s: reaped %lu entries\n", ctx->dbname, count);
2352 * if cache is almost full then reduce it to a safe level by
2353 * evicting LRU entries
2356 (void) rw_rdlock(&ctx->cfg_rwlp);
2357 maxentries = ctx->cfg.maxentries;
2358 (void) rw_unlock(&ctx->cfg_rwlp);
2360 /* No limit on number of entries. Done */
2361 if (maxentries == 0)
2362 goto out;
2364 (void) mutex_lock(&ctx->stats_mutex);
2365 nentries = ctx->stats.entries;
2366 (void) mutex_unlock(&ctx->stats_mutex);
2368 /* what is the percentage of cache used ? */
2369 value = (nentries * 100) / maxentries;
2370 if (value < _NSC_EVICTION_START_LEVEL)
2371 goto out;
2374 * cache needs to be reduced to a safe level
2376 value -= _NSC_EVICTION_SAFE_LEVEL;
2377 for (i = 0, count = 0; i < ctx->db_count; i++) {
2379 * Reduce each subcache by 'value' percent
2381 nscdb = ctx->nsc_db[i];
2382 (void) mutex_lock(&nscdb->db_mutex);
2383 nodes_togo = (value * avl_numnodes(&nscdb->tree)) / 100;
2385 /* Start from LRU entry i.e queue head */
2386 next_node = nscdb->qhead;
2387 while (nodes_togo > 0 && next_node != NULL) {
2388 node = next_node;
2389 next_node = next_node->qprev;
2390 if (node->stats.status == ST_DISCARD ||
2391 !(node->stats.status & ST_PENDING)) {
2392 /* Leave nodes with pending updates alone */
2393 delete_entry(nscdb, ctx, node);
2394 count++;
2395 nodes_togo--;
2398 (void) mutex_unlock(&nscdb->db_mutex);
2401 _NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_DEBUG)
2402 (me, "%s: evicted %lu LRU entries\n", ctx->dbname, count);
2404 out:
2405 return (total_sleep);