kill tsol ("Trusted Solaris") aka TX ("Trusted Extensions")
[unleashed.git] / usr / src / cmd / nscd / cache.c
blobf0d206a32dc81a622d27ad4a937751f827559093
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.
27 * Cache routines for nscd
29 #include <assert.h>
30 #include <errno.h>
31 #include <memory.h>
32 #include <signal.h>
33 #include <stdlib.h>
34 #include <stddef.h>
35 #include <stdio.h>
36 #include <string.h>
37 #include <sys/stat.h>
38 #include <sys/time.h>
39 #include <sys/types.h>
40 #include <sys/wait.h>
41 #include <unistd.h>
42 #include <ucred.h>
43 #include <nss_common.h>
44 #include <locale.h>
45 #include <ctype.h>
46 #include <strings.h>
47 #include <string.h>
48 #include <umem.h>
49 #include <fcntl.h>
50 #include "cache.h"
51 #include "nscd_door.h"
52 #include "nscd_log.h"
53 #include "nscd_config.h"
54 #include "nscd_frontend.h"
55 #include "nscd_switch.h"
57 #define SUCCESS 0
58 #define NOTFOUND -1
59 #define SERVERERROR -2
60 #define NOSERVER -3
61 #define CONTINUE -4
63 static nsc_db_t *nsc_get_db(nsc_ctx_t *, int);
64 static nscd_rc_t lookup_cache(nsc_lookup_args_t *, nscd_cfg_cache_t *,
65 nss_XbyY_args_t *, char *, nsc_entry_t **);
66 static uint_t reap_cache(nsc_ctx_t *, uint_t, uint_t);
67 static void delete_entry(nsc_db_t *, nsc_ctx_t *, nsc_entry_t *);
68 static void print_stats(nscd_cfg_stat_cache_t *);
69 static void print_cfg(nscd_cfg_cache_t *);
70 static int lookup_int(nsc_lookup_args_t *, int);
72 #ifdef NSCD_DEBUG
73 static void print_entry(nsc_db_t *, time_t, nsc_entry_t *);
74 static void avl_dump(nsc_db_t *, time_t);
75 static void hash_dump(nsc_db_t *, time_t);
76 #endif /* NSCD_DEBUG */
77 static nsc_entry_t *hash_find(nsc_db_t *, nsc_entry_t *, uint_t *, nscd_bool_t);
79 static void queue_adjust(nsc_db_t *, nsc_entry_t *);
80 static void queue_remove(nsc_db_t *, nsc_entry_t *);
81 #ifdef NSCD_DEBUG
82 static void queue_dump(nsc_db_t *, time_t);
83 #endif /* NSCD_DEBUG */
85 static int launch_update(nsc_lookup_args_t *);
86 static void do_update(nsc_lookup_args_t *);
87 static void getxy_keepalive(nsc_ctx_t *, nsc_db_t *, int, int);
89 static void ctx_info(nsc_ctx_t *);
90 static void ctx_info_nolock(nsc_ctx_t *);
91 static void ctx_invalidate(nsc_ctx_t *);
93 static void nsc_db_str_key_getlogstr(char *, char *, size_t, nss_XbyY_args_t *);
94 static void nsc_db_int_key_getlogstr(char *, char *, size_t, nss_XbyY_args_t *);
95 static void nsc_db_any_key_getlogstr(char *, char *, size_t, nss_XbyY_args_t *);
97 static int nsc_db_cis_key_compar(const void *, const void *);
98 static int nsc_db_ces_key_compar(const void *, const void *);
99 static int nsc_db_int_key_compar(const void *, const void *);
101 static uint_t nsc_db_cis_key_gethash(nss_XbyY_key_t *, int);
102 static uint_t nsc_db_ces_key_gethash(nss_XbyY_key_t *, int);
103 static uint_t nsc_db_int_key_gethash(nss_XbyY_key_t *, int);
105 static umem_cache_t *nsc_entry_cache;
107 static nsc_ctx_t *init_cache_ctx(int);
108 static void reaper(nsc_ctx_t *);
109 static void revalidate(nsc_ctx_t *);
111 static nss_status_t
112 dup_packed_buffer(void *src, void *dst) {
113 nsc_lookup_args_t *s = (nsc_lookup_args_t *)src;
114 nsc_entry_t *d = (nsc_entry_t *)dst;
115 nss_pheader_t *sphdr = (nss_pheader_t *)s->buffer;
116 nss_pheader_t *dphdr = (nss_pheader_t *)d->buffer;
117 int slen, new_pbufsiz = 0;
119 if (NSCD_GET_STATUS(sphdr) != NSS_SUCCESS) {
121 /* no result, copy header only (status, errno, etc) */
122 slen = sphdr->data_off;
123 } else {
125 * lookup result returned, data to copy is the packed
126 * header plus result (add 1 for the terminating NULL
127 * just in case)
129 slen = sphdr->data_off + sphdr->data_len + 1;
132 /* allocate cache packed buffer */
133 if (dphdr != NULL && d->bufsize <= slen && d->bufsize != 0) {
134 /* old buffer too small, free it */
135 free(dphdr);
136 d->buffer = NULL;
137 d->bufsize = 0;
138 dphdr = NULL;
140 if (dphdr == NULL) {
141 /* get new buffer */
142 dphdr = calloc(1, slen + 1);
143 if (dphdr == NULL)
144 return (NSS_ERROR);
145 d->buffer = dphdr;
146 d->bufsize = slen + 1;
147 new_pbufsiz = slen + 1;
150 (void) memcpy(dphdr, sphdr, slen);
151 if (new_pbufsiz != 0)
152 dphdr->pbufsiz = new_pbufsiz;
154 return (NSS_SUCCESS);
157 char *cache_name[CACHE_CTX_COUNT] = {
158 NSS_DBNAM_PASSWD,
159 NSS_DBNAM_GROUP,
160 NSS_DBNAM_HOSTS,
161 NSS_DBNAM_IPNODES,
162 NSS_DBNAM_EXECATTR,
163 NSS_DBNAM_PROFATTR,
164 NSS_DBNAM_USERATTR,
165 NSS_DBNAM_ETHERS,
166 NSS_DBNAM_RPC,
167 NSS_DBNAM_PROTOCOLS,
168 NSS_DBNAM_NETWORKS,
169 NSS_DBNAM_BOOTPARAMS,
170 NSS_DBNAM_AUTHATTR,
171 NSS_DBNAM_SERVICES,
172 NSS_DBNAM_NETMASKS,
173 NSS_DBNAM_PRINTERS,
174 NSS_DBNAM_PROJECT
177 typedef void (*cache_init_ctx_t)(nsc_ctx_t *);
178 static cache_init_ctx_t cache_init_ctx[CACHE_CTX_COUNT] = {
179 passwd_init_ctx,
180 group_init_ctx,
181 host_init_ctx,
182 ipnode_init_ctx,
183 exec_init_ctx,
184 prof_init_ctx,
185 user_init_ctx,
186 ether_init_ctx,
187 rpc_init_ctx,
188 proto_init_ctx,
189 net_init_ctx,
190 bootp_init_ctx,
191 auth_init_ctx,
192 serv_init_ctx,
193 netmask_init_ctx,
194 printer_init_ctx,
195 project_init_ctx,
198 nsc_ctx_t *cache_ctx_p[CACHE_CTX_COUNT] = { 0 };
199 static nscd_cfg_stat_cache_t null_stats = { 0 };
200 static nscd_cfg_global_cache_t global_cfg;
203 * Given database name 'dbname' find cache index
206 get_cache_idx(char *dbname) {
207 int i;
208 char *nsc_name;
210 for (i = 0; i < CACHE_CTX_COUNT; i++) {
211 nsc_name = cache_name[i];
212 if (strcmp(nsc_name, dbname) == 0)
213 return (i);
215 return (-1);
219 * Given database name 'dbname' retrieve cache context,
220 * if not created yet, allocate and initialize it.
222 static nscd_rc_t
223 get_cache_ctx(char *dbname, nsc_ctx_t **ctx) {
224 int i;
226 *ctx = NULL;
228 i = get_cache_idx(dbname);
229 if (i == -1)
230 return (NSCD_INVALID_ARGUMENT);
231 if ((*ctx = cache_ctx_p[i]) == NULL) {
232 *ctx = init_cache_ctx(i);
233 if (*ctx == NULL)
234 return (NSCD_NO_MEMORY);
237 return (NSCD_SUCCESS);
241 * Generate a log string to identify backend operation in debug logs
243 static void
244 nsc_db_str_key_getlogstr(char *name, char *whoami, size_t len,
245 nss_XbyY_args_t *argp) {
246 (void) snprintf(whoami, len, "%s [key=%s]", name, argp->key.name);
250 static void
251 nsc_db_int_key_getlogstr(char *name, char *whoami, size_t len,
252 nss_XbyY_args_t *argp) {
253 (void) snprintf(whoami, len, "%s [key=%d]", name, argp->key.number);
256 /*ARGSUSED*/
257 static void
258 nsc_db_any_key_getlogstr(char *name, char *whoami, size_t len,
259 nss_XbyY_args_t *argp) {
260 (void) snprintf(whoami, len, "%s", name);
265 * Returns cache based on dbop
267 static nsc_db_t *
268 nsc_get_db(nsc_ctx_t *ctx, int dbop) {
269 int i;
271 for (i = 0; i < ctx->db_count; i++) {
272 if (ctx->nsc_db[i] && dbop == ctx->nsc_db[i]->dbop)
273 return (ctx->nsc_db[i]);
275 return (NULL);
280 * integer compare routine for _NSC_DB_INT_KEY
282 static int
283 nsc_db_int_key_compar(const void *n1, const void *n2) {
284 nsc_entry_t *e1, *e2;
286 e1 = (nsc_entry_t *)n1;
287 e2 = (nsc_entry_t *)n2;
288 return (_NSC_INT_KEY_CMP(e1->key.number, e2->key.number));
293 * case sensitive name compare routine for _NSC_DB_CES_KEY
295 static int
296 nsc_db_ces_key_compar(const void *n1, const void *n2) {
297 nsc_entry_t *e1, *e2;
298 int res, l1, l2;
300 e1 = (nsc_entry_t *)n1;
301 e2 = (nsc_entry_t *)n2;
302 l1 = strlen(e1->key.name);
303 l2 = strlen(e2->key.name);
304 res = strncmp(e1->key.name, e2->key.name, (l1 > l2)?l1:l2);
305 return (_NSC_INT_KEY_CMP(res, 0));
310 * case insensitive name compare routine _NSC_DB_CIS_KEY
312 static int
313 nsc_db_cis_key_compar(const void *n1, const void *n2) {
314 nsc_entry_t *e1, *e2;
315 int res, l1, l2;
317 e1 = (nsc_entry_t *)n1;
318 e2 = (nsc_entry_t *)n2;
319 l1 = strlen(e1->key.name);
320 l2 = strlen(e2->key.name);
321 res = strncasecmp(e1->key.name, e2->key.name, (l1 > l2)?l1:l2);
322 return (_NSC_INT_KEY_CMP(res, 0));
326 * macro used to generate elf hashes for strings
328 #define _NSC_ELF_STR_GETHASH(func, str, htsize, hval) \
329 hval = 0; \
330 while (*str) { \
331 uint_t g; \
332 hval = (hval << 4) + func(*str++); \
333 if ((g = (hval & 0xf0000000)) != 0) \
334 hval ^= g >> 24; \
335 hval &= ~g; \
337 hval %= htsize;
341 * cis hash function
343 uint_t
344 cis_gethash(const char *key, int htsize) {
345 uint_t hval;
346 if (key == NULL)
347 return (0);
348 _NSC_ELF_STR_GETHASH(tolower, key, htsize, hval);
349 return (hval);
354 * ces hash function
356 uint_t
357 ces_gethash(const char *key, int htsize) {
358 uint_t hval;
359 if (key == NULL)
360 return (0);
361 _NSC_ELF_STR_GETHASH(, key, htsize, hval);
362 return (hval);
367 * one-at-a-time hash function
369 uint_t
370 db_gethash(const void *key, int len, int htsize) {
371 uint_t hval, i;
372 const char *str = key;
374 if (str == NULL)
375 return (0);
377 for (hval = 0, i = 0; i < len; i++) {
378 hval += str[i];
379 hval += (hval << 10);
380 hval ^= (hval >> 6);
382 hval += (hval << 3);
383 hval ^= (hval >> 11);
384 hval += (hval << 15);
385 return (hval % htsize);
390 * case insensitive name gethash routine _NSC_DB_CIS_KEY
392 static uint_t
393 nsc_db_cis_key_gethash(nss_XbyY_key_t *key, int htsize) {
394 return (cis_gethash(key->name, htsize));
399 * case sensitive name gethash routine _NSC_DB_CES_KEY
401 static uint_t
402 nsc_db_ces_key_gethash(nss_XbyY_key_t *key, int htsize) {
403 return (ces_gethash(key->name, htsize));
408 * integer gethash routine _NSC_DB_INT_KEY
410 static uint_t
411 nsc_db_int_key_gethash(nss_XbyY_key_t *key, int htsize) {
412 return (db_gethash(&key->number, sizeof (key->number), htsize));
417 * Find entry in the hash table
418 * if cmp == nscd_true)
419 * return entry only if the keys match
420 * else
421 * return entry in the hash location without checking the keys
424 static nsc_entry_t *
425 hash_find(nsc_db_t *nscdb, nsc_entry_t *entry, uint_t *hash,
426 nscd_bool_t cmp) {
428 nsc_entry_t *hashentry;
430 if (nscdb->gethash)
431 *hash = nscdb->gethash(&entry->key, nscdb->htsize);
432 else
433 return (NULL);
435 hashentry = nscdb->htable[*hash];
436 if (cmp == nscd_false || hashentry == NULL)
437 return (hashentry);
438 if (nscdb->compar) {
439 if (nscdb->compar(entry, hashentry) == 0)
440 return (hashentry);
442 return (NULL);
446 #define HASH_REMOVE(nscdb, entry, hash, cmp) \
447 if (nscdb->htable) { \
448 if (entry == hash_find(nscdb, entry, &hash, cmp)) \
449 nscdb->htable[hash] = NULL; \
453 #define HASH_INSERT(nscdb, entry, hash, cmp) \
454 if (nscdb->htable) { \
455 (void) hash_find(nscdb, entry, &hash, cmp); \
456 nscdb->htable[hash] = entry; \
460 #ifdef NSCD_DEBUG
461 static void
462 print_entry(nsc_db_t *nscdb, time_t now, nsc_entry_t *entry) {
463 nss_XbyY_args_t args;
464 char whoami[512];
466 switch (entry->stats.status) {
467 case ST_NEW_ENTRY:
468 (void) fprintf(stdout, gettext("\t status: new entry\n"));
469 return;
470 case ST_UPDATE_PENDING:
471 (void) fprintf(stdout, gettext("\t status: update pending\n"));
472 return;
473 case ST_LOOKUP_PENDING:
474 (void) fprintf(stdout, gettext("\t status: lookup pending\n"));
475 return;
476 case ST_DISCARD:
477 (void) fprintf(stdout, gettext("\t status: discarded entry\n"));
478 return;
479 default:
480 if (entry->stats.timestamp < now)
481 (void) fprintf(stdout,
482 gettext("\t status: expired (%d seconds ago)\n"),
483 now - entry->stats.timestamp);
484 else
485 (void) fprintf(stdout,
486 gettext("\t status: valid (expiry in %d seconds)\n"),
487 entry->stats.timestamp - now);
488 break;
490 (void) fprintf(stdout, gettext("\t hits: %u\n"), entry->stats.hits);
491 args.key = entry->key;
492 (void) nscdb->getlogstr(nscdb->name, whoami, sizeof (whoami), &args);
493 (void) fprintf(stdout, "\t %s\n", whoami);
495 #endif /* NSCD_DEBUG */
497 static void
498 print_stats(nscd_cfg_stat_cache_t *statsp) {
500 (void) fprintf(stdout, gettext("\n\t STATISTICS:\n"));
501 (void) fprintf(stdout, gettext("\t positive hits: %lu\n"),
502 statsp->pos_hits);
503 (void) fprintf(stdout, gettext("\t negative hits: %lu\n"),
504 statsp->neg_hits);
505 (void) fprintf(stdout, gettext("\t positive misses: %lu\n"),
506 statsp->pos_misses);
507 (void) fprintf(stdout, gettext("\t negative misses: %lu\n"),
508 statsp->neg_misses);
509 (void) fprintf(stdout, gettext("\t total entries: %lu\n"),
510 statsp->entries);
511 (void) fprintf(stdout, gettext("\t queries queued: %lu\n"),
512 statsp->wait_count);
513 (void) fprintf(stdout, gettext("\t queries dropped: %lu\n"),
514 statsp->drop_count);
515 (void) fprintf(stdout, gettext("\t cache invalidations: %lu\n"),
516 statsp->invalidate_count);
518 _NSC_GET_HITRATE(statsp);
519 (void) fprintf(stdout, gettext("\t cache hit rate: %10.1f\n"),
520 statsp->hitrate);
524 static void
525 print_cfg(nscd_cfg_cache_t *cfgp) {
526 (void) fprintf(stdout, gettext("\n\t CONFIG:\n"));
527 (void) fprintf(stdout, gettext("\t enabled: %s\n"),
528 yes_no(cfgp->enable));
529 (void) fprintf(stdout, gettext("\t per user cache: %s\n"),
530 yes_no(cfgp->per_user));
531 (void) fprintf(stdout, gettext("\t avoid name service: %s\n"),
532 yes_no(cfgp->avoid_ns));
533 (void) fprintf(stdout, gettext("\t check file: %s\n"),
534 yes_no(cfgp->check_files));
535 (void) fprintf(stdout, gettext("\t check file interval: %d\n"),
536 cfgp->check_interval);
537 (void) fprintf(stdout, gettext("\t positive ttl: %d\n"),
538 cfgp->pos_ttl);
539 (void) fprintf(stdout, gettext("\t negative ttl: %d\n"),
540 cfgp->neg_ttl);
541 (void) fprintf(stdout, gettext("\t keep hot count: %d\n"),
542 cfgp->keephot);
543 (void) fprintf(stdout, gettext("\t hint size: %d\n"),
544 cfgp->hint_size);
545 (void) fprintf(stdout, gettext("\t max entries: %lu%s"),
546 cfgp->maxentries,
547 cfgp->maxentries?"\n":" (unlimited)\n");
551 #ifdef NSCD_DEBUG
552 static void
553 hash_dump(nsc_db_t *nscdb, time_t now) {
554 nsc_entry_t *entry;
555 int i;
557 (void) fprintf(stdout, gettext("\n\nHASH TABLE:\n"));
558 for (i = 0; i < nscdb->htsize; i++) {
559 if ((entry = nscdb->htable[i]) != NULL) {
560 (void) fprintf(stdout, "hash[%d]:\n", i);
561 print_entry(nscdb, now, entry);
565 #endif /* NSCD_DEBUG */
568 #ifdef NSCD_DEBUG
569 static void
570 avl_dump(nsc_db_t *nscdb, time_t now) {
571 nsc_entry_t *entry;
572 int i;
574 (void) fprintf(stdout, gettext("\n\nAVL TREE:\n"));
575 for (entry = avl_first(&nscdb->tree), i = 0; entry != NULL;
576 entry = avl_walk(&nscdb->tree, entry, AVL_AFTER)) {
577 (void) fprintf(stdout, "avl node[%d]:\n", i++);
578 print_entry(nscdb, now, entry);
581 #endif /* NSCD_DEBUG */
584 #ifdef NSCD_DEBUG
585 static void
586 queue_dump(nsc_db_t *nscdb, time_t now) {
587 nsc_entry_t *entry;
588 int i;
590 (void) fprintf(stdout,
591 gettext("\n\nCACHE [name=%s, nodes=%lu]:\n"),
592 nscdb->name, avl_numnodes(&nscdb->tree));
594 (void) fprintf(stdout,
595 gettext("Starting with the most recently accessed:\n"));
597 for (entry = nscdb->qtail, i = 0; entry; entry = entry->qnext) {
598 (void) fprintf(stdout, "entry[%d]:\n", i++);
599 print_entry(nscdb, now, entry);
602 #endif /* NSCD_DEBUG */
604 static void
605 queue_remove(nsc_db_t *nscdb, nsc_entry_t *entry) {
607 if (nscdb->qtail == entry)
608 nscdb->qtail = entry->qnext;
609 else
610 entry->qprev->qnext = entry->qnext;
612 if (nscdb->qhead == entry)
613 nscdb->qhead = entry->qprev;
614 else
615 entry->qnext->qprev = entry->qprev;
617 if (nscdb->reap_node == entry)
618 nscdb->reap_node = entry->qnext;
619 entry->qnext = entry->qprev = NULL;
623 static void
624 queue_adjust(nsc_db_t *nscdb, nsc_entry_t *entry) {
626 #ifdef NSCD_DEBUG
627 assert(nscdb->qtail || entry->qnext == NULL &&
628 entry->qprev == NULL);
630 assert(nscdb->qtail && nscdb->qhead ||
631 nscdb->qtail == NULL && nscdb->qhead == NULL);
633 assert(entry->qprev || entry->qnext == NULL ||
634 nscdb->qtail == entry);
635 #endif /* NSCD_DEBUG */
637 /* already in the desired position */
638 if (nscdb->qtail == entry)
639 return;
641 /* new queue */
642 if (nscdb->qtail == NULL) {
643 nscdb->qhead = nscdb->qtail = entry;
644 return;
647 /* new entry (prev == NULL AND tail != entry) */
648 if (entry->qprev == NULL) {
649 nscdb->qtail->qprev = entry;
650 entry->qnext = nscdb->qtail;
651 nscdb->qtail = entry;
652 return;
655 /* existing entry */
656 if (nscdb->reap_node == entry)
657 nscdb->reap_node = entry->qnext;
658 if (nscdb->qhead == entry)
659 nscdb->qhead = entry->qprev;
660 else
661 entry->qnext->qprev = entry->qprev;
662 entry->qprev->qnext = entry->qnext;
663 entry->qprev = NULL;
664 entry->qnext = nscdb->qtail;
665 nscdb->qtail->qprev = entry;
666 nscdb->qtail = entry;
671 * Init cache
673 nscd_rc_t
674 init_cache(int debug_level) {
675 int cflags;
677 cflags = (debug_level > 0)?0:UMC_NODEBUG;
678 nsc_entry_cache = umem_cache_create("nsc_entry_cache",
679 sizeof (nsc_entry_t), 0, NULL, NULL, NULL,
680 NULL, NULL, cflags);
681 if (nsc_entry_cache == NULL)
682 return (NSCD_NO_MEMORY);
683 return (NSCD_SUCCESS);
688 * Create cache
690 nsc_db_t *
691 make_cache(enum db_type dbtype, int dbop, char *name,
692 int (*compar) (const void *, const void *),
693 void (*getlogstr)(char *, char *, size_t, nss_XbyY_args_t *),
694 uint_t (*gethash)(nss_XbyY_key_t *, int),
695 enum hash_type httype, int htsize)
697 nsc_db_t *nscdb;
698 char *me = "make_cache";
700 nscdb = (nsc_db_t *)malloc(sizeof (*nscdb));
701 if (nscdb == NULL) {
702 _NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_ERROR)
703 (me, "%s: memory allocation failure\n", name);
704 goto out;
706 (void) memset(nscdb, 0, sizeof (*nscdb));
708 nscdb->dbop = dbop;
709 nscdb->name = name;
710 nscdb->db_type = dbtype;
712 /* Assign compare routine */
713 if (compar == NULL) {
714 if (_NSC_DB_CES_KEY(nscdb))
715 nscdb->compar = nsc_db_ces_key_compar;
716 else if (_NSC_DB_CIS_KEY(nscdb))
717 nscdb->compar = nsc_db_cis_key_compar;
718 else if (_NSC_DB_INT_KEY(nscdb))
719 nscdb->compar = nsc_db_int_key_compar;
720 else
721 assert(0);
722 } else {
723 nscdb->compar = compar;
726 /* The cache is an AVL tree */
727 avl_create(&nscdb->tree, nscdb->compar, sizeof (nsc_entry_t),
728 offsetof(nsc_entry_t, avl_link));
730 /* Assign log routine */
731 if (getlogstr == NULL) {
732 if (_NSC_DB_STR_KEY(nscdb))
733 nscdb->getlogstr = nsc_db_str_key_getlogstr;
734 else if (_NSC_DB_INT_KEY(nscdb))
735 nscdb->getlogstr = nsc_db_int_key_getlogstr;
736 else
737 nscdb->getlogstr = nsc_db_any_key_getlogstr;
738 } else {
739 nscdb->getlogstr = getlogstr;
742 /* The AVL tree based cache uses a hash table for quick access */
743 if (htsize != 0) {
744 /* Determine hash table size based on type */
745 nscdb->hash_type = httype;
746 if (htsize < 0) {
747 switch (httype) {
748 case nsc_ht_power2:
749 htsize = _NSC_INIT_HTSIZE_POWER2;
750 break;
751 case nsc_ht_prime:
752 case nsc_ht_default:
753 default:
754 htsize = _NSC_INIT_HTSIZE_PRIME;
757 nscdb->htsize = htsize;
759 /* Create the hash table */
760 nscdb->htable = calloc(htsize, sizeof (*(nscdb->htable)));
761 if (nscdb->htable == NULL) {
762 _NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_ERROR)
763 (me, "%s: memory allocation failure\n", name);
764 goto out;
767 /* Assign gethash routine */
768 if (gethash == NULL) {
769 if (_NSC_DB_CES_KEY(nscdb))
770 nscdb->gethash = nsc_db_ces_key_gethash;
771 else if (_NSC_DB_CIS_KEY(nscdb))
772 nscdb->gethash = nsc_db_cis_key_gethash;
773 else if (_NSC_DB_INT_KEY(nscdb))
774 nscdb->gethash = nsc_db_int_key_gethash;
775 else
776 assert(0);
777 } else {
778 nscdb->gethash = gethash;
782 (void) mutex_init(&nscdb->db_mutex, USYNC_THREAD, NULL);
783 return (nscdb);
785 out:
786 free(nscdb->htable);
787 free(nscdb);
788 return (NULL);
793 * verify
795 /* ARGSUSED */
796 nscd_rc_t
797 _nscd_cfg_cache_verify(
798 void *data,
799 struct nscd_cfg_param_desc *pdesc,
800 nscd_cfg_id_t *nswdb,
801 nscd_cfg_flag_t dflag,
802 nscd_cfg_error_t **errorp,
803 void **cookie)
806 return (NSCD_SUCCESS);
810 * notify
812 /* ARGSUSED */
813 nscd_rc_t
814 _nscd_cfg_cache_notify(
815 void *data,
816 struct nscd_cfg_param_desc *pdesc,
817 nscd_cfg_id_t *nswdb,
818 nscd_cfg_flag_t dflag,
819 nscd_cfg_error_t **errorp,
820 void **cookie)
822 nsc_ctx_t *ctx;
823 void *dp;
824 int i;
826 /* group data */
827 if (_nscd_cfg_flag_is_set(dflag, NSCD_CFG_DFLAG_GROUP)) {
828 if (_nscd_cfg_flag_is_set(pdesc->pflag,
829 NSCD_CFG_PFLAG_GLOBAL)) {
830 /* global config */
831 global_cfg = *(nscd_cfg_global_cache_t *)data;
832 } else if (_nscd_cfg_flag_is_set(dflag,
833 NSCD_CFG_DFLAG_SET_ALL_DB)) {
834 /* non-global config for all dbs */
835 for (i = 0; i < CACHE_CTX_COUNT; i++) {
836 ctx = cache_ctx_p[i];
837 if (ctx == NULL)
838 return (NSCD_CTX_NOT_FOUND);
839 (void) rw_wrlock(&ctx->cfg_rwlp);
840 ctx->cfg = *(nscd_cfg_cache_t *)data;
841 ctx->cfg_mtime = time(NULL);
842 (void) rw_unlock(&ctx->cfg_rwlp);
844 } else {
845 /* non-global config for a specific db */
847 /* ignore non-caching databases */
848 if (get_cache_ctx(nswdb->name, &ctx) != NSCD_SUCCESS)
849 return (NSCD_SUCCESS);
850 (void) rw_wrlock(&ctx->cfg_rwlp);
851 ctx->cfg = *(nscd_cfg_cache_t *)data;
852 ctx->cfg_mtime = time(NULL);
853 (void) rw_unlock(&ctx->cfg_rwlp);
855 return (NSCD_SUCCESS);
858 /* individual data */
859 if (_nscd_cfg_flag_is_set(pdesc->pflag, NSCD_CFG_PFLAG_GLOBAL)) {
860 /* global config */
861 dp = (char *)&global_cfg + pdesc->p_offset;
862 (void) memcpy(dp, data, pdesc->p_size);
863 } else if (_nscd_cfg_flag_is_set(dflag,
864 NSCD_CFG_DFLAG_SET_ALL_DB)) {
865 /* non-global config for all dbs */
866 for (i = 0; i < CACHE_CTX_COUNT; i++) {
867 ctx = cache_ctx_p[i];
868 if (ctx == NULL)
869 return (NSCD_CTX_NOT_FOUND);
870 dp = (char *)&ctx->cfg + pdesc->p_offset;
871 (void) rw_wrlock(&ctx->cfg_rwlp);
872 (void) memcpy(dp, data, pdesc->p_size);
873 ctx->cfg_mtime = time(NULL);
874 (void) rw_unlock(&ctx->cfg_rwlp);
876 } else {
877 /* non-global config for a specific db */
879 /* ignore non-caching databases */
880 if (get_cache_ctx(nswdb->name, &ctx) != NSCD_SUCCESS)
881 return (NSCD_SUCCESS);
882 dp = (char *)&ctx->cfg + pdesc->p_offset;
883 (void) rw_wrlock(&ctx->cfg_rwlp);
884 (void) memcpy(dp, data, pdesc->p_size);
885 ctx->cfg_mtime = time(NULL);
886 (void) rw_unlock(&ctx->cfg_rwlp);
888 return (NSCD_SUCCESS);
893 * get stat
895 /* ARGSUSED */
896 nscd_rc_t
897 _nscd_cfg_cache_get_stat(
898 void **stat,
899 struct nscd_cfg_stat_desc *sdesc,
900 nscd_cfg_id_t *nswdb,
901 nscd_cfg_flag_t *dflag,
902 void (**free_stat)(void *stat),
903 nscd_cfg_error_t **errorp)
905 nscd_cfg_stat_cache_t *statsp, stats;
906 nsc_ctx_t *ctx;
907 int i;
908 nscd_rc_t rc;
910 statsp = calloc(1, sizeof (*statsp));
911 if (statsp == NULL)
912 return (NSCD_NO_MEMORY);
914 if (_nscd_cfg_flag_is_set(sdesc->sflag, NSCD_CFG_SFLAG_GLOBAL)) {
915 for (i = 0; i < CACHE_CTX_COUNT; i++) {
916 if (cache_ctx_p[i] == NULL)
917 stats = null_stats;
918 else {
919 (void) mutex_lock(&cache_ctx_p[i]->stats_mutex);
920 stats = cache_ctx_p[i]->stats;
921 (void) mutex_unlock(
922 &cache_ctx_p[i]->stats_mutex);
924 statsp->pos_hits += stats.pos_hits;
925 statsp->neg_hits += stats.neg_hits;
926 statsp->pos_misses += stats.pos_misses;
927 statsp->neg_misses += stats.neg_misses;
928 statsp->entries += stats.entries;
929 statsp->drop_count += stats.drop_count;
930 statsp->wait_count += stats.wait_count;
931 statsp->invalidate_count +=
932 stats.invalidate_count;
934 } else {
935 if ((rc = get_cache_ctx(nswdb->name, &ctx)) != NSCD_SUCCESS) {
936 free(statsp);
937 return (rc);
939 (void) mutex_lock(&ctx->stats_mutex);
940 *statsp = ctx->stats;
941 (void) mutex_unlock(&ctx->stats_mutex);
944 _NSC_GET_HITRATE(statsp);
945 *stat = statsp;
946 return (NSCD_SUCCESS);
950 * This function should only be called when nscd is
951 * not a daemon.
953 void
954 nsc_info(nsc_ctx_t *ctx, char *dbname, nscd_cfg_cache_t cfg[],
955 nscd_cfg_stat_cache_t stats[])
957 int i;
958 char *me = "nsc_info";
959 nsc_ctx_t *ctx1;
960 nsc_ctx_t ctx2;
961 nscd_rc_t rc;
963 if (ctx) {
964 ctx_info(ctx);
965 return;
968 if (dbname) {
969 rc = get_cache_ctx(dbname, &ctx1);
970 if (rc == NSCD_INVALID_ARGUMENT) {
971 _NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_WARNING)
972 (me, "%s: no cache context found\n", dbname);
973 return;
974 } else if (rc == NSCD_NO_MEMORY) {
975 _NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_WARNING)
976 (me, "%s: unable to create cache context - no memory\n",
977 dbname);
978 return;
980 ctx_info(ctx1);
981 return;
984 if (cfg == NULL || stats == NULL)
985 return;
987 for (i = 0; i < CACHE_CTX_COUNT; i++) {
989 ctx2.dbname = cache_name[i];
990 ctx2.cfg = cfg[i];
991 ctx2.stats = stats[i];
992 ctx_info_nolock(&ctx2);
996 static void
997 ctx_info_nolock(nsc_ctx_t *ctx) {
998 nscd_cfg_cache_t cfg;
999 nscd_cfg_stat_cache_t stats;
1001 cfg = ctx->cfg;
1002 (void) fprintf(stdout, gettext("\n\nCACHE: %s\n"), ctx->dbname);
1003 (void) print_cfg(&cfg);
1005 if (cfg.enable == nscd_false)
1006 return;
1008 stats = ctx->stats;
1009 (void) print_stats(&stats);
1012 static void
1013 ctx_info(nsc_ctx_t *ctx) {
1014 nscd_cfg_cache_t cfg;
1015 nscd_cfg_stat_cache_t stats;
1017 (void) rw_rdlock(&ctx->cfg_rwlp);
1018 cfg = ctx->cfg;
1019 (void) rw_unlock(&ctx->cfg_rwlp);
1020 (void) fprintf(stdout, gettext("\n\nCACHE: %s\n"), ctx->dbname);
1021 (void) print_cfg(&cfg);
1023 if (cfg.enable == nscd_false)
1024 return;
1026 (void) mutex_lock(&ctx->stats_mutex);
1027 stats = ctx->stats;
1028 (void) mutex_unlock(&ctx->stats_mutex);
1029 (void) print_stats(&stats);
1032 #ifdef NSCD_DEBUG
1034 * This function should only be called when nscd is
1035 * not a daemon.
1038 nsc_dump(char *dbname, int dbop)
1040 nsc_ctx_t *ctx;
1041 nsc_db_t *nscdb;
1042 nscd_bool_t enabled;
1043 time_t now;
1044 char *me = "nsc_dump";
1045 int i;
1047 if ((i = get_cache_idx(dbname)) == -1) {
1048 (void) fprintf(stdout, gettext("invalid cache name\n"));
1050 _NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_WARNING)
1051 (me, "%s: invalid cache name\n", dbname);
1052 return (NSCD_CACHE_INVALID_CACHE_NAME);
1055 if ((ctx = cache_ctx_p[i]) == NULL) {
1056 (void) fprintf(stdout, gettext("no cache context\n"));
1058 _NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_WARNING)
1059 (me, "%s: no cache context\n", dbname);
1060 return (NSCD_CACHE_NO_CACHE_CTX);
1063 now = time(NULL);
1064 (void) rw_rdlock(&ctx->cfg_rwlp);
1065 enabled = ctx->cfg.enable;
1066 (void) rw_unlock(&ctx->cfg_rwlp);
1068 if (enabled == nscd_false)
1069 return (NSCD_CACHE_DISABLED);
1071 nscdb = nsc_get_db(ctx, dbop);
1072 if (nscdb == NULL) {
1073 _NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_WARNING)
1074 (me, "%s:%d: no cache found\n", dbname, dbop);
1075 return (NSCD_CACHE_NO_CACHE_FOUND);
1078 (void) mutex_lock(&nscdb->db_mutex);
1079 (void) queue_dump(nscdb, now);
1080 (void) hash_dump(nscdb, now);
1081 (void) avl_dump(nscdb, now);
1082 (void) mutex_unlock(&nscdb->db_mutex);
1083 return (NSCD_SUCCESS);
1085 #endif /* NSCD_DEBUG */
1088 * These macros are for exclusive use of nsc_lookup
1090 #define NSC_LOOKUP_LOG(loglevel, fmt) \
1091 _NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_##loglevel) \
1092 (me, fmt, whoami);
1094 static int
1095 nsc_lookup_no_cache(nsc_lookup_args_t *largs, const char *str)
1097 char *me = "nsc_lookup_no_cache";
1098 nss_status_t status;
1100 _NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_DEBUG)
1101 (me, "%s: name service lookup (bypassing cache)\n", str);
1102 nss_psearch(largs->buffer, largs->bufsize);
1103 status = NSCD_GET_STATUS(largs->buffer);
1104 _NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_DEBUG)
1105 (me, "%s: name service lookup status = %d\n", str, status);
1106 if (status == NSS_SUCCESS) {
1107 return (SUCCESS);
1108 } else if (status == NSS_NOTFOUND) {
1109 return (NOTFOUND);
1110 } else {
1111 return (SERVERERROR);
1116 * This function starts the revalidation and reaper threads
1117 * for a cache
1119 static void
1120 start_threads(nsc_ctx_t *ctx)
1122 int errnum;
1123 char *me = "start_threads";
1126 * kick off the revalidate thread (if necessary)
1128 if (ctx->revalidate_on != nscd_true) {
1129 if (thr_create(NULL, NULL, (void *(*)(void *))revalidate,
1130 ctx, 0, NULL) != 0) {
1131 errnum = errno;
1132 _NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_ERROR)
1133 (me, "thr_create (revalidate thread for %s): %s\n",
1134 ctx->dbname, strerror(errnum));
1135 exit(1);
1137 ctx->revalidate_on = nscd_true;
1141 * kick off the reaper thread (if necessary)
1143 if (ctx->reaper_on != nscd_true) {
1144 if (thr_create(NULL, NULL, (void *(*)(void *))reaper,
1145 ctx, 0, NULL) != 0) {
1146 errnum = errno;
1147 _NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_ERROR)
1148 (me, "thr_create (reaper thread for %s): %s\n",
1149 ctx->dbname, strerror(errnum));
1150 exit(1);
1152 ctx->reaper_on = nscd_true;
1157 * Examine the packed buffer, see if the front-end parameters
1158 * indicate that the caller specified nsswitch config should be
1159 * used for the lookup. Return 1 if yes, otherwise 0.
1161 static int
1162 nsw_config_in_phdr(void *buf)
1164 nss_pheader_t *pbuf = (nss_pheader_t *)buf;
1165 nssuint_t off;
1166 nss_dbd_t *pdbd;
1167 char *me = "nsw_config_in_phdr";
1169 off = pbuf->dbd_off;
1170 if (off == 0)
1171 return (0);
1172 pdbd = (nss_dbd_t *)((void *)((char *)pbuf + off));
1173 if (pdbd->o_default_config == 0)
1174 return (0);
1176 if ((enum nss_dbp_flags)pdbd->flags & NSS_USE_DEFAULT_CONFIG) {
1177 _NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_DEBUG)
1178 (me, "use caller specified nsswitch config\n");
1179 return (1);
1180 } else
1181 return (0);
1184 static nss_status_t
1185 copy_result(void *rbuf, void *cbuf)
1187 nss_pheader_t *rphdr = (nss_pheader_t *)rbuf;
1188 nss_pheader_t *cphdr = (nss_pheader_t *)cbuf;
1189 char *me = "copy_result";
1191 /* return NSS_ERROR if not enough room to copy result */
1192 if (cphdr->data_len + 1 > rphdr->data_len) {
1193 NSCD_SET_STATUS(rphdr, NSS_ERROR, ERANGE);
1194 return (NSS_ERROR);
1195 } else {
1196 char *dst;
1198 if (cphdr->data_len == 0)
1199 return (NSS_SUCCESS);
1201 dst = (char *)rphdr + rphdr->data_off;
1202 (void) memcpy(dst, (char *)cphdr + cphdr->data_off,
1203 cphdr->data_len);
1204 rphdr->data_len = cphdr->data_len;
1205 /* some frontend code expects a terminating NULL char */
1206 *(dst + rphdr->data_len) = '\0';
1208 _NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_DEBUG)
1209 (me, "cache data (len = %lld): >>%s<<\n",
1210 cphdr->data_len, (char *)cphdr + cphdr->data_off);
1212 return (NSS_SUCCESS);
1216 static int
1217 get_dns_ttl(void *pbuf, char *dbname)
1219 nss_pheader_t *phdr = (nss_pheader_t *)pbuf;
1220 int ttl;
1221 char *me = "get_dns_ttl";
1223 /* if returned, dns ttl is stored in the extended data area */
1224 if (phdr->ext_off == 0)
1225 return (-1);
1227 if (strcmp(dbname, NSS_DBNAM_HOSTS) != 0 &&
1228 strcmp(dbname, NSS_DBNAM_IPNODES) != 0)
1229 return (-1);
1231 ttl = *(nssuint_t *)((void *)((char *)pbuf + phdr->ext_off));
1233 _NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_DEBUG)
1234 (me, "dns ttl is %d seconds\n", ttl);
1236 return (ttl);
1239 static int
1240 check_config(nsc_lookup_args_t *largs, nscd_cfg_cache_t *cfgp,
1241 char *whoami, int flag)
1243 nsc_db_t *nscdb;
1244 nsc_ctx_t *ctx;
1245 char *me = "check_config";
1247 ctx = largs->ctx;
1248 nscdb = largs->nscdb;
1250 /* see if the cached config needs update */
1251 if (nscdb->cfg_mtime != ctx->cfg_mtime) {
1252 (void) rw_rdlock(&ctx->cfg_rwlp);
1253 nscdb->cfg = ctx->cfg;
1254 nscdb->cfg_mtime = ctx->cfg_mtime;
1255 (void) rw_unlock(&ctx->cfg_rwlp);
1256 _NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_DEBUG)
1257 (me, "config for context %s, database %s updated\n",
1258 ctx->dbname, nscdb->name);
1260 *cfgp = nscdb->cfg;
1262 if (cfgp->enable == nscd_false) {
1263 _NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_DEBUG)
1264 (me, "%s: cache disabled\n", ctx->dbname);
1266 if (UPDATEBIT & flag)
1267 return (NOTFOUND);
1268 else
1269 return (nsc_lookup_no_cache(largs, whoami));
1273 * if caller requests lookup using his
1274 * own nsswitch config, bypass cache
1276 if (nsw_config_in_phdr(largs->buffer))
1277 return (nsc_lookup_no_cache(largs, whoami));
1279 /* no need of cache if we are dealing with 0 ttls */
1280 if (cfgp->pos_ttl <= 0 && cfgp->neg_ttl <= 0) {
1281 if (flag & UPDATEBIT)
1282 return (NOTFOUND);
1283 else if (cfgp->avoid_ns == nscd_true)
1284 return (SERVERERROR);
1285 return (nsc_lookup_no_cache(largs, whoami));
1288 return (CONTINUE);
1292 * Invalidate cache if database file has been modified.
1293 * See check_files config param for details.
1295 static void
1296 check_db_file(nsc_ctx_t *ctx, nscd_cfg_cache_t cfg,
1297 char *whoami, time_t now)
1299 struct stat buf;
1300 nscd_bool_t file_modified = nscd_false;
1301 char *me = "check_db_file";
1303 if (cfg.check_interval != 0 &&
1304 (now - ctx->file_chktime) < cfg.check_interval)
1305 return;
1307 ctx->file_chktime = now;
1308 if (stat(ctx->file_name, &buf) == 0) {
1309 if (ctx->file_mtime == 0) {
1310 (void) mutex_lock(&ctx->file_mutex);
1311 if (ctx->file_mtime == 0) {
1312 ctx->file_mtime = buf.st_mtime;
1313 ctx->file_size = buf.st_size;
1314 ctx->file_ino = buf.st_ino;
1316 (void) mutex_unlock(&ctx->file_mutex);
1317 } else if (ctx->file_mtime < buf.st_mtime ||
1318 ctx->file_size != buf.st_size ||
1319 ctx->file_ino != buf.st_ino) {
1320 (void) mutex_lock(&ctx->file_mutex);
1321 if (ctx->file_mtime < buf.st_mtime ||
1322 ctx->file_size != buf.st_size ||
1323 ctx->file_ino != buf.st_ino) {
1324 file_modified = nscd_true;
1325 ctx->file_mtime = buf.st_mtime;
1326 ctx->file_size = buf.st_size;
1327 ctx->file_ino = buf.st_ino;
1329 (void) mutex_unlock(&ctx->file_mutex);
1333 if (file_modified == nscd_true) {
1334 _NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_DEBUG)
1335 (me, "%s: file %s has been modified - invalidating cache\n",
1336 whoami, ctx->file_name);
1337 ctx_invalidate(ctx);
1341 static int
1342 lookup_int(nsc_lookup_args_t *largs, int flag)
1344 nsc_ctx_t *ctx;
1345 nsc_db_t *nscdb;
1346 nscd_cfg_cache_t cfg;
1347 nsc_entry_t *this_entry;
1348 nsc_entry_stat_t *this_stats;
1349 nsc_action_t next_action;
1350 nss_status_t status;
1351 nscd_bool_t delete;
1352 nscd_rc_t rc;
1353 char *dbname;
1354 int dbop, errnum;
1355 int cfg_rc;
1356 nss_XbyY_args_t args;
1357 char whoami[128];
1358 time_t now = time(NULL); /* current time */
1359 char *me = "lookup_int";
1361 /* extract dbop, dbname, key and cred */
1362 status = nss_packed_getkey(largs->buffer, largs->bufsize, &dbname,
1363 &dbop, &args);
1364 if (status != NSS_SUCCESS) {
1365 _NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_ERROR)
1366 (me, "nss_packed_getkey failure (%d)\n", status);
1367 return (SERVERERROR);
1370 /* get the cache context */
1371 if (largs->ctx == NULL) {
1372 if (get_cache_ctx(dbname, &largs->ctx) != NSCD_SUCCESS) {
1373 _NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_WARNING)
1374 (me, "%s: no cache context found\n", dbname);
1376 if (UPDATEBIT & flag)
1377 return (NOTFOUND);
1378 else
1379 return (nsc_lookup_no_cache(largs, dbname));
1382 ctx = largs->ctx;
1384 if (largs->nscdb == NULL) {
1385 if ((largs->nscdb = nsc_get_db(ctx, dbop)) == NULL) {
1386 _NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_WARNING)
1387 (me, "%s:%d: no cache found\n",
1388 dbname, dbop);
1390 if (UPDATEBIT & flag)
1391 return (NOTFOUND);
1392 else
1393 return (nsc_lookup_no_cache(largs, dbname));
1397 nscdb = largs->nscdb;
1399 _NSCD_LOG_IF(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_ALL) {
1400 (void) nscdb->getlogstr(nscdb->name, whoami,
1401 sizeof (whoami), &args);
1404 if (UPDATEBIT & flag) {
1405 _NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_DEBUG)
1406 (me, "%s: refresh start\n", whoami);
1407 } else {
1408 _NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_DEBUG)
1409 (me, "%s: lookup start\n", whoami);
1412 cfg_rc = check_config(largs, &cfg, whoami, flag);
1413 if (cfg_rc != CONTINUE)
1414 return (cfg_rc);
1417 * Invalidate cache if file has been modified.
1419 if (cfg.check_files == nscd_true)
1420 check_db_file(ctx, cfg, whoami, now);
1422 (void) mutex_lock(&nscdb->db_mutex);
1424 /* Lookup the cache table */
1425 for (;;) {
1426 delete = nscd_false;
1427 rc = lookup_cache(largs, &cfg, &args, whoami, &this_entry);
1428 if (rc != NSCD_SUCCESS) {
1429 (void) mutex_unlock(&nscdb->db_mutex);
1431 /* Either no entry and avoid name service */
1432 if (rc == NSCD_DB_ENTRY_NOT_FOUND ||
1433 rc == NSCD_INVALID_ARGUMENT)
1434 return (NOTFOUND);
1436 /* OR memory error */
1437 return (SERVERERROR);
1440 /* get the stats from the entry */
1441 this_stats = &this_entry->stats;
1444 * What should we do next ?
1446 switch (this_stats->status) {
1447 case ST_NEW_ENTRY:
1448 delete = nscd_true;
1449 next_action = _NSC_NSLOOKUP;
1450 break;
1451 case ST_UPDATE_PENDING:
1452 if (flag & UPDATEBIT) {
1453 (void) mutex_unlock(&nscdb->db_mutex);
1454 return (NOTFOUND);
1455 } else if (this_stats->timestamp < now)
1456 next_action = _NSC_WAIT;
1457 else
1458 next_action = _NSC_USECACHED;
1459 break;
1460 case ST_LOOKUP_PENDING:
1461 if (flag & UPDATEBIT) {
1462 (void) mutex_unlock(&nscdb->db_mutex);
1463 return (NOTFOUND);
1465 next_action = _NSC_WAIT;
1466 break;
1467 case ST_DISCARD:
1468 if (cfg.avoid_ns == nscd_true) {
1469 (void) mutex_unlock(&nscdb->db_mutex);
1470 return (NOTFOUND);
1472 /* otherwise reuse the entry */
1473 (void) memset(this_stats, 0, sizeof (*this_stats));
1474 next_action = _NSC_NSLOOKUP;
1475 break;
1476 default:
1477 if (cfg.avoid_ns == nscd_true)
1478 next_action = _NSC_USECACHED;
1479 else if ((flag & UPDATEBIT) ||
1480 (this_stats->timestamp < now)) {
1481 _NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_DEBUG)
1482 (me, "%s: cached entry needs to be updated\n",
1483 whoami);
1484 next_action = _NSC_NSLOOKUP;
1485 } else
1486 next_action = _NSC_USECACHED;
1487 break;
1490 if (next_action == _NSC_WAIT) {
1491 _NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_DEBUG)
1492 (me, "%s: need to wait\n", whoami);
1494 /* do we have clearance ? */
1495 if (_nscd_get_clearance(&ctx->throttle_sema) != 0) {
1496 /* nope. quit */
1497 (void) mutex_lock(&ctx->stats_mutex);
1498 ctx->stats.drop_count++;
1499 (void) mutex_unlock(&ctx->stats_mutex);
1500 _NSCD_LOG(NSCD_LOG_CACHE,
1501 NSCD_LOG_LEVEL_DEBUG_6)
1502 (me, "%s: throttling load\n", whoami);
1503 (void) mutex_unlock(&nscdb->db_mutex);
1504 NSC_LOOKUP_LOG(WARNING,
1505 "%s: no clearance to wait\n");
1506 return (NOSERVER);
1508 /* yes can wait */
1509 (void) nscd_wait(ctx, nscdb, this_entry);
1510 (void) _nscd_release_clearance(&ctx->throttle_sema);
1511 continue;
1514 break;
1518 if (!(UPDATEBIT & flag))
1519 this_stats->hits++; /* update hit count */
1521 if (next_action == _NSC_NSLOOKUP) {
1523 _NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_DEBUG)
1524 (me, "%s: name service lookup required\n", whoami);
1526 if (_nscd_get_clearance(&ctx->throttle_sema) != 0) {
1527 if (delete == nscd_true)
1528 delete_entry(nscdb, ctx, this_entry);
1529 else
1530 this_stats->status = ST_DISCARD;
1531 (void) mutex_lock(&ctx->stats_mutex);
1532 ctx->stats.drop_count++;
1533 (void) mutex_unlock(&ctx->stats_mutex);
1534 (void) mutex_unlock(&nscdb->db_mutex);
1535 NSC_LOOKUP_LOG(WARNING,
1536 "%s: no clearance for lookup\n");
1537 return (NOSERVER);
1540 /* block any threads accessing this entry */
1541 this_stats->status = (flag & UPDATEBIT) ?
1542 ST_UPDATE_PENDING : ST_LOOKUP_PENDING;
1544 /* release lock and do name service lookup */
1545 (void) mutex_unlock(&nscdb->db_mutex);
1546 nss_psearch(largs->buffer, largs->bufsize);
1547 status = NSCD_GET_STATUS(largs->buffer);
1548 (void) mutex_lock(&nscdb->db_mutex);
1549 this_stats->status = 0;
1550 (void) _nscd_release_clearance(&ctx->throttle_sema);
1552 /* signal waiting threads */
1553 (void) nscd_signal(ctx, nscdb, this_entry);
1555 _NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_DEBUG)
1556 (me, "%s: name service lookup status = %d\n",
1557 whoami, status);
1559 if (status == NSS_SUCCESS) {
1560 int ttl;
1563 * data found in name service
1564 * update cache
1566 status = dup_packed_buffer(largs, this_entry);
1567 if (status != NSS_SUCCESS) {
1568 delete_entry(nscdb, ctx, this_entry);
1569 (void) mutex_unlock(&nscdb->db_mutex);
1570 NSC_LOOKUP_LOG(ERROR,
1571 "%s: failed to update cache\n");
1572 return (SERVERERROR);
1576 * store unpacked key in cache
1578 status = nss_packed_getkey(this_entry->buffer,
1579 this_entry->bufsize,
1580 &dbname, &dbop, &args);
1581 if (status != NSS_SUCCESS) {
1582 delete_entry(nscdb, ctx, this_entry);
1583 (void) mutex_unlock(&nscdb->db_mutex);
1584 NSC_LOOKUP_LOG(ERROR,
1585 "%s: failed to extract key\n");
1586 return (SERVERERROR);
1588 this_entry->key = args.key; /* struct copy */
1590 /* update +ve miss count */
1591 if (!(UPDATEBIT & flag)) {
1592 (void) mutex_lock(&ctx->stats_mutex);
1593 ctx->stats.pos_misses++;
1594 (void) mutex_unlock(&ctx->stats_mutex);
1597 /* update +ve ttl */
1598 ttl = get_dns_ttl(largs->buffer, dbname);
1599 /* honor the dns ttl less than postive ttl */
1600 if (ttl < 0 || ttl > cfg.pos_ttl)
1601 ttl = cfg.pos_ttl;
1602 this_stats->timestamp = time(NULL) + ttl;
1605 * start the revalidation and reaper threads
1606 * if not already started
1608 start_threads(ctx);
1610 (void) mutex_unlock(&nscdb->db_mutex);
1611 NSC_LOOKUP_LOG(DEBUG,
1612 "%s: cache updated with positive entry\n");
1613 return (SUCCESS);
1614 } else if (status == NSS_NOTFOUND) {
1616 * data not found in name service
1617 * update cache
1619 _NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_DEBUG_6)
1620 (me, "%s: name service lookup failed\n", whoami);
1622 if (NSCD_GET_ERRNO(largs->buffer) == ERANGE) {
1623 delete_entry(nscdb, ctx, this_entry);
1624 (void) mutex_unlock(&nscdb->db_mutex);
1625 NSC_LOOKUP_LOG(DEBUG,
1626 "%s: ERANGE, cache not updated "
1627 "with negative entry\n");
1628 return (NOTFOUND);
1631 status = dup_packed_buffer(largs, this_entry);
1632 if (status != NSS_SUCCESS) {
1633 delete_entry(nscdb, ctx, this_entry);
1634 (void) mutex_unlock(&nscdb->db_mutex);
1635 NSC_LOOKUP_LOG(ERROR,
1636 "%s: failed to update cache\n");
1637 return (SERVERERROR);
1640 /* store unpacked key in cache */
1641 status = nss_packed_getkey(this_entry->buffer,
1642 this_entry->bufsize,
1643 &dbname, &dbop, &args);
1644 if (status != NSS_SUCCESS) {
1645 delete_entry(nscdb, ctx, this_entry);
1646 (void) mutex_unlock(&nscdb->db_mutex);
1647 NSC_LOOKUP_LOG(ERROR,
1648 "%s: failed to extract key\n");
1649 return (SERVERERROR);
1651 this_entry->key = args.key; /* struct copy */
1653 /* update -ve ttl */
1654 this_stats->timestamp = time(NULL) + cfg.neg_ttl;
1656 /* update -ve miss count */
1657 if (!(UPDATEBIT & flag)) {
1658 (void) mutex_lock(&ctx->stats_mutex);
1659 ctx->stats.neg_misses++;
1660 (void) mutex_unlock(&ctx->stats_mutex);
1664 * start the revalidation and reaper threads
1665 * if not already started
1667 start_threads(ctx);
1669 (void) mutex_unlock(&nscdb->db_mutex);
1670 NSC_LOOKUP_LOG(DEBUG,
1671 "%s: cache updated with negative entry\n");
1672 return (NOTFOUND);
1673 } else {
1675 * name service lookup failed
1677 _NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_DEBUG_6)
1678 (me, "%s: name service lookup failed\n", whoami);
1680 errnum = NSCD_GET_ERRNO(largs->buffer);
1681 if (delete == nscd_true)
1682 delete_entry(nscdb, ctx, this_entry);
1683 else
1684 this_stats->status = ST_DISCARD;
1686 (void) mutex_unlock(&nscdb->db_mutex);
1687 _NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_WARNING)
1688 (me, "%s: name service lookup failed "
1689 "(status=%d, errno=%d)\n",
1690 whoami, status, errnum);
1692 return (SERVERERROR);
1694 } else if (next_action == _NSC_USECACHED) {
1696 * found entry in cache
1698 if (UPDATEBIT & flag) {
1699 (void) mutex_unlock(&nscdb->db_mutex);
1700 NSC_LOOKUP_LOG(DEBUG, "%s: no need to update\n");
1701 return (SUCCESS);
1704 if (NSCD_GET_STATUS((nss_pheader_t *)this_entry->buffer) ==
1705 NSS_SUCCESS) {
1706 /* positive hit */
1707 (void) mutex_lock(&ctx->stats_mutex);
1708 ctx->stats.pos_hits++;
1709 (void) mutex_unlock(&ctx->stats_mutex);
1711 /* update response buffer */
1712 if (copy_result(largs->buffer,
1713 this_entry->buffer) != NSS_SUCCESS) {
1714 (void) mutex_unlock(&nscdb->db_mutex);
1715 NSC_LOOKUP_LOG(ERROR,
1716 "%s: response buffer insufficient\n");
1717 return (SERVERERROR);
1720 (void) mutex_unlock(&nscdb->db_mutex);
1721 NSC_LOOKUP_LOG(DEBUG,
1722 "%s: positive entry in cache\n");
1723 return (SUCCESS);
1724 } else {
1725 /* negative hit */
1726 (void) mutex_lock(&ctx->stats_mutex);
1727 ctx->stats.neg_hits++;
1728 (void) mutex_unlock(&ctx->stats_mutex);
1730 NSCD_SET_STATUS((nss_pheader_t *)largs->buffer,
1731 NSCD_GET_STATUS(this_entry->buffer),
1732 NSCD_GET_ERRNO(this_entry->buffer));
1733 NSCD_SET_HERRNO((nss_pheader_t *)largs->buffer,
1734 NSCD_GET_HERRNO(this_entry->buffer));
1736 (void) mutex_unlock(&nscdb->db_mutex);
1737 NSC_LOOKUP_LOG(DEBUG,
1738 "%s: negative entry in cache\n");
1739 return (NOTFOUND);
1743 (void) mutex_unlock(&nscdb->db_mutex);
1744 NSC_LOOKUP_LOG(ERROR, "%s: cache backend failure\n");
1745 return (SERVERERROR);
1749 * NSCD cache backend lookup function
1751 /*ARGSUSED*/
1752 void
1753 nsc_lookup(nsc_lookup_args_t *largs, int flag) {
1755 nss_pheader_t *phdr = (nss_pheader_t *)largs->buffer;
1756 int rc;
1758 rc = lookup_int(largs, 0);
1760 if (NSCD_GET_STATUS(phdr) == NSS_TRYLOCAL)
1761 return;
1763 switch (rc) {
1765 case SUCCESS:
1766 NSCD_SET_STATUS(phdr, NSS_SUCCESS, 0);
1767 break;
1769 case NOTFOUND:
1770 NSCD_SET_STATUS(phdr, NSS_NOTFOUND, -1);
1771 break;
1773 case SERVERERROR:
1775 * status and errno should have been set in the phdr,
1776 * if not, set status to NSS_ERROR
1778 if (NSCD_STATUS_IS_OK(phdr)) {
1779 NSCD_SET_STATUS(phdr, NSS_ERROR, 0);
1781 break;
1783 case NOSERVER:
1784 NSCD_SET_STATUS(phdr, NSS_TRYLOCAL, -1);
1785 break;
1790 static nsc_ctx_t *
1791 init_cache_ctx(int i) {
1792 nsc_ctx_t *ctx;
1794 ctx = calloc(1, sizeof (nsc_ctx_t));
1795 if (ctx == NULL)
1796 return (NULL);
1798 /* init locks and semaphores */
1799 (void) mutex_init(&ctx->file_mutex, USYNC_THREAD, NULL);
1800 (void) rwlock_init(&ctx->cfg_rwlp, USYNC_THREAD, NULL);
1801 (void) mutex_init(&ctx->stats_mutex, USYNC_THREAD, NULL);
1802 (void) _nscd_init_cache_sema(&ctx->throttle_sema, cache_name[i]);
1803 cache_init_ctx[i](ctx);
1804 cache_ctx_p[i] = ctx;
1806 return (ctx);
1810 static void
1811 revalidate(nsc_ctx_t *ctx)
1813 for (;;) {
1814 int i, slp, interval, count;
1816 (void) rw_rdlock(&ctx->cfg_rwlp);
1817 slp = ctx->cfg.pos_ttl;
1818 count = ctx->cfg.keephot;
1819 (void) rw_unlock(&ctx->cfg_rwlp);
1821 if (slp < 60)
1822 slp = 60;
1823 if (count != 0) {
1824 interval = (slp/2)/count;
1825 if (interval == 0)
1826 interval = 1;
1827 (void) sleep(slp*2/3);
1828 for (i = 0; i < ctx->db_count; i++) {
1829 getxy_keepalive(ctx, ctx->nsc_db[i],
1830 count, interval);
1832 } else {
1833 (void) sleep(slp);
1839 static void
1840 getxy_keepalive(nsc_ctx_t *ctx, nsc_db_t *nscdb, int keep, int interval)
1842 nsc_keephot_t *table;
1843 nsc_entry_t *entry, *ptr;
1844 int i;
1845 nsc_lookup_args_t *largs;
1846 nss_pheader_t *phdr;
1847 int bufsiz;
1848 char *me = "getxy_keepalive";
1850 /* we won't be here if keep == 0 so need to check that */
1852 _NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_DEBUG)
1853 (me, "%s: keep alive\n", nscdb->name);
1855 if ((table = maken(keep)) == NULL) {
1856 _NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_ERROR)
1857 (me, "memory allocation failure\n");
1858 exit(1);
1861 (void) mutex_lock(&nscdb->db_mutex);
1862 entry = nscdb->qtail;
1863 while (entry != NULL) {
1864 /* leave pending calls alone */
1865 if (!(entry->stats.status & ST_PENDING)) {
1866 /* do_revalidate */
1867 (void) insertn(table, entry->stats.hits, entry);
1869 entry = entry->qnext;
1871 for (i = 1; i <= keep; i++) {
1872 if (table[i].ptr == NULL)
1873 continue;
1874 ptr = (nsc_entry_t *)table[i].ptr;
1875 phdr = (nss_pheader_t *)ptr->buffer;
1876 if (NSCD_GET_STATUS(phdr) == NSS_SUCCESS)
1878 * for positive cache, in addition to the packed
1879 * header size, allocate twice the size of the
1880 * existing result (in case the result grows
1881 * larger) plus 2K (for the file/compat backend to
1882 * process a possible large entry in the /etc files)
1884 bufsiz = phdr->data_off + 2 * phdr->data_len + 2048;
1885 else
1887 * for negative cache, allocate 8K buffer to
1888 * hold result in case the next lookup may
1889 * return something (in addition to the
1890 * packed header size)
1892 bufsiz = phdr->data_off + 8096;
1893 table[i].ptr = malloc(bufsiz);
1894 if (table[i].ptr == NULL) {
1895 (void) mutex_unlock(&nscdb->db_mutex);
1896 _NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_ERROR)
1897 (me, "memory allocation failure\n");
1898 exit(1);
1900 (void) memcpy(table[i].ptr, ptr->buffer, ptr->bufsize);
1901 ((nss_pheader_t *)table[i].ptr)->pbufsiz = bufsiz;
1902 table[i].num = bufsiz;
1904 (void) mutex_unlock(&nscdb->db_mutex);
1906 /* launch update thread for each keep hot entry */
1907 for (i = keep; i > 0; i--) {
1908 if (table[i].ptr == NULL)
1909 continue; /* unused slot in table */
1910 _NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_DEBUG)
1911 (me, "%s: launching update\n", nscdb->name);
1912 largs = (nsc_lookup_args_t *)malloc(sizeof (*largs));
1913 if (largs == NULL) {
1914 _NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_ERROR)
1915 (me, "memory allocation failure\n");
1916 exit(1);
1918 largs->buffer = table[i].ptr;
1919 largs->bufsize = table[i].num;
1920 largs->ctx = ctx;
1921 largs->nscdb = nscdb;
1922 if (launch_update(largs) < 0)
1923 exit(1);
1924 (void) sleep(interval);
1928 * The update thread will handle freeing of buffer and largs.
1929 * Free the table here.
1931 free(table);
1935 static int
1936 launch_update(nsc_lookup_args_t *in)
1938 char *me = "launch_update";
1939 int errnum;
1941 errnum = thr_create(NULL, NULL, (void *(*)(void*))do_update,
1942 in, 0|THR_DETACHED, NULL);
1943 if (errnum != 0) {
1944 _NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_ERROR)
1945 (me, "%s: thread creation failure (%d)\n",
1946 in->nscdb->name, errnum);
1947 return (-1);
1949 return (0);
1953 static void
1954 do_update(nsc_lookup_args_t *in) {
1955 nss_pheader_t *phdr = (nss_pheader_t *)in->buffer;
1957 /* update the length of the data buffer */
1958 phdr->data_len = phdr->pbufsiz - phdr->data_off;
1960 (void) lookup_int(in, UPDATEBIT);
1961 free(in->buffer);
1962 free(in);
1967 * Invalidate cache
1969 void
1970 nsc_invalidate(nsc_ctx_t *ctx, char *dbname, nsc_ctx_t **ctxs)
1972 int i;
1973 char *me = "nsc_invalidate";
1975 if (ctx) {
1976 ctx_invalidate(ctx);
1977 return;
1980 if (dbname) {
1981 if ((i = get_cache_idx(dbname)) == -1) {
1982 _NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_WARNING)
1983 (me, "%s: invalid cache name\n", dbname);
1984 return;
1986 if ((ctx = cache_ctx_p[i]) == NULL) {
1987 _NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_WARNING)
1988 (me, "%s: no cache context found\n",
1989 dbname);
1990 return;
1992 ctx_invalidate(ctx);
1993 return;
1996 if (ctxs == NULL)
1997 ctxs = cache_ctx_p;
1999 for (i = 0; i < CACHE_CTX_COUNT; i++) {
2000 if (ctxs[i] != NULL)
2001 ctx_invalidate(ctxs[i]);
2007 * Invalidate cache by context
2009 static void
2010 ctx_invalidate(nsc_ctx_t *ctx)
2012 int i;
2013 nsc_entry_t *entry;
2014 char *me = "ctx_invalidate";
2016 _NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_DEBUG)
2017 (me, "%s: invalidate cache\n", ctx->dbname);
2019 for (i = 0; i < ctx->db_count; i++) {
2020 if (ctx->nsc_db[i] == NULL)
2021 continue;
2022 (void) mutex_lock(&ctx->nsc_db[i]->db_mutex);
2023 entry = ctx->nsc_db[i]->qtail;
2024 while (entry != NULL) {
2025 /* leave pending calls alone */
2026 if (!(entry->stats.status & ST_PENDING))
2027 entry->stats.status = ST_DISCARD;
2028 entry = entry->qnext;
2030 (void) mutex_unlock(&ctx->nsc_db[i]->db_mutex);
2033 (void) mutex_lock(&ctx->stats_mutex);
2034 ctx->stats.invalidate_count++;
2035 (void) mutex_unlock(&ctx->stats_mutex);
2040 * Free nsc_entry_t
2042 * Pre-reqs:
2043 * nscdb->db_mutex lock must be held before calling this function
2045 static void
2046 delete_entry(nsc_db_t *nscdb, nsc_ctx_t *ctx, nsc_entry_t *entry) {
2047 uint_t hash;
2049 avl_remove(&nscdb->tree, entry);
2050 HASH_REMOVE(nscdb, entry, hash, nscd_false);
2051 queue_remove(nscdb, entry);
2052 if (entry->buffer != NULL) {
2053 free(entry->buffer);
2054 entry->buffer = NULL;
2056 umem_cache_free(nsc_entry_cache, entry);
2057 (void) mutex_lock(&ctx->stats_mutex);
2058 ctx->stats.entries--;
2059 (void) mutex_unlock(&ctx->stats_mutex);
2063 static nscd_rc_t
2064 lookup_cache(nsc_lookup_args_t *largs, nscd_cfg_cache_t *cfgp,
2065 nss_XbyY_args_t *argp, char *whoami, nsc_entry_t **entry)
2067 nsc_db_t *nscdb;
2068 nsc_ctx_t *ctx;
2069 uint_t hash;
2070 avl_index_t pos;
2071 ulong_t nentries;
2072 nsc_entry_t find_entry, *node;
2073 char *me = "lookup_cache";
2075 ctx = largs->ctx;
2076 nscdb = largs->nscdb;
2078 /* set the search key */
2079 find_entry.key = argp->key; /* struct copy (not deep) */
2081 /* lookup the hash table ==> O(1) */
2082 if (nscdb->htable) {
2083 *entry = hash_find(nscdb, &find_entry, &hash, nscd_true);
2084 if (*entry != NULL) {
2085 (void) queue_adjust(nscdb, *entry);
2086 return (NSCD_SUCCESS);
2090 /* if not found, lookup the AVL tree ==> O(log n) */
2091 *entry = (nsc_entry_t *)avl_find(&nscdb->tree, &find_entry, &pos);
2092 if (*entry != NULL) {
2093 (void) queue_adjust(nscdb, *entry);
2094 /* move it to the hash table */
2095 if (nscdb->htable) {
2096 if (nscdb->htable[hash] == NULL ||
2097 (*entry)->stats.hits >=
2098 nscdb->htable[hash]->stats.hits) {
2099 nscdb->htable[hash] = *entry;
2102 return (NSCD_SUCCESS);
2105 /* entry not found in the cache */
2106 _NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_DEBUG)
2107 (me, "%s: cache miss\n", whoami);
2109 if (cfgp->avoid_ns == nscd_true) {
2110 _NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_DEBUG)
2111 (me, "%s: avoid name service\n", whoami);
2112 return (NSCD_DB_ENTRY_NOT_FOUND);
2115 /* allocate memory for new entry (stub) */
2116 *entry = (nsc_entry_t *)umem_cache_alloc(nsc_entry_cache,
2117 UMEM_DEFAULT);
2118 if (*entry == NULL) {
2119 _NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_ERROR)
2120 (me, "%s: memory allocation failure\n", whoami);
2121 return (NSCD_NO_MEMORY);
2123 (void) memset(*entry, 0, sizeof (**entry));
2126 * Note that the actual data for the key is stored within
2127 * the largs->buffer (input buffer to nsc_lookup).
2128 * find_entry.key only contains pointers to this data.
2130 * If largs->buffer will be re-allocated by nss_psearch
2131 * then (*entry)->key will have dangling pointers.
2132 * In such case, the following assignment needs to be
2133 * replaced by code that duplicates the key.
2135 (*entry)->key = find_entry.key;
2138 * Add the entry to the cache.
2140 avl_insert(&nscdb->tree, *entry, pos); /* O(log n) */
2141 (void) queue_adjust(nscdb, *entry); /* constant */
2142 if (nscdb->htable) /* constant */
2143 nscdb->htable[hash] = *entry;
2144 (*entry)->stats.status = ST_NEW_ENTRY;
2146 (void) mutex_lock(&ctx->stats_mutex);
2147 nentries = ++(ctx->stats.entries);
2148 (void) mutex_unlock(&ctx->stats_mutex);
2150 /* Have we exceeded max entries ? */
2151 if (cfgp->maxentries > 0 && nentries > cfgp->maxentries) {
2152 _NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_DEBUG)
2153 (me, "%s: maximum entries exceeded -- "
2154 "deleting least recently used entry\n",
2155 whoami);
2157 node = nscdb->qhead;
2158 while (node != NULL && node != *entry) {
2159 if (node->stats.status == ST_DISCARD ||
2160 !(node->stats.status & ST_PENDING)) {
2161 delete_entry(nscdb, ctx, node);
2162 break;
2164 node = node->qprev;
2168 * It's okay if we were not able to find one to delete.
2169 * The reaper (when invoked) will return the cache to a
2170 * safe level.
2174 return (NSCD_SUCCESS);
2177 static void
2178 reaper(nsc_ctx_t *ctx)
2180 uint_t ttl, extra_sleep, total_sleep, intervals;
2181 uint_t nodes_per_interval, seconds_per_interval;
2182 ulong_t nsc_entries;
2183 char *me = "reaper";
2185 for (;;) {
2186 (void) mutex_lock(&ctx->stats_mutex);
2187 nsc_entries = ctx->stats.entries;
2188 (void) mutex_unlock(&ctx->stats_mutex);
2190 (void) rw_rdlock(&ctx->cfg_rwlp);
2191 ttl = ctx->cfg.pos_ttl;
2192 (void) rw_unlock(&ctx->cfg_rwlp);
2194 if (nsc_entries == 0) {
2195 _NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_DEBUG)
2196 (me, "%s: nothing to reap\n", ctx->dbname);
2198 /* sleep for atleast 60 seconds */
2199 if (ttl < 60)
2200 ttl = 60;
2201 _NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_DEBUG)
2202 (me, "%s: sleep %d\n", ctx->dbname, ttl);
2203 (void) sleep(ttl);
2204 continue;
2207 if (ttl < 32) ttl = 32;
2208 if (ttl > (1<<28)) ttl = 1<<28;
2211 * minimum nodes_per_interval = 256 or 1<<8
2212 * maximum nodes_per_interval = nsc_entries
2213 * minimum seconds_per_interval = 32 or 1<<5
2214 * maximum_seconds_per_interval = ttl
2216 if (nsc_entries <= ttl) {
2217 intervals = (nsc_entries >> 8) + 1;
2218 seconds_per_interval = ttl / intervals;
2219 nodes_per_interval = 256;
2220 } else {
2221 intervals = (ttl >> 5) + 1;
2222 seconds_per_interval = 32;
2223 nodes_per_interval = nsc_entries / intervals;
2224 if (nodes_per_interval < 256)
2225 nodes_per_interval = 256;
2228 _NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_DEBUG)
2229 (me, "%s: total entries = %d, "
2230 "seconds per interval = %d, "
2231 "nodes per interval = %d\n",
2232 ctx->dbname, nsc_entries, seconds_per_interval,
2233 nodes_per_interval);
2234 total_sleep = reap_cache(ctx, nodes_per_interval,
2235 seconds_per_interval);
2236 extra_sleep = 1 + ttl - total_sleep;
2237 if (extra_sleep > 0)
2238 (void) sleep(extra_sleep);
2243 static uint_t
2244 reap_cache(nsc_ctx_t *ctx, uint_t nodes_per_interval,
2245 uint_t seconds_per_interval)
2247 uint_t nodes_togo, total_sleep;
2248 time_t now;
2249 nsc_entry_t *node, *next_node;
2250 nsc_db_t *nscdb;
2251 uint_t primes[] = {_NSC_HTSIZE_PRIMES};
2252 ulong_t count, nentries, maxentries;
2253 int i, slot, value, newhtsize;
2254 char *me = "reap_cache";
2256 count = 0;
2257 total_sleep = 0;
2258 nodes_togo = nodes_per_interval;
2259 now = time(NULL);
2261 for (i = 0; i < ctx->db_count; i++) {
2262 nscdb = ctx->nsc_db[i];
2263 (void) mutex_lock(&nscdb->db_mutex);
2264 nscdb->reap_node = nscdb->qtail;
2265 while (nscdb->reap_node != NULL) {
2266 if (nodes_togo == 0) {
2267 (void) mutex_unlock(&nscdb->db_mutex);
2268 (void) sleep(seconds_per_interval);
2269 total_sleep += seconds_per_interval;
2270 nodes_togo = nodes_per_interval;
2271 now = time(NULL);
2272 (void) mutex_lock(&nscdb->db_mutex);
2274 /* delete ST_DISCARD and expired nodes */
2275 if ((node = nscdb->reap_node) == NULL)
2276 break;
2277 if (node->stats.status == ST_DISCARD ||
2278 (!(node->stats.status & ST_PENDING) &&
2279 node->stats.timestamp < now)) {
2281 * Delete entry if its discard flag is
2282 * set OR if it has expired. Entries
2283 * with pending updates are not
2284 * deleted.
2285 * nscdb->reap_node will be adjusted
2286 * by delete_entry()
2288 delete_entry(nscdb, ctx, node);
2289 count++;
2290 } else {
2291 nscdb->reap_node = node->qnext;
2293 nodes_togo--;
2296 if (nscdb->htsize == 0) {
2297 (void) mutex_unlock(&nscdb->db_mutex);
2298 continue;
2302 * Dynamic adjustment of hash table size.
2304 * Hash table size is roughly 1/8th of the
2305 * total entries. However the size is changed
2306 * only when the number of entries double or
2307 * reduced by half
2309 nentries = avl_numnodes(&nscdb->tree);
2310 for (slot = 0, value = _NSC_INIT_HTSIZE_SLOT_VALUE;
2311 slot < _NSC_HTSIZE_NUM_SLOTS && nentries > value;
2312 value = (value << 1) + 1, slot++)
2314 if (nscdb->hash_type == nsc_ht_power2)
2315 newhtsize = _NSC_INIT_HTSIZE_POWER2 << slot;
2316 else
2317 newhtsize = primes[slot];
2319 /* Recommended size is same as the current size. Done */
2320 if (nscdb->htsize == newhtsize) {
2321 (void) mutex_unlock(&nscdb->db_mutex);
2322 continue;
2325 _NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_DEBUG)
2326 (me, "%s: resizing hash table from %d to %d\n",
2327 nscdb->name, nscdb->htsize, newhtsize);
2330 * Dump old hashes because it would be time
2331 * consuming to rehash them.
2333 (void) free(nscdb->htable);
2334 nscdb->htable = calloc(newhtsize, sizeof (*(nscdb->htable)));
2335 if (nscdb->htable == NULL) {
2336 _NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_ERROR)
2337 (me, "%s: memory allocation failure\n",
2338 nscdb->name);
2339 /* -1 to try later */
2340 nscdb->htsize = -1;
2341 } else {
2342 nscdb->htsize = newhtsize;
2344 (void) mutex_unlock(&nscdb->db_mutex);
2347 _NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_DEBUG)
2348 (me, "%s: reaped %lu entries\n", ctx->dbname, count);
2351 * if cache is almost full then reduce it to a safe level by
2352 * evicting LRU entries
2355 (void) rw_rdlock(&ctx->cfg_rwlp);
2356 maxentries = ctx->cfg.maxentries;
2357 (void) rw_unlock(&ctx->cfg_rwlp);
2359 /* No limit on number of entries. Done */
2360 if (maxentries == 0)
2361 goto out;
2363 (void) mutex_lock(&ctx->stats_mutex);
2364 nentries = ctx->stats.entries;
2365 (void) mutex_unlock(&ctx->stats_mutex);
2367 /* what is the percentage of cache used ? */
2368 value = (nentries * 100) / maxentries;
2369 if (value < _NSC_EVICTION_START_LEVEL)
2370 goto out;
2373 * cache needs to be reduced to a safe level
2375 value -= _NSC_EVICTION_SAFE_LEVEL;
2376 for (i = 0, count = 0; i < ctx->db_count; i++) {
2378 * Reduce each subcache by 'value' percent
2380 nscdb = ctx->nsc_db[i];
2381 (void) mutex_lock(&nscdb->db_mutex);
2382 nodes_togo = (value * avl_numnodes(&nscdb->tree)) / 100;
2384 /* Start from LRU entry i.e queue head */
2385 next_node = nscdb->qhead;
2386 while (nodes_togo > 0 && next_node != NULL) {
2387 node = next_node;
2388 next_node = next_node->qprev;
2389 if (node->stats.status == ST_DISCARD ||
2390 !(node->stats.status & ST_PENDING)) {
2391 /* Leave nodes with pending updates alone */
2392 delete_entry(nscdb, ctx, node);
2393 count++;
2394 nodes_togo--;
2397 (void) mutex_unlock(&nscdb->db_mutex);
2400 _NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_DEBUG)
2401 (me, "%s: evicted %lu LRU entries\n", ctx->dbname, count);
2403 out:
2404 return (total_sleep);