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