/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright (c) 2006, 2010, Oracle and/or its affiliates. All rights reserved. * Copyright 2012 Milan Jurik. All rights reserved. */ /* * Cache routines for nscd */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "cache.h" #include "nscd_door.h" #include "nscd_log.h" #include "nscd_config.h" #include "nscd_frontend.h" #include "nscd_switch.h" #define SUCCESS 0 #define NOTFOUND -1 #define SERVERERROR -2 #define NOSERVER -3 #define CONTINUE -4 static nsc_db_t *nsc_get_db(nsc_ctx_t *, int); static nscd_rc_t lookup_cache(nsc_lookup_args_t *, nscd_cfg_cache_t *, nss_XbyY_args_t *, char *, nsc_entry_t **); static uint_t reap_cache(nsc_ctx_t *, uint_t, uint_t); static void delete_entry(nsc_db_t *, nsc_ctx_t *, nsc_entry_t *); static void print_stats(nscd_cfg_stat_cache_t *); static void print_cfg(nscd_cfg_cache_t *); static int lookup_int(nsc_lookup_args_t *, int); #ifdef NSCD_DEBUG static void print_entry(nsc_db_t *, time_t, nsc_entry_t *); static void avl_dump(nsc_db_t *, time_t); static void hash_dump(nsc_db_t *, time_t); #endif /* NSCD_DEBUG */ static nsc_entry_t *hash_find(nsc_db_t *, nsc_entry_t *, uint_t *, nscd_bool_t); static void queue_adjust(nsc_db_t *, nsc_entry_t *); static void queue_remove(nsc_db_t *, nsc_entry_t *); #ifdef NSCD_DEBUG static void queue_dump(nsc_db_t *, time_t); #endif /* NSCD_DEBUG */ static int launch_update(nsc_lookup_args_t *); static void do_update(nsc_lookup_args_t *); static void getxy_keepalive(nsc_ctx_t *, nsc_db_t *, int, int); static void ctx_info(nsc_ctx_t *); static void ctx_info_nolock(nsc_ctx_t *); static void ctx_invalidate(nsc_ctx_t *); static void nsc_db_str_key_getlogstr(char *, char *, size_t, nss_XbyY_args_t *); static void nsc_db_int_key_getlogstr(char *, char *, size_t, nss_XbyY_args_t *); static void nsc_db_any_key_getlogstr(char *, char *, size_t, nss_XbyY_args_t *); static int nsc_db_cis_key_compar(const void *, const void *); static int nsc_db_ces_key_compar(const void *, const void *); static int nsc_db_int_key_compar(const void *, const void *); static uint_t nsc_db_cis_key_gethash(nss_XbyY_key_t *, int); static uint_t nsc_db_ces_key_gethash(nss_XbyY_key_t *, int); static uint_t nsc_db_int_key_gethash(nss_XbyY_key_t *, int); static umem_cache_t *nsc_entry_cache; static nsc_ctx_t *init_cache_ctx(int); static void reaper(nsc_ctx_t *); static void revalidate(nsc_ctx_t *); static nss_status_t dup_packed_buffer(void *src, void *dst) { nsc_lookup_args_t *s = (nsc_lookup_args_t *)src; nsc_entry_t *d = (nsc_entry_t *)dst; nss_pheader_t *sphdr = (nss_pheader_t *)s->buffer; nss_pheader_t *dphdr = (nss_pheader_t *)d->buffer; int slen, new_pbufsiz = 0; if (NSCD_GET_STATUS(sphdr) != NSS_SUCCESS) { /* no result, copy header only (status, errno, etc) */ slen = sphdr->data_off; } else { /* * lookup result returned, data to copy is the packed * header plus result (add 1 for the terminating NULL * just in case) */ slen = sphdr->data_off + sphdr->data_len + 1; } /* allocate cache packed buffer */ if (dphdr != NULL && d->bufsize <= slen && d->bufsize != 0) { /* old buffer too small, free it */ free(dphdr); d->buffer = NULL; d->bufsize = 0; dphdr = NULL; } if (dphdr == NULL) { /* get new buffer */ dphdr = calloc(1, slen + 1); if (dphdr == NULL) return (NSS_ERROR); d->buffer = dphdr; d->bufsize = slen + 1; new_pbufsiz = slen + 1; } (void) memcpy(dphdr, sphdr, slen); if (new_pbufsiz != 0) dphdr->pbufsiz = new_pbufsiz; return (NSS_SUCCESS); } char *cache_name[CACHE_CTX_COUNT] = { NSS_DBNAM_PASSWD, NSS_DBNAM_GROUP, NSS_DBNAM_HOSTS, NSS_DBNAM_IPNODES, NSS_DBNAM_EXECATTR, NSS_DBNAM_PROFATTR, NSS_DBNAM_USERATTR, NSS_DBNAM_ETHERS, NSS_DBNAM_RPC, NSS_DBNAM_PROTOCOLS, NSS_DBNAM_NETWORKS, NSS_DBNAM_BOOTPARAMS, NSS_DBNAM_AUTHATTR, NSS_DBNAM_SERVICES, NSS_DBNAM_NETMASKS, NSS_DBNAM_PRINTERS, NSS_DBNAM_PROJECT, NSS_DBNAM_TSOL_TP, NSS_DBNAM_TSOL_RH }; typedef void (*cache_init_ctx_t)(nsc_ctx_t *); static cache_init_ctx_t cache_init_ctx[CACHE_CTX_COUNT] = { passwd_init_ctx, group_init_ctx, host_init_ctx, ipnode_init_ctx, exec_init_ctx, prof_init_ctx, user_init_ctx, ether_init_ctx, rpc_init_ctx, proto_init_ctx, net_init_ctx, bootp_init_ctx, auth_init_ctx, serv_init_ctx, netmask_init_ctx, printer_init_ctx, project_init_ctx, tnrhtp_init_ctx, tnrhdb_init_ctx }; nsc_ctx_t *cache_ctx_p[CACHE_CTX_COUNT] = { 0 }; static nscd_cfg_stat_cache_t null_stats = { 0 }; static nscd_cfg_global_cache_t global_cfg; /* * Given database name 'dbname' find cache index */ int get_cache_idx(char *dbname) { int i; char *nsc_name; for (i = 0; i < CACHE_CTX_COUNT; i++) { nsc_name = cache_name[i]; if (strcmp(nsc_name, dbname) == 0) return (i); } return (-1); } /* * Given database name 'dbname' retrieve cache context, * if not created yet, allocate and initialize it. */ static nscd_rc_t get_cache_ctx(char *dbname, nsc_ctx_t **ctx) { int i; *ctx = NULL; i = get_cache_idx(dbname); if (i == -1) return (NSCD_INVALID_ARGUMENT); if ((*ctx = cache_ctx_p[i]) == NULL) { *ctx = init_cache_ctx(i); if (*ctx == NULL) return (NSCD_NO_MEMORY); } return (NSCD_SUCCESS); } /* * Generate a log string to identify backend operation in debug logs */ static void nsc_db_str_key_getlogstr(char *name, char *whoami, size_t len, nss_XbyY_args_t *argp) { (void) snprintf(whoami, len, "%s [key=%s]", name, argp->key.name); } static void nsc_db_int_key_getlogstr(char *name, char *whoami, size_t len, nss_XbyY_args_t *argp) { (void) snprintf(whoami, len, "%s [key=%d]", name, argp->key.number); } /*ARGSUSED*/ static void nsc_db_any_key_getlogstr(char *name, char *whoami, size_t len, nss_XbyY_args_t *argp) { (void) snprintf(whoami, len, "%s", name); } /* * Returns cache based on dbop */ static nsc_db_t * nsc_get_db(nsc_ctx_t *ctx, int dbop) { int i; for (i = 0; i < ctx->db_count; i++) { if (ctx->nsc_db[i] && dbop == ctx->nsc_db[i]->dbop) return (ctx->nsc_db[i]); } return (NULL); } /* * integer compare routine for _NSC_DB_INT_KEY */ static int nsc_db_int_key_compar(const void *n1, const void *n2) { nsc_entry_t *e1, *e2; e1 = (nsc_entry_t *)n1; e2 = (nsc_entry_t *)n2; return (_NSC_INT_KEY_CMP(e1->key.number, e2->key.number)); } /* * case sensitive name compare routine for _NSC_DB_CES_KEY */ static int nsc_db_ces_key_compar(const void *n1, const void *n2) { nsc_entry_t *e1, *e2; int res, l1, l2; e1 = (nsc_entry_t *)n1; e2 = (nsc_entry_t *)n2; l1 = strlen(e1->key.name); l2 = strlen(e2->key.name); res = strncmp(e1->key.name, e2->key.name, (l1 > l2)?l1:l2); return (_NSC_INT_KEY_CMP(res, 0)); } /* * case insensitive name compare routine _NSC_DB_CIS_KEY */ static int nsc_db_cis_key_compar(const void *n1, const void *n2) { nsc_entry_t *e1, *e2; int res, l1, l2; e1 = (nsc_entry_t *)n1; e2 = (nsc_entry_t *)n2; l1 = strlen(e1->key.name); l2 = strlen(e2->key.name); res = strncasecmp(e1->key.name, e2->key.name, (l1 > l2)?l1:l2); return (_NSC_INT_KEY_CMP(res, 0)); } /* * macro used to generate elf hashes for strings */ #define _NSC_ELF_STR_GETHASH(func, str, htsize, hval) \ hval = 0; \ while (*str) { \ uint_t g; \ hval = (hval << 4) + func(*str++); \ if ((g = (hval & 0xf0000000)) != 0) \ hval ^= g >> 24; \ hval &= ~g; \ } \ hval %= htsize; /* * cis hash function */ uint_t cis_gethash(const char *key, int htsize) { uint_t hval; if (key == NULL) return (0); _NSC_ELF_STR_GETHASH(tolower, key, htsize, hval); return (hval); } /* * ces hash function */ uint_t ces_gethash(const char *key, int htsize) { uint_t hval; if (key == NULL) return (0); _NSC_ELF_STR_GETHASH(, key, htsize, hval); return (hval); } /* * one-at-a-time hash function */ uint_t db_gethash(const void *key, int len, int htsize) { uint_t hval, i; const char *str = key; if (str == NULL) return (0); for (hval = 0, i = 0; i < len; i++) { hval += str[i]; hval += (hval << 10); hval ^= (hval >> 6); } hval += (hval << 3); hval ^= (hval >> 11); hval += (hval << 15); return (hval % htsize); } /* * case insensitive name gethash routine _NSC_DB_CIS_KEY */ static uint_t nsc_db_cis_key_gethash(nss_XbyY_key_t *key, int htsize) { return (cis_gethash(key->name, htsize)); } /* * case sensitive name gethash routine _NSC_DB_CES_KEY */ static uint_t nsc_db_ces_key_gethash(nss_XbyY_key_t *key, int htsize) { return (ces_gethash(key->name, htsize)); } /* * integer gethash routine _NSC_DB_INT_KEY */ static uint_t nsc_db_int_key_gethash(nss_XbyY_key_t *key, int htsize) { return (db_gethash(&key->number, sizeof (key->number), htsize)); } /* * Find entry in the hash table * if cmp == nscd_true) * return entry only if the keys match * else * return entry in the hash location without checking the keys * */ static nsc_entry_t * hash_find(nsc_db_t *nscdb, nsc_entry_t *entry, uint_t *hash, nscd_bool_t cmp) { nsc_entry_t *hashentry; if (nscdb->gethash) *hash = nscdb->gethash(&entry->key, nscdb->htsize); else return (NULL); hashentry = nscdb->htable[*hash]; if (cmp == nscd_false || hashentry == NULL) return (hashentry); if (nscdb->compar) { if (nscdb->compar(entry, hashentry) == 0) return (hashentry); } return (NULL); } #define HASH_REMOVE(nscdb, entry, hash, cmp) \ if (nscdb->htable) { \ if (entry == hash_find(nscdb, entry, &hash, cmp)) \ nscdb->htable[hash] = NULL; \ } #define HASH_INSERT(nscdb, entry, hash, cmp) \ if (nscdb->htable) { \ (void) hash_find(nscdb, entry, &hash, cmp); \ nscdb->htable[hash] = entry; \ } #ifdef NSCD_DEBUG static void print_entry(nsc_db_t *nscdb, time_t now, nsc_entry_t *entry) { nss_XbyY_args_t args; char whoami[512]; switch (entry->stats.status) { case ST_NEW_ENTRY: (void) fprintf(stdout, gettext("\t status: new entry\n")); return; case ST_UPDATE_PENDING: (void) fprintf(stdout, gettext("\t status: update pending\n")); return; case ST_LOOKUP_PENDING: (void) fprintf(stdout, gettext("\t status: lookup pending\n")); return; case ST_DISCARD: (void) fprintf(stdout, gettext("\t status: discarded entry\n")); return; default: if (entry->stats.timestamp < now) (void) fprintf(stdout, gettext("\t status: expired (%d seconds ago)\n"), now - entry->stats.timestamp); else (void) fprintf(stdout, gettext("\t status: valid (expiry in %d seconds)\n"), entry->stats.timestamp - now); break; } (void) fprintf(stdout, gettext("\t hits: %u\n"), entry->stats.hits); args.key = entry->key; (void) nscdb->getlogstr(nscdb->name, whoami, sizeof (whoami), &args); (void) fprintf(stdout, "\t %s\n", whoami); } #endif /* NSCD_DEBUG */ static void print_stats(nscd_cfg_stat_cache_t *statsp) { (void) fprintf(stdout, gettext("\n\t STATISTICS:\n")); (void) fprintf(stdout, gettext("\t positive hits: %lu\n"), statsp->pos_hits); (void) fprintf(stdout, gettext("\t negative hits: %lu\n"), statsp->neg_hits); (void) fprintf(stdout, gettext("\t positive misses: %lu\n"), statsp->pos_misses); (void) fprintf(stdout, gettext("\t negative misses: %lu\n"), statsp->neg_misses); (void) fprintf(stdout, gettext("\t total entries: %lu\n"), statsp->entries); (void) fprintf(stdout, gettext("\t queries queued: %lu\n"), statsp->wait_count); (void) fprintf(stdout, gettext("\t queries dropped: %lu\n"), statsp->drop_count); (void) fprintf(stdout, gettext("\t cache invalidations: %lu\n"), statsp->invalidate_count); _NSC_GET_HITRATE(statsp); (void) fprintf(stdout, gettext("\t cache hit rate: %10.1f\n"), statsp->hitrate); } static void print_cfg(nscd_cfg_cache_t *cfgp) { (void) fprintf(stdout, gettext("\n\t CONFIG:\n")); (void) fprintf(stdout, gettext("\t enabled: %s\n"), yes_no(cfgp->enable)); (void) fprintf(stdout, gettext("\t per user cache: %s\n"), yes_no(cfgp->per_user)); (void) fprintf(stdout, gettext("\t avoid name service: %s\n"), yes_no(cfgp->avoid_ns)); (void) fprintf(stdout, gettext("\t check file: %s\n"), yes_no(cfgp->check_files)); (void) fprintf(stdout, gettext("\t check file interval: %d\n"), cfgp->check_interval); (void) fprintf(stdout, gettext("\t positive ttl: %d\n"), cfgp->pos_ttl); (void) fprintf(stdout, gettext("\t negative ttl: %d\n"), cfgp->neg_ttl); (void) fprintf(stdout, gettext("\t keep hot count: %d\n"), cfgp->keephot); (void) fprintf(stdout, gettext("\t hint size: %d\n"), cfgp->hint_size); (void) fprintf(stdout, gettext("\t max entries: %lu%s"), cfgp->maxentries, cfgp->maxentries?"\n":" (unlimited)\n"); } #ifdef NSCD_DEBUG static void hash_dump(nsc_db_t *nscdb, time_t now) { nsc_entry_t *entry; int i; (void) fprintf(stdout, gettext("\n\nHASH TABLE:\n")); for (i = 0; i < nscdb->htsize; i++) { if ((entry = nscdb->htable[i]) != NULL) { (void) fprintf(stdout, "hash[%d]:\n", i); print_entry(nscdb, now, entry); } } } #endif /* NSCD_DEBUG */ #ifdef NSCD_DEBUG static void avl_dump(nsc_db_t *nscdb, time_t now) { nsc_entry_t *entry; int i; (void) fprintf(stdout, gettext("\n\nAVL TREE:\n")); for (entry = avl_first(&nscdb->tree), i = 0; entry != NULL; entry = avl_walk(&nscdb->tree, entry, AVL_AFTER)) { (void) fprintf(stdout, "avl node[%d]:\n", i++); print_entry(nscdb, now, entry); } } #endif /* NSCD_DEBUG */ #ifdef NSCD_DEBUG static void queue_dump(nsc_db_t *nscdb, time_t now) { nsc_entry_t *entry; int i; (void) fprintf(stdout, gettext("\n\nCACHE [name=%s, nodes=%lu]:\n"), nscdb->name, avl_numnodes(&nscdb->tree)); (void) fprintf(stdout, gettext("Starting with the most recently accessed:\n")); for (entry = nscdb->qtail, i = 0; entry; entry = entry->qnext) { (void) fprintf(stdout, "entry[%d]:\n", i++); print_entry(nscdb, now, entry); } } #endif /* NSCD_DEBUG */ static void queue_remove(nsc_db_t *nscdb, nsc_entry_t *entry) { if (nscdb->qtail == entry) nscdb->qtail = entry->qnext; else entry->qprev->qnext = entry->qnext; if (nscdb->qhead == entry) nscdb->qhead = entry->qprev; else entry->qnext->qprev = entry->qprev; if (nscdb->reap_node == entry) nscdb->reap_node = entry->qnext; entry->qnext = entry->qprev = NULL; } static void queue_adjust(nsc_db_t *nscdb, nsc_entry_t *entry) { #ifdef NSCD_DEBUG assert(nscdb->qtail || entry->qnext == NULL && entry->qprev == NULL); assert(nscdb->qtail && nscdb->qhead || nscdb->qtail == NULL && nscdb->qhead == NULL); assert(entry->qprev || entry->qnext == NULL || nscdb->qtail == entry); #endif /* NSCD_DEBUG */ /* already in the desired position */ if (nscdb->qtail == entry) return; /* new queue */ if (nscdb->qtail == NULL) { nscdb->qhead = nscdb->qtail = entry; return; } /* new entry (prev == NULL AND tail != entry) */ if (entry->qprev == NULL) { nscdb->qtail->qprev = entry; entry->qnext = nscdb->qtail; nscdb->qtail = entry; return; } /* existing entry */ if (nscdb->reap_node == entry) nscdb->reap_node = entry->qnext; if (nscdb->qhead == entry) nscdb->qhead = entry->qprev; else entry->qnext->qprev = entry->qprev; entry->qprev->qnext = entry->qnext; entry->qprev = NULL; entry->qnext = nscdb->qtail; nscdb->qtail->qprev = entry; nscdb->qtail = entry; } /* * Init cache */ nscd_rc_t init_cache(int debug_level) { int cflags; cflags = (debug_level > 0)?0:UMC_NODEBUG; nsc_entry_cache = umem_cache_create("nsc_entry_cache", sizeof (nsc_entry_t), 0, NULL, NULL, NULL, NULL, NULL, cflags); if (nsc_entry_cache == NULL) return (NSCD_NO_MEMORY); return (NSCD_SUCCESS); } /* * Create cache */ nsc_db_t * make_cache(enum db_type dbtype, int dbop, char *name, int (*compar) (const void *, const void *), void (*getlogstr)(char *, char *, size_t, nss_XbyY_args_t *), uint_t (*gethash)(nss_XbyY_key_t *, int), enum hash_type httype, int htsize) { nsc_db_t *nscdb; char *me = "make_cache"; nscdb = (nsc_db_t *)malloc(sizeof (*nscdb)); if (nscdb == NULL) { _NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_ERROR) (me, "%s: memory allocation failure\n", name); goto out; } (void) memset(nscdb, 0, sizeof (*nscdb)); nscdb->dbop = dbop; nscdb->name = name; nscdb->db_type = dbtype; /* Assign compare routine */ if (compar == NULL) { if (_NSC_DB_CES_KEY(nscdb)) nscdb->compar = nsc_db_ces_key_compar; else if (_NSC_DB_CIS_KEY(nscdb)) nscdb->compar = nsc_db_cis_key_compar; else if (_NSC_DB_INT_KEY(nscdb)) nscdb->compar = nsc_db_int_key_compar; else assert(0); } else { nscdb->compar = compar; } /* The cache is an AVL tree */ avl_create(&nscdb->tree, nscdb->compar, sizeof (nsc_entry_t), offsetof(nsc_entry_t, avl_link)); /* Assign log routine */ if (getlogstr == NULL) { if (_NSC_DB_STR_KEY(nscdb)) nscdb->getlogstr = nsc_db_str_key_getlogstr; else if (_NSC_DB_INT_KEY(nscdb)) nscdb->getlogstr = nsc_db_int_key_getlogstr; else nscdb->getlogstr = nsc_db_any_key_getlogstr; } else { nscdb->getlogstr = getlogstr; } /* The AVL tree based cache uses a hash table for quick access */ if (htsize != 0) { /* Determine hash table size based on type */ nscdb->hash_type = httype; if (htsize < 0) { switch (httype) { case nsc_ht_power2: htsize = _NSC_INIT_HTSIZE_POWER2; break; case nsc_ht_prime: case nsc_ht_default: default: htsize = _NSC_INIT_HTSIZE_PRIME; } } nscdb->htsize = htsize; /* Create the hash table */ nscdb->htable = calloc(htsize, sizeof (*(nscdb->htable))); if (nscdb->htable == NULL) { _NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_ERROR) (me, "%s: memory allocation failure\n", name); goto out; } /* Assign gethash routine */ if (gethash == NULL) { if (_NSC_DB_CES_KEY(nscdb)) nscdb->gethash = nsc_db_ces_key_gethash; else if (_NSC_DB_CIS_KEY(nscdb)) nscdb->gethash = nsc_db_cis_key_gethash; else if (_NSC_DB_INT_KEY(nscdb)) nscdb->gethash = nsc_db_int_key_gethash; else assert(0); } else { nscdb->gethash = gethash; } } (void) mutex_init(&nscdb->db_mutex, USYNC_THREAD, NULL); return (nscdb); out: if (nscdb->htable) free(nscdb->htable); if (nscdb) free(nscdb); return (NULL); } /* * verify */ /* ARGSUSED */ nscd_rc_t _nscd_cfg_cache_verify( void *data, struct nscd_cfg_param_desc *pdesc, nscd_cfg_id_t *nswdb, nscd_cfg_flag_t dflag, nscd_cfg_error_t **errorp, void **cookie) { return (NSCD_SUCCESS); } /* * notify */ /* ARGSUSED */ nscd_rc_t _nscd_cfg_cache_notify( void *data, struct nscd_cfg_param_desc *pdesc, nscd_cfg_id_t *nswdb, nscd_cfg_flag_t dflag, nscd_cfg_error_t **errorp, void **cookie) { nsc_ctx_t *ctx; void *dp; int i; /* group data */ if (_nscd_cfg_flag_is_set(dflag, NSCD_CFG_DFLAG_GROUP)) { if (_nscd_cfg_flag_is_set(pdesc->pflag, NSCD_CFG_PFLAG_GLOBAL)) { /* global config */ global_cfg = *(nscd_cfg_global_cache_t *)data; } else if (_nscd_cfg_flag_is_set(dflag, NSCD_CFG_DFLAG_SET_ALL_DB)) { /* non-global config for all dbs */ for (i = 0; i < CACHE_CTX_COUNT; i++) { ctx = cache_ctx_p[i]; if (ctx == NULL) return (NSCD_CTX_NOT_FOUND); (void) rw_wrlock(&ctx->cfg_rwlp); ctx->cfg = *(nscd_cfg_cache_t *)data; ctx->cfg_mtime = time(NULL); (void) rw_unlock(&ctx->cfg_rwlp); } } else { /* non-global config for a specific db */ /* ignore non-caching databases */ if (get_cache_ctx(nswdb->name, &ctx) != NSCD_SUCCESS) return (NSCD_SUCCESS); (void) rw_wrlock(&ctx->cfg_rwlp); ctx->cfg = *(nscd_cfg_cache_t *)data; ctx->cfg_mtime = time(NULL); (void) rw_unlock(&ctx->cfg_rwlp); } return (NSCD_SUCCESS); } /* individual data */ if (_nscd_cfg_flag_is_set(pdesc->pflag, NSCD_CFG_PFLAG_GLOBAL)) { /* global config */ dp = (char *)&global_cfg + pdesc->p_offset; (void) memcpy(dp, data, pdesc->p_size); } else if (_nscd_cfg_flag_is_set(dflag, NSCD_CFG_DFLAG_SET_ALL_DB)) { /* non-global config for all dbs */ for (i = 0; i < CACHE_CTX_COUNT; i++) { ctx = cache_ctx_p[i]; if (ctx == NULL) return (NSCD_CTX_NOT_FOUND); dp = (char *)&ctx->cfg + pdesc->p_offset; (void) rw_wrlock(&ctx->cfg_rwlp); (void) memcpy(dp, data, pdesc->p_size); ctx->cfg_mtime = time(NULL); (void) rw_unlock(&ctx->cfg_rwlp); } } else { /* non-global config for a specific db */ /* ignore non-caching databases */ if (get_cache_ctx(nswdb->name, &ctx) != NSCD_SUCCESS) return (NSCD_SUCCESS); dp = (char *)&ctx->cfg + pdesc->p_offset; (void) rw_wrlock(&ctx->cfg_rwlp); (void) memcpy(dp, data, pdesc->p_size); ctx->cfg_mtime = time(NULL); (void) rw_unlock(&ctx->cfg_rwlp); } return (NSCD_SUCCESS); } /* * get stat */ /* ARGSUSED */ nscd_rc_t _nscd_cfg_cache_get_stat( void **stat, struct nscd_cfg_stat_desc *sdesc, nscd_cfg_id_t *nswdb, nscd_cfg_flag_t *dflag, void (**free_stat)(void *stat), nscd_cfg_error_t **errorp) { nscd_cfg_stat_cache_t *statsp, stats; nsc_ctx_t *ctx; int i; nscd_rc_t rc; statsp = calloc(1, sizeof (*statsp)); if (statsp == NULL) return (NSCD_NO_MEMORY); if (_nscd_cfg_flag_is_set(sdesc->sflag, NSCD_CFG_SFLAG_GLOBAL)) { for (i = 0; i < CACHE_CTX_COUNT; i++) { if (cache_ctx_p[i] == NULL) stats = null_stats; else { (void) mutex_lock(&cache_ctx_p[i]->stats_mutex); stats = cache_ctx_p[i]->stats; (void) mutex_unlock( &cache_ctx_p[i]->stats_mutex); } statsp->pos_hits += stats.pos_hits; statsp->neg_hits += stats.neg_hits; statsp->pos_misses += stats.pos_misses; statsp->neg_misses += stats.neg_misses; statsp->entries += stats.entries; statsp->drop_count += stats.drop_count; statsp->wait_count += stats.wait_count; statsp->invalidate_count += stats.invalidate_count; } } else { if ((rc = get_cache_ctx(nswdb->name, &ctx)) != NSCD_SUCCESS) { free(statsp); return (rc); } (void) mutex_lock(&ctx->stats_mutex); *statsp = ctx->stats; (void) mutex_unlock(&ctx->stats_mutex); } _NSC_GET_HITRATE(statsp); *stat = statsp; return (NSCD_SUCCESS); } /* * This function should only be called when nscd is * not a daemon. */ void nsc_info(nsc_ctx_t *ctx, char *dbname, nscd_cfg_cache_t cfg[], nscd_cfg_stat_cache_t stats[]) { int i; char *me = "nsc_info"; nsc_ctx_t *ctx1; nsc_ctx_t ctx2; nscd_rc_t rc; if (ctx) { ctx_info(ctx); return; } if (dbname) { rc = get_cache_ctx(dbname, &ctx1); if (rc == NSCD_INVALID_ARGUMENT) { _NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_WARNING) (me, "%s: no cache context found\n", dbname); return; } else if (rc == NSCD_NO_MEMORY) { _NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_WARNING) (me, "%s: unable to create cache context - no memory\n", dbname); return; } ctx_info(ctx1); return; } if (cfg == NULL || stats == NULL) return; for (i = 0; i < CACHE_CTX_COUNT; i++) { ctx2.dbname = cache_name[i]; ctx2.cfg = cfg[i]; ctx2.stats = stats[i]; ctx_info_nolock(&ctx2); } } static void ctx_info_nolock(nsc_ctx_t *ctx) { nscd_cfg_cache_t cfg; nscd_cfg_stat_cache_t stats; cfg = ctx->cfg; (void) fprintf(stdout, gettext("\n\nCACHE: %s\n"), ctx->dbname); (void) print_cfg(&cfg); if (cfg.enable == nscd_false) return; stats = ctx->stats; (void) print_stats(&stats); } static void ctx_info(nsc_ctx_t *ctx) { nscd_cfg_cache_t cfg; nscd_cfg_stat_cache_t stats; (void) rw_rdlock(&ctx->cfg_rwlp); cfg = ctx->cfg; (void) rw_unlock(&ctx->cfg_rwlp); (void) fprintf(stdout, gettext("\n\nCACHE: %s\n"), ctx->dbname); (void) print_cfg(&cfg); if (cfg.enable == nscd_false) return; (void) mutex_lock(&ctx->stats_mutex); stats = ctx->stats; (void) mutex_unlock(&ctx->stats_mutex); (void) print_stats(&stats); } #ifdef NSCD_DEBUG /* * This function should only be called when nscd is * not a daemon. */ int nsc_dump(char *dbname, int dbop) { nsc_ctx_t *ctx; nsc_db_t *nscdb; nscd_bool_t enabled; time_t now; char *me = "nsc_dump"; int i; if ((i = get_cache_idx(dbname)) == -1) { (void) fprintf(stdout, gettext("invalid cache name\n")); _NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_WARNING) (me, "%s: invalid cache name\n", dbname); return (NSCD_CACHE_INVALID_CACHE_NAME); } if ((ctx = cache_ctx_p[i]) == NULL) { (void) fprintf(stdout, gettext("no cache context\n")); _NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_WARNING) (me, "%s: no cache context\n", dbname); return (NSCD_CACHE_NO_CACHE_CTX); } now = time(NULL); (void) rw_rdlock(&ctx->cfg_rwlp); enabled = ctx->cfg.enable; (void) rw_unlock(&ctx->cfg_rwlp); if (enabled == nscd_false) return (NSCD_CACHE_DISABLED); nscdb = nsc_get_db(ctx, dbop); if (nscdb == NULL) { _NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_WARNING) (me, "%s:%d: no cache found\n", dbname, dbop); return (NSCD_CACHE_NO_CACHE_FOUND); } (void) mutex_lock(&nscdb->db_mutex); (void) queue_dump(nscdb, now); (void) hash_dump(nscdb, now); (void) avl_dump(nscdb, now); (void) mutex_unlock(&nscdb->db_mutex); return (NSCD_SUCCESS); } #endif /* NSCD_DEBUG */ /* * These macros are for exclusive use of nsc_lookup */ #define NSC_LOOKUP_LOG(loglevel, fmt) \ _NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_##loglevel) \ (me, fmt, whoami); static int nsc_lookup_no_cache(nsc_lookup_args_t *largs, const char *str) { char *me = "nsc_lookup_no_cache"; nss_status_t status; _NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_DEBUG) (me, "%s: name service lookup (bypassing cache)\n", str); nss_psearch(largs->buffer, largs->bufsize); status = NSCD_GET_STATUS(largs->buffer); _NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_DEBUG) (me, "%s: name service lookup status = %d\n", str, status); if (status == NSS_SUCCESS) { return (SUCCESS); } else if (status == NSS_NOTFOUND) { return (NOTFOUND); } else { return (SERVERERROR); } } /* * This function starts the revalidation and reaper threads * for a cache */ static void start_threads(nsc_ctx_t *ctx) { int errnum; char *me = "start_threads"; /* * kick off the revalidate thread (if necessary) */ if (ctx->revalidate_on != nscd_true) { if (thr_create(NULL, NULL, (void *(*)(void *))revalidate, ctx, 0, NULL) != 0) { errnum = errno; _NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_ERROR) (me, "thr_create (revalidate thread for %s): %s\n", ctx->dbname, strerror(errnum)); exit(1); } ctx->revalidate_on = nscd_true; } /* * kick off the reaper thread (if necessary) */ if (ctx->reaper_on != nscd_true) { if (thr_create(NULL, NULL, (void *(*)(void *))reaper, ctx, 0, NULL) != 0) { errnum = errno; _NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_ERROR) (me, "thr_create (reaper thread for %s): %s\n", ctx->dbname, strerror(errnum)); exit(1); } ctx->reaper_on = nscd_true; } } /* * Examine the packed buffer, see if the front-end parameters * indicate that the caller specified nsswitch config should be * used for the lookup. Return 1 if yes, otherwise 0. */ static int nsw_config_in_phdr(void *buf) { nss_pheader_t *pbuf = (nss_pheader_t *)buf; nssuint_t off; nss_dbd_t *pdbd; char *me = "nsw_config_in_phdr"; off = pbuf->dbd_off; if (off == 0) return (0); pdbd = (nss_dbd_t *)((void *)((char *)pbuf + off)); if (pdbd->o_default_config == 0) return (0); if ((enum nss_dbp_flags)pdbd->flags & NSS_USE_DEFAULT_CONFIG) { _NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_DEBUG) (me, "use caller specified nsswitch config\n"); return (1); } else return (0); } static nss_status_t copy_result(void *rbuf, void *cbuf) { nss_pheader_t *rphdr = (nss_pheader_t *)rbuf; nss_pheader_t *cphdr = (nss_pheader_t *)cbuf; char *me = "copy_result"; /* return NSS_ERROR if not enough room to copy result */ if (cphdr->data_len + 1 > rphdr->data_len) { NSCD_SET_STATUS(rphdr, NSS_ERROR, ERANGE); return (NSS_ERROR); } else { char *dst; if (cphdr->data_len == 0) return (NSS_SUCCESS); dst = (char *)rphdr + rphdr->data_off; (void) memcpy(dst, (char *)cphdr + cphdr->data_off, cphdr->data_len); rphdr->data_len = cphdr->data_len; /* some frontend code expects a terminating NULL char */ *(dst + rphdr->data_len) = '\0'; _NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_DEBUG) (me, "cache data (len = %lld): >>%s<<\n", cphdr->data_len, (char *)cphdr + cphdr->data_off); return (NSS_SUCCESS); } } static int get_dns_ttl(void *pbuf, char *dbname) { nss_pheader_t *phdr = (nss_pheader_t *)pbuf; int ttl; char *me = "get_dns_ttl"; /* if returned, dns ttl is stored in the extended data area */ if (phdr->ext_off == 0) return (-1); if (strcmp(dbname, NSS_DBNAM_HOSTS) != 0 && strcmp(dbname, NSS_DBNAM_IPNODES) != 0) return (-1); ttl = *(nssuint_t *)((void *)((char *)pbuf + phdr->ext_off)); _NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_DEBUG) (me, "dns ttl is %d seconds\n", ttl); return (ttl); } static int check_config(nsc_lookup_args_t *largs, nscd_cfg_cache_t *cfgp, char *whoami, int flag) { nsc_db_t *nscdb; nsc_ctx_t *ctx; char *me = "check_config"; ctx = largs->ctx; nscdb = largs->nscdb; /* see if the cached config needs update */ if (nscdb->cfg_mtime != ctx->cfg_mtime) { (void) rw_rdlock(&ctx->cfg_rwlp); nscdb->cfg = ctx->cfg; nscdb->cfg_mtime = ctx->cfg_mtime; (void) rw_unlock(&ctx->cfg_rwlp); _NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_DEBUG) (me, "config for context %s, database %s updated\n", ctx->dbname, nscdb->name); } *cfgp = nscdb->cfg; if (cfgp->enable == nscd_false) { _NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_DEBUG) (me, "%s: cache disabled\n", ctx->dbname); if (UPDATEBIT & flag) return (NOTFOUND); else return (nsc_lookup_no_cache(largs, whoami)); } /* * if caller requests lookup using his * own nsswitch config, bypass cache */ if (nsw_config_in_phdr(largs->buffer)) return (nsc_lookup_no_cache(largs, whoami)); /* no need of cache if we are dealing with 0 ttls */ if (cfgp->pos_ttl <= 0 && cfgp->neg_ttl <= 0) { if (flag & UPDATEBIT) return (NOTFOUND); else if (cfgp->avoid_ns == nscd_true) return (SERVERERROR); return (nsc_lookup_no_cache(largs, whoami)); } return (CONTINUE); } /* * Invalidate cache if database file has been modified. * See check_files config param for details. */ static void check_db_file(nsc_ctx_t *ctx, nscd_cfg_cache_t cfg, char *whoami, time_t now) { struct stat buf; nscd_bool_t file_modified = nscd_false; char *me = "check_db_file"; if (cfg.check_interval != 0 && (now - ctx->file_chktime) < cfg.check_interval) return; ctx->file_chktime = now; if (stat(ctx->file_name, &buf) == 0) { if (ctx->file_mtime == 0) { (void) mutex_lock(&ctx->file_mutex); if (ctx->file_mtime == 0) { ctx->file_mtime = buf.st_mtime; ctx->file_size = buf.st_size; ctx->file_ino = buf.st_ino; } (void) mutex_unlock(&ctx->file_mutex); } else if (ctx->file_mtime < buf.st_mtime || ctx->file_size != buf.st_size || ctx->file_ino != buf.st_ino) { (void) mutex_lock(&ctx->file_mutex); if (ctx->file_mtime < buf.st_mtime || ctx->file_size != buf.st_size || ctx->file_ino != buf.st_ino) { file_modified = nscd_true; ctx->file_mtime = buf.st_mtime; ctx->file_size = buf.st_size; ctx->file_ino = buf.st_ino; } (void) mutex_unlock(&ctx->file_mutex); } } if (file_modified == nscd_true) { _NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_DEBUG) (me, "%s: file %s has been modified - invalidating cache\n", whoami, ctx->file_name); ctx_invalidate(ctx); } } static int lookup_int(nsc_lookup_args_t *largs, int flag) { nsc_ctx_t *ctx; nsc_db_t *nscdb; nscd_cfg_cache_t cfg; nsc_entry_t *this_entry; nsc_entry_stat_t *this_stats; nsc_action_t next_action; nss_status_t status; nscd_bool_t delete; nscd_rc_t rc; char *dbname; int dbop, errnum; int cfg_rc; nss_XbyY_args_t args; char whoami[128]; time_t now = time(NULL); /* current time */ char *me = "lookup_int"; /* extract dbop, dbname, key and cred */ status = nss_packed_getkey(largs->buffer, largs->bufsize, &dbname, &dbop, &args); if (status != NSS_SUCCESS) { _NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_ERROR) (me, "nss_packed_getkey failure (%d)\n", status); return (SERVERERROR); } /* get the cache context */ if (largs->ctx == NULL) { if (get_cache_ctx(dbname, &largs->ctx) != NSCD_SUCCESS) { _NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_WARNING) (me, "%s: no cache context found\n", dbname); if (UPDATEBIT & flag) return (NOTFOUND); else return (nsc_lookup_no_cache(largs, dbname)); } } ctx = largs->ctx; if (largs->nscdb == NULL) { if ((largs->nscdb = nsc_get_db(ctx, dbop)) == NULL) { _NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_WARNING) (me, "%s:%d: no cache found\n", dbname, dbop); if (UPDATEBIT & flag) return (NOTFOUND); else return (nsc_lookup_no_cache(largs, dbname)); } } nscdb = largs->nscdb; _NSCD_LOG_IF(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_ALL) { (void) nscdb->getlogstr(nscdb->name, whoami, sizeof (whoami), &args); } if (UPDATEBIT & flag) { _NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_DEBUG) (me, "%s: refresh start\n", whoami); } else { _NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_DEBUG) (me, "%s: lookup start\n", whoami); } cfg_rc = check_config(largs, &cfg, whoami, flag); if (cfg_rc != CONTINUE) return (cfg_rc); /* * Invalidate cache if file has been modified. */ if (cfg.check_files == nscd_true) check_db_file(ctx, cfg, whoami, now); (void) mutex_lock(&nscdb->db_mutex); /* Lookup the cache table */ for (;;) { delete = nscd_false; rc = lookup_cache(largs, &cfg, &args, whoami, &this_entry); if (rc != NSCD_SUCCESS) { (void) mutex_unlock(&nscdb->db_mutex); /* Either no entry and avoid name service */ if (rc == NSCD_DB_ENTRY_NOT_FOUND || rc == NSCD_INVALID_ARGUMENT) return (NOTFOUND); /* OR memory error */ return (SERVERERROR); } /* get the stats from the entry */ this_stats = &this_entry->stats; /* * What should we do next ? */ switch (this_stats->status) { case ST_NEW_ENTRY: delete = nscd_true; next_action = _NSC_NSLOOKUP; break; case ST_UPDATE_PENDING: if (flag & UPDATEBIT) { (void) mutex_unlock(&nscdb->db_mutex); return (NOTFOUND); } else if (this_stats->timestamp < now) next_action = _NSC_WAIT; else next_action = _NSC_USECACHED; break; case ST_LOOKUP_PENDING: if (flag & UPDATEBIT) { (void) mutex_unlock(&nscdb->db_mutex); return (NOTFOUND); } next_action = _NSC_WAIT; break; case ST_DISCARD: if (cfg.avoid_ns == nscd_true) { (void) mutex_unlock(&nscdb->db_mutex); return (NOTFOUND); } /* otherwise reuse the entry */ (void) memset(this_stats, 0, sizeof (*this_stats)); next_action = _NSC_NSLOOKUP; break; default: if (cfg.avoid_ns == nscd_true) next_action = _NSC_USECACHED; else if ((flag & UPDATEBIT) || (this_stats->timestamp < now)) { _NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_DEBUG) (me, "%s: cached entry needs to be updated\n", whoami); next_action = _NSC_NSLOOKUP; } else next_action = _NSC_USECACHED; break; } if (next_action == _NSC_WAIT) { _NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_DEBUG) (me, "%s: need to wait\n", whoami); /* do we have clearance ? */ if (_nscd_get_clearance(&ctx->throttle_sema) != 0) { /* nope. quit */ (void) mutex_lock(&ctx->stats_mutex); ctx->stats.drop_count++; (void) mutex_unlock(&ctx->stats_mutex); _NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_DEBUG_6) (me, "%s: throttling load\n", whoami); (void) mutex_unlock(&nscdb->db_mutex); NSC_LOOKUP_LOG(WARNING, "%s: no clearance to wait\n"); return (NOSERVER); } /* yes can wait */ (void) nscd_wait(ctx, nscdb, this_entry); (void) _nscd_release_clearance(&ctx->throttle_sema); continue; } break; } if (!(UPDATEBIT & flag)) this_stats->hits++; /* update hit count */ if (next_action == _NSC_NSLOOKUP) { _NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_DEBUG) (me, "%s: name service lookup required\n", whoami); if (_nscd_get_clearance(&ctx->throttle_sema) != 0) { if (delete == nscd_true) delete_entry(nscdb, ctx, this_entry); else this_stats->status = ST_DISCARD; (void) mutex_lock(&ctx->stats_mutex); ctx->stats.drop_count++; (void) mutex_unlock(&ctx->stats_mutex); (void) mutex_unlock(&nscdb->db_mutex); NSC_LOOKUP_LOG(WARNING, "%s: no clearance for lookup\n"); return (NOSERVER); } /* block any threads accessing this entry */ this_stats->status = (flag & UPDATEBIT) ? ST_UPDATE_PENDING : ST_LOOKUP_PENDING; /* release lock and do name service lookup */ (void) mutex_unlock(&nscdb->db_mutex); nss_psearch(largs->buffer, largs->bufsize); status = NSCD_GET_STATUS(largs->buffer); (void) mutex_lock(&nscdb->db_mutex); this_stats->status = 0; (void) _nscd_release_clearance(&ctx->throttle_sema); /* signal waiting threads */ (void) nscd_signal(ctx, nscdb, this_entry); _NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_DEBUG) (me, "%s: name service lookup status = %d\n", whoami, status); if (status == NSS_SUCCESS) { int ttl; /* * data found in name service * update cache */ status = dup_packed_buffer(largs, this_entry); if (status != NSS_SUCCESS) { delete_entry(nscdb, ctx, this_entry); (void) mutex_unlock(&nscdb->db_mutex); NSC_LOOKUP_LOG(ERROR, "%s: failed to update cache\n"); return (SERVERERROR); } /* * store unpacked key in cache */ status = nss_packed_getkey(this_entry->buffer, this_entry->bufsize, &dbname, &dbop, &args); if (status != NSS_SUCCESS) { delete_entry(nscdb, ctx, this_entry); (void) mutex_unlock(&nscdb->db_mutex); NSC_LOOKUP_LOG(ERROR, "%s: failed to extract key\n"); return (SERVERERROR); } this_entry->key = args.key; /* struct copy */ /* update +ve miss count */ if (!(UPDATEBIT & flag)) { (void) mutex_lock(&ctx->stats_mutex); ctx->stats.pos_misses++; (void) mutex_unlock(&ctx->stats_mutex); } /* update +ve ttl */ ttl = get_dns_ttl(largs->buffer, dbname); /* honor the dns ttl less than postive ttl */ if (ttl < 0 || ttl > cfg.pos_ttl) ttl = cfg.pos_ttl; this_stats->timestamp = time(NULL) + ttl; /* * start the revalidation and reaper threads * if not already started */ start_threads(ctx); (void) mutex_unlock(&nscdb->db_mutex); NSC_LOOKUP_LOG(DEBUG, "%s: cache updated with positive entry\n"); return (SUCCESS); } else if (status == NSS_NOTFOUND) { /* * data not found in name service * update cache */ _NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_DEBUG_6) (me, "%s: name service lookup failed\n", whoami); if (NSCD_GET_ERRNO(largs->buffer) == ERANGE) { delete_entry(nscdb, ctx, this_entry); (void) mutex_unlock(&nscdb->db_mutex); NSC_LOOKUP_LOG(DEBUG, "%s: ERANGE, cache not updated " "with negative entry\n"); return (NOTFOUND); } status = dup_packed_buffer(largs, this_entry); if (status != NSS_SUCCESS) { delete_entry(nscdb, ctx, this_entry); (void) mutex_unlock(&nscdb->db_mutex); NSC_LOOKUP_LOG(ERROR, "%s: failed to update cache\n"); return (SERVERERROR); } /* store unpacked key in cache */ status = nss_packed_getkey(this_entry->buffer, this_entry->bufsize, &dbname, &dbop, &args); if (status != NSS_SUCCESS) { delete_entry(nscdb, ctx, this_entry); (void) mutex_unlock(&nscdb->db_mutex); NSC_LOOKUP_LOG(ERROR, "%s: failed to extract key\n"); return (SERVERERROR); } this_entry->key = args.key; /* struct copy */ /* update -ve ttl */ this_stats->timestamp = time(NULL) + cfg.neg_ttl; /* update -ve miss count */ if (!(UPDATEBIT & flag)) { (void) mutex_lock(&ctx->stats_mutex); ctx->stats.neg_misses++; (void) mutex_unlock(&ctx->stats_mutex); } /* * start the revalidation and reaper threads * if not already started */ start_threads(ctx); (void) mutex_unlock(&nscdb->db_mutex); NSC_LOOKUP_LOG(DEBUG, "%s: cache updated with negative entry\n"); return (NOTFOUND); } else { /* * name service lookup failed */ _NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_DEBUG_6) (me, "%s: name service lookup failed\n", whoami); errnum = NSCD_GET_ERRNO(largs->buffer); if (delete == nscd_true) delete_entry(nscdb, ctx, this_entry); else this_stats->status = ST_DISCARD; (void) mutex_unlock(&nscdb->db_mutex); _NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_WARNING) (me, "%s: name service lookup failed " "(status=%d, errno=%d)\n", whoami, status, errnum); return (SERVERERROR); } } else if (next_action == _NSC_USECACHED) { /* * found entry in cache */ if (UPDATEBIT & flag) { (void) mutex_unlock(&nscdb->db_mutex); NSC_LOOKUP_LOG(DEBUG, "%s: no need to update\n"); return (SUCCESS); } if (NSCD_GET_STATUS((nss_pheader_t *)this_entry->buffer) == NSS_SUCCESS) { /* positive hit */ (void) mutex_lock(&ctx->stats_mutex); ctx->stats.pos_hits++; (void) mutex_unlock(&ctx->stats_mutex); /* update response buffer */ if (copy_result(largs->buffer, this_entry->buffer) != NSS_SUCCESS) { (void) mutex_unlock(&nscdb->db_mutex); NSC_LOOKUP_LOG(ERROR, "%s: response buffer insufficient\n"); return (SERVERERROR); } (void) mutex_unlock(&nscdb->db_mutex); NSC_LOOKUP_LOG(DEBUG, "%s: positive entry in cache\n"); return (SUCCESS); } else { /* negative hit */ (void) mutex_lock(&ctx->stats_mutex); ctx->stats.neg_hits++; (void) mutex_unlock(&ctx->stats_mutex); NSCD_SET_STATUS((nss_pheader_t *)largs->buffer, NSCD_GET_STATUS(this_entry->buffer), NSCD_GET_ERRNO(this_entry->buffer)); NSCD_SET_HERRNO((nss_pheader_t *)largs->buffer, NSCD_GET_HERRNO(this_entry->buffer)); (void) mutex_unlock(&nscdb->db_mutex); NSC_LOOKUP_LOG(DEBUG, "%s: negative entry in cache\n"); return (NOTFOUND); } } (void) mutex_unlock(&nscdb->db_mutex); NSC_LOOKUP_LOG(ERROR, "%s: cache backend failure\n"); return (SERVERERROR); } /* * NSCD cache backend lookup function */ /*ARGSUSED*/ void nsc_lookup(nsc_lookup_args_t *largs, int flag) { nss_pheader_t *phdr = (nss_pheader_t *)largs->buffer; int rc; rc = lookup_int(largs, 0); if (NSCD_GET_STATUS(phdr) == NSS_TRYLOCAL) return; switch (rc) { case SUCCESS: NSCD_SET_STATUS(phdr, NSS_SUCCESS, 0); break; case NOTFOUND: NSCD_SET_STATUS(phdr, NSS_NOTFOUND, -1); break; case SERVERERROR: /* * status and errno should have been set in the phdr, * if not, set status to NSS_ERROR */ if (NSCD_STATUS_IS_OK(phdr)) { NSCD_SET_STATUS(phdr, NSS_ERROR, 0); } break; case NOSERVER: NSCD_SET_STATUS(phdr, NSS_TRYLOCAL, -1); break; } } static nsc_ctx_t * init_cache_ctx(int i) { nsc_ctx_t *ctx; ctx = calloc(1, sizeof (nsc_ctx_t)); if (ctx == NULL) return (NULL); /* init locks and semaphores */ (void) mutex_init(&ctx->file_mutex, USYNC_THREAD, NULL); (void) rwlock_init(&ctx->cfg_rwlp, USYNC_THREAD, NULL); (void) mutex_init(&ctx->stats_mutex, USYNC_THREAD, NULL); (void) _nscd_init_cache_sema(&ctx->throttle_sema, cache_name[i]); cache_init_ctx[i](ctx); cache_ctx_p[i] = ctx; return (ctx); } static void revalidate(nsc_ctx_t *ctx) { for (;;) { int i, slp, interval, count; (void) rw_rdlock(&ctx->cfg_rwlp); slp = ctx->cfg.pos_ttl; count = ctx->cfg.keephot; (void) rw_unlock(&ctx->cfg_rwlp); if (slp < 60) slp = 60; if (count != 0) { interval = (slp/2)/count; if (interval == 0) interval = 1; (void) sleep(slp*2/3); for (i = 0; i < ctx->db_count; i++) { getxy_keepalive(ctx, ctx->nsc_db[i], count, interval); } } else { (void) sleep(slp); } } } static void getxy_keepalive(nsc_ctx_t *ctx, nsc_db_t *nscdb, int keep, int interval) { nsc_keephot_t *table; nsc_entry_t *entry, *ptr; int i; nsc_lookup_args_t *largs; nss_pheader_t *phdr; int bufsiz; char *me = "getxy_keepalive"; /* we won't be here if keep == 0 so need to check that */ _NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_DEBUG) (me, "%s: keep alive\n", nscdb->name); if ((table = maken(keep)) == NULL) { _NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_ERROR) (me, "memory allocation failure\n"); exit(1); } (void) mutex_lock(&nscdb->db_mutex); entry = nscdb->qtail; while (entry != NULL) { /* leave pending calls alone */ if (!(entry->stats.status & ST_PENDING)) { /* do_revalidate */ (void) insertn(table, entry->stats.hits, entry); } entry = entry->qnext; } for (i = 1; i <= keep; i++) { if (table[i].ptr == NULL) continue; ptr = (nsc_entry_t *)table[i].ptr; phdr = (nss_pheader_t *)ptr->buffer; if (NSCD_GET_STATUS(phdr) == NSS_SUCCESS) /* * for positive cache, in addition to the packed * header size, allocate twice the size of the * existing result (in case the result grows * larger) plus 2K (for the file/compat backend to * process a possible large entry in the /etc files) */ bufsiz = phdr->data_off + 2 * phdr->data_len + 2048; else /* * for negative cache, allocate 8K buffer to * hold result in case the next lookup may * return something (in addition to the * packed header size) */ bufsiz = phdr->data_off + 8096; table[i].ptr = malloc(bufsiz); if (table[i].ptr == NULL) { (void) mutex_unlock(&nscdb->db_mutex); _NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_ERROR) (me, "memory allocation failure\n"); exit(1); } (void) memcpy(table[i].ptr, ptr->buffer, ptr->bufsize); ((nss_pheader_t *)table[i].ptr)->pbufsiz = bufsiz; table[i].num = bufsiz; } (void) mutex_unlock(&nscdb->db_mutex); /* launch update thread for each keep hot entry */ for (i = keep; i > 0; i--) { if (table[i].ptr == NULL) continue; /* unused slot in table */ _NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_DEBUG) (me, "%s: launching update\n", nscdb->name); largs = (nsc_lookup_args_t *)malloc(sizeof (*largs)); if (largs == NULL) { _NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_ERROR) (me, "memory allocation failure\n"); exit(1); } largs->buffer = table[i].ptr; largs->bufsize = table[i].num; largs->ctx = ctx; largs->nscdb = nscdb; if (launch_update(largs) < 0) exit(1); (void) sleep(interval); } /* * The update thread will handle freeing of buffer and largs. * Free the table here. */ free(table); } static int launch_update(nsc_lookup_args_t *in) { char *me = "launch_update"; int errnum; errnum = thr_create(NULL, NULL, (void *(*)(void*))do_update, in, 0|THR_DETACHED, NULL); if (errnum != 0) { _NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_ERROR) (me, "%s: thread creation failure (%d)\n", in->nscdb->name, errnum); return (-1); } return (0); } static void do_update(nsc_lookup_args_t *in) { nss_pheader_t *phdr = (nss_pheader_t *)in->buffer; /* update the length of the data buffer */ phdr->data_len = phdr->pbufsiz - phdr->data_off; (void) lookup_int(in, UPDATEBIT); if (in->buffer) free(in->buffer); free(in); } /* * Invalidate cache */ void nsc_invalidate(nsc_ctx_t *ctx, char *dbname, nsc_ctx_t **ctxs) { int i; char *me = "nsc_invalidate"; if (ctx) { ctx_invalidate(ctx); return; } if (dbname) { if ((i = get_cache_idx(dbname)) == -1) { _NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_WARNING) (me, "%s: invalid cache name\n", dbname); return; } if ((ctx = cache_ctx_p[i]) == NULL) { _NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_WARNING) (me, "%s: no cache context found\n", dbname); return; } ctx_invalidate(ctx); return; } if (ctxs == NULL) ctxs = cache_ctx_p; for (i = 0; i < CACHE_CTX_COUNT; i++) { if (ctxs[i] != NULL) ctx_invalidate(ctxs[i]); } } /* * Invalidate cache by context */ static void ctx_invalidate(nsc_ctx_t *ctx) { int i; nsc_entry_t *entry; char *me = "ctx_invalidate"; _NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_DEBUG) (me, "%s: invalidate cache\n", ctx->dbname); for (i = 0; i < ctx->db_count; i++) { if (ctx->nsc_db[i] == NULL) continue; (void) mutex_lock(&ctx->nsc_db[i]->db_mutex); entry = ctx->nsc_db[i]->qtail; while (entry != NULL) { /* leave pending calls alone */ if (!(entry->stats.status & ST_PENDING)) entry->stats.status = ST_DISCARD; entry = entry->qnext; } (void) mutex_unlock(&ctx->nsc_db[i]->db_mutex); } (void) mutex_lock(&ctx->stats_mutex); ctx->stats.invalidate_count++; (void) mutex_unlock(&ctx->stats_mutex); } /* * Free nsc_entry_t * * Pre-reqs: * nscdb->db_mutex lock must be held before calling this function */ static void delete_entry(nsc_db_t *nscdb, nsc_ctx_t *ctx, nsc_entry_t *entry) { uint_t hash; avl_remove(&nscdb->tree, entry); HASH_REMOVE(nscdb, entry, hash, nscd_false); queue_remove(nscdb, entry); if (entry->buffer != NULL) { free(entry->buffer); entry->buffer = NULL; } umem_cache_free(nsc_entry_cache, entry); (void) mutex_lock(&ctx->stats_mutex); ctx->stats.entries--; (void) mutex_unlock(&ctx->stats_mutex); } static nscd_rc_t lookup_cache(nsc_lookup_args_t *largs, nscd_cfg_cache_t *cfgp, nss_XbyY_args_t *argp, char *whoami, nsc_entry_t **entry) { nsc_db_t *nscdb; nsc_ctx_t *ctx; uint_t hash; avl_index_t pos; ulong_t nentries; nsc_entry_t find_entry, *node; char *me = "lookup_cache"; ctx = largs->ctx; nscdb = largs->nscdb; /* set the search key */ find_entry.key = argp->key; /* struct copy (not deep) */ /* lookup the hash table ==> O(1) */ if (nscdb->htable) { *entry = hash_find(nscdb, &find_entry, &hash, nscd_true); if (*entry != NULL) { (void) queue_adjust(nscdb, *entry); return (NSCD_SUCCESS); } } /* if not found, lookup the AVL tree ==> O(log n) */ *entry = (nsc_entry_t *)avl_find(&nscdb->tree, &find_entry, &pos); if (*entry != NULL) { (void) queue_adjust(nscdb, *entry); /* move it to the hash table */ if (nscdb->htable) { if (nscdb->htable[hash] == NULL || (*entry)->stats.hits >= nscdb->htable[hash]->stats.hits) { nscdb->htable[hash] = *entry; } } return (NSCD_SUCCESS); } /* entry not found in the cache */ _NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_DEBUG) (me, "%s: cache miss\n", whoami); if (cfgp->avoid_ns == nscd_true) { _NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_DEBUG) (me, "%s: avoid name service\n", whoami); return (NSCD_DB_ENTRY_NOT_FOUND); } /* allocate memory for new entry (stub) */ *entry = (nsc_entry_t *)umem_cache_alloc(nsc_entry_cache, UMEM_DEFAULT); if (*entry == NULL) { _NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_ERROR) (me, "%s: memory allocation failure\n", whoami); return (NSCD_NO_MEMORY); } (void) memset(*entry, 0, sizeof (**entry)); /* * Note that the actual data for the key is stored within * the largs->buffer (input buffer to nsc_lookup). * find_entry.key only contains pointers to this data. * * If largs->buffer will be re-allocated by nss_psearch * then (*entry)->key will have dangling pointers. * In such case, the following assignment needs to be * replaced by code that duplicates the key. */ (*entry)->key = find_entry.key; /* * Add the entry to the cache. */ avl_insert(&nscdb->tree, *entry, pos); /* O(log n) */ (void) queue_adjust(nscdb, *entry); /* constant */ if (nscdb->htable) /* constant */ nscdb->htable[hash] = *entry; (*entry)->stats.status = ST_NEW_ENTRY; (void) mutex_lock(&ctx->stats_mutex); nentries = ++(ctx->stats.entries); (void) mutex_unlock(&ctx->stats_mutex); /* Have we exceeded max entries ? */ if (cfgp->maxentries > 0 && nentries > cfgp->maxentries) { _NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_DEBUG) (me, "%s: maximum entries exceeded -- " "deleting least recently used entry\n", whoami); node = nscdb->qhead; while (node != NULL && node != *entry) { if (node->stats.status == ST_DISCARD || !(node->stats.status & ST_PENDING)) { delete_entry(nscdb, ctx, node); break; } node = node->qprev; } /* * It's okay if we were not able to find one to delete. * The reaper (when invoked) will return the cache to a * safe level. */ } return (NSCD_SUCCESS); } static void reaper(nsc_ctx_t *ctx) { uint_t ttl, extra_sleep, total_sleep, intervals; uint_t nodes_per_interval, seconds_per_interval; ulong_t nsc_entries; char *me = "reaper"; for (;;) { (void) mutex_lock(&ctx->stats_mutex); nsc_entries = ctx->stats.entries; (void) mutex_unlock(&ctx->stats_mutex); (void) rw_rdlock(&ctx->cfg_rwlp); ttl = ctx->cfg.pos_ttl; (void) rw_unlock(&ctx->cfg_rwlp); if (nsc_entries == 0) { _NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_DEBUG) (me, "%s: nothing to reap\n", ctx->dbname); /* sleep for atleast 60 seconds */ if (ttl < 60) ttl = 60; _NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_DEBUG) (me, "%s: sleep %d\n", ctx->dbname, ttl); (void) sleep(ttl); continue; } if (ttl < 32) ttl = 32; if (ttl > (1<<28)) ttl = 1<<28; /* * minimum nodes_per_interval = 256 or 1<<8 * maximum nodes_per_interval = nsc_entries * minimum seconds_per_interval = 32 or 1<<5 * maximum_seconds_per_interval = ttl */ if (nsc_entries <= ttl) { intervals = (nsc_entries >> 8) + 1; seconds_per_interval = ttl / intervals; nodes_per_interval = 256; } else { intervals = (ttl >> 5) + 1; seconds_per_interval = 32; nodes_per_interval = nsc_entries / intervals; if (nodes_per_interval < 256) nodes_per_interval = 256; } _NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_DEBUG) (me, "%s: total entries = %d, " "seconds per interval = %d, " "nodes per interval = %d\n", ctx->dbname, nsc_entries, seconds_per_interval, nodes_per_interval); total_sleep = reap_cache(ctx, nodes_per_interval, seconds_per_interval); extra_sleep = 1 + ttl - total_sleep; if (extra_sleep > 0) (void) sleep(extra_sleep); } } static uint_t reap_cache(nsc_ctx_t *ctx, uint_t nodes_per_interval, uint_t seconds_per_interval) { uint_t nodes_togo, total_sleep; time_t now; nsc_entry_t *node, *next_node; nsc_db_t *nscdb; uint_t primes[] = {_NSC_HTSIZE_PRIMES}; ulong_t count, nentries, maxentries; int i, slot, value, newhtsize; char *me = "reap_cache"; count = 0; total_sleep = 0; nodes_togo = nodes_per_interval; now = time(NULL); for (i = 0; i < ctx->db_count; i++) { nscdb = ctx->nsc_db[i]; (void) mutex_lock(&nscdb->db_mutex); nscdb->reap_node = nscdb->qtail; while (nscdb->reap_node != NULL) { if (nodes_togo == 0) { (void) mutex_unlock(&nscdb->db_mutex); (void) sleep(seconds_per_interval); total_sleep += seconds_per_interval; nodes_togo = nodes_per_interval; now = time(NULL); (void) mutex_lock(&nscdb->db_mutex); } /* delete ST_DISCARD and expired nodes */ if ((node = nscdb->reap_node) == NULL) break; if (node->stats.status == ST_DISCARD || (!(node->stats.status & ST_PENDING) && node->stats.timestamp < now)) { /* * Delete entry if its discard flag is * set OR if it has expired. Entries * with pending updates are not * deleted. * nscdb->reap_node will be adjusted * by delete_entry() */ delete_entry(nscdb, ctx, node); count++; } else { nscdb->reap_node = node->qnext; } nodes_togo--; } if (nscdb->htsize == 0) { (void) mutex_unlock(&nscdb->db_mutex); continue; } /* * Dynamic adjustment of hash table size. * * Hash table size is roughly 1/8th of the * total entries. However the size is changed * only when the number of entries double or * reduced by half */ nentries = avl_numnodes(&nscdb->tree); for (slot = 0, value = _NSC_INIT_HTSIZE_SLOT_VALUE; slot < _NSC_HTSIZE_NUM_SLOTS && nentries > value; value = (value << 1) + 1, slot++) ; if (nscdb->hash_type == nsc_ht_power2) newhtsize = _NSC_INIT_HTSIZE_POWER2 << slot; else newhtsize = primes[slot]; /* Recommended size is same as the current size. Done */ if (nscdb->htsize == newhtsize) { (void) mutex_unlock(&nscdb->db_mutex); continue; } _NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_DEBUG) (me, "%s: resizing hash table from %d to %d\n", nscdb->name, nscdb->htsize, newhtsize); /* * Dump old hashes because it would be time * consuming to rehash them. */ (void) free(nscdb->htable); nscdb->htable = calloc(newhtsize, sizeof (*(nscdb->htable))); if (nscdb->htable == NULL) { _NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_ERROR) (me, "%s: memory allocation failure\n", nscdb->name); /* -1 to try later */ nscdb->htsize = -1; } else { nscdb->htsize = newhtsize; } (void) mutex_unlock(&nscdb->db_mutex); } _NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_DEBUG) (me, "%s: reaped %lu entries\n", ctx->dbname, count); /* * if cache is almost full then reduce it to a safe level by * evicting LRU entries */ (void) rw_rdlock(&ctx->cfg_rwlp); maxentries = ctx->cfg.maxentries; (void) rw_unlock(&ctx->cfg_rwlp); /* No limit on number of entries. Done */ if (maxentries == 0) goto out; (void) mutex_lock(&ctx->stats_mutex); nentries = ctx->stats.entries; (void) mutex_unlock(&ctx->stats_mutex); /* what is the percentage of cache used ? */ value = (nentries * 100) / maxentries; if (value < _NSC_EVICTION_START_LEVEL) goto out; /* * cache needs to be reduced to a safe level */ value -= _NSC_EVICTION_SAFE_LEVEL; for (i = 0, count = 0; i < ctx->db_count; i++) { /* * Reduce each subcache by 'value' percent */ nscdb = ctx->nsc_db[i]; (void) mutex_lock(&nscdb->db_mutex); nodes_togo = (value * avl_numnodes(&nscdb->tree)) / 100; /* Start from LRU entry i.e queue head */ next_node = nscdb->qhead; while (nodes_togo > 0 && next_node != NULL) { node = next_node; next_node = next_node->qprev; if (node->stats.status == ST_DISCARD || !(node->stats.status & ST_PENDING)) { /* Leave nodes with pending updates alone */ delete_entry(nscdb, ctx, node); count++; nodes_togo--; } } (void) mutex_unlock(&nscdb->db_mutex); } _NSCD_LOG(NSCD_LOG_CACHE, NSCD_LOG_LEVEL_DEBUG) (me, "%s: evicted %lu LRU entries\n", ctx->dbname, count); out: return (total_sleep); }