cache.c revision ad0e80f7538b612141768bfda60009eb76550ee7
/*
* 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
* 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 2006 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
#pragma ident "%Z%%M% %I% %E% SMI"
/*
* Cache routines for nscd
*/
#include <assert.h>
#include <errno.h>
#include <memory.h>
#include <signal.h>
#include <stdlib.h>
#include <stddef.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <ucred.h>
#include <nss_common.h>
#include <locale.h>
#include <ctype.h>
#include <strings.h>
#include <string.h>
#include <umem.h>
#include <fcntl.h>
#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
nss_XbyY_args_t *, char *, 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
#endif /* NSCD_DEBUG */
#ifdef NSCD_DEBUG
#endif /* NSCD_DEBUG */
static int launch_update(nsc_lookup_args_t *);
static void do_update(nsc_lookup_args_t *);
static void ctx_info_nolock(nsc_ctx_t *);
static void ctx_invalidate(nsc_ctx_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 umem_cache_t *nsc_entry_cache;
static nsc_ctx_t *init_cache_ctx(int);
static void revalidate(nsc_ctx_t *);
static nss_status_t
int slen, new_pbufsiz = 0;
/* no result, copy header only (status, errno, etc) */
} else {
/*
* lookup result returned, data to copy is the packed
* header plus result (add 1 for the terminating NULL
* just in case)
*/
}
/* allocate cache packed buffer */
/* old buffer too small, free it */
d->bufsize = 0;
}
/* get new buffer */
return (NSS_ERROR);
}
if (new_pbufsiz != 0)
return (NSS_SUCCESS);
}
char *cache_name[CACHE_CTX_COUNT] = {
};
typedef void (*cache_init_ctx_t)(nsc_ctx_t *);
};
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];
return (i);
}
return (-1);
}
/*
* Given database name 'dbname' retrieve cache context,
* if not created yet, allocate and initialize it.
*/
static nscd_rc_t
int i;
i = get_cache_idx(dbname);
if (i == -1)
return (NSCD_INVALID_ARGUMENT);
*ctx = init_cache_ctx(i);
return (NSCD_NO_MEMORY);
}
return (NSCD_SUCCESS);
}
/*
* Generate a log string to identify backend operation in debug logs
*/
static void
nss_XbyY_args_t *argp) {
}
static void
nss_XbyY_args_t *argp) {
}
/*ARGSUSED*/
static void
nss_XbyY_args_t *argp) {
}
/*
* Returns cache based on dbop
*/
static nsc_db_t *
int i;
}
return (NULL);
}
/*
* integer compare routine for _NSC_DB_INT_KEY
*/
static int
}
/*
* case sensitive name compare routine for _NSC_DB_CES_KEY
*/
static int
return (_NSC_INT_KEY_CMP(res, 0));
}
/*
* case insensitive name compare routine _NSC_DB_CIS_KEY
*/
static int
return (_NSC_INT_KEY_CMP(res, 0));
}
/*
* macro used to generate elf hashes for strings
*/
hval = 0; \
while (*str) { \
uint_t g; \
if ((g = (hval & 0xf0000000)) != 0) \
hval ^= g >> 24; \
hval &= ~g; \
} \
/*
* cis hash function
*/
return (0);
return (hval);
}
/*
* ces hash function
*/
return (0);
return (hval);
}
/*
* one-at-a-time hash function
*/
return (0);
}
}
/*
* case insensitive name gethash routine _NSC_DB_CIS_KEY
*/
static uint_t
}
/*
* case sensitive name gethash routine _NSC_DB_CES_KEY
*/
static uint_t
}
/*
* integer gethash routine _NSC_DB_INT_KEY
*/
static uint_t
}
/*
* 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 *
nscd_bool_t cmp) {
else
return (NULL);
return (hashentry);
return (hashentry);
}
return (NULL);
}
}
}
#ifdef NSCD_DEBUG
static void
char whoami[512];
case ST_NEW_ENTRY:
return;
case ST_UPDATE_PENDING:
return;
case ST_LOOKUP_PENDING:
return;
case ST_DISCARD:
return;
default:
gettext("\t status: expired (%d seconds ago)\n"),
else
gettext("\t status: valid (expiry in %d seconds)\n"),
break;
}
}
#endif /* NSCD_DEBUG */
static void
statsp->pos_misses);
statsp->neg_misses);
statsp->wait_count);
statsp->drop_count);
}
static void
}
#ifdef NSCD_DEBUG
static void
int i;
}
}
}
#endif /* NSCD_DEBUG */
#ifdef NSCD_DEBUG
static void
int i;
}
}
#endif /* NSCD_DEBUG */
#ifdef NSCD_DEBUG
static void
int i;
gettext("\n\nCACHE [name=%s, nodes=%lu]:\n"),
gettext("Starting with the most recently accessed:\n"));
}
}
#endif /* NSCD_DEBUG */
static void
else
else
}
static void
#ifdef NSCD_DEBUG
#endif /* NSCD_DEBUG */
/* already in the desired position */
return;
/* new queue */
return;
}
/* new entry (prev == NULL AND tail != entry) */
return;
}
/* existing entry */
else
}
/*
* Init cache
*/
init_cache(int debug_level) {
int cflags;
if (nsc_entry_cache == NULL)
return (NSCD_NO_MEMORY);
return (NSCD_SUCCESS);
}
/*
* Create cache
*/
nsc_db_t *
int (*compar) (const void *, const void *),
char *me = "make_cache";
goto out;
}
/* Assign compare routine */
if (_NSC_DB_CES_KEY(nscdb))
else if (_NSC_DB_CIS_KEY(nscdb))
else if (_NSC_DB_INT_KEY(nscdb))
else
assert(0);
} else {
}
/* The cache is an AVL tree */
/* Assign log routine */
if (_NSC_DB_STR_KEY(nscdb))
else if (_NSC_DB_INT_KEY(nscdb))
else
} else {
}
/* The AVL tree based cache uses a hash table for quick access */
if (htsize != 0) {
/* Determine hash table size based on type */
if (htsize < 0) {
switch (httype) {
case nsc_ht_power2:
break;
case nsc_ht_prime:
case nsc_ht_default:
default:
}
}
/* Create the hash table */
goto out;
}
/* Assign gethash routine */
if (_NSC_DB_CES_KEY(nscdb))
else if (_NSC_DB_CIS_KEY(nscdb))
else if (_NSC_DB_INT_KEY(nscdb))
else
assert(0);
} else {
}
}
return (nscdb);
out:
if (nscdb)
return (NULL);
}
/*
* verify
*/
/* ARGSUSED */
void *data,
struct nscd_cfg_param_desc *pdesc,
void **cookie)
{
return (NSCD_SUCCESS);
}
/*
* notify
*/
/* ARGSUSED */
void *data,
struct nscd_cfg_param_desc *pdesc,
void **cookie)
{
void *dp;
int i;
/* group data */
/* global config */
} else if (_nscd_cfg_flag_is_set(dflag,
/* non-global config for all dbs */
for (i = 0; i < CACHE_CTX_COUNT; i++) {
ctx = cache_ctx_p[i];
return (NSCD_CTX_NOT_FOUND);
}
} else {
/* non-global config for a specific db */
/* ignore non-caching databases */
return (NSCD_SUCCESS);
}
return (NSCD_SUCCESS);
}
/* individual data */
/* global config */
} else if (_nscd_cfg_flag_is_set(dflag,
/* non-global config for all dbs */
for (i = 0; i < CACHE_CTX_COUNT; i++) {
ctx = cache_ctx_p[i];
return (NSCD_CTX_NOT_FOUND);
}
} else {
/* non-global config for a specific db */
/* ignore non-caching databases */
return (NSCD_SUCCESS);
}
return (NSCD_SUCCESS);
}
/*
* get stat
*/
/* ARGSUSED */
void **stat,
struct nscd_cfg_stat_desc *sdesc,
{
int i;
return (NSCD_NO_MEMORY);
for (i = 0; i < CACHE_CTX_COUNT; i++) {
if (cache_ctx_p[i] == NULL)
stats = null_stats;
else {
(void) mutex_unlock(
&cache_ctx_p[i]->stats_mutex);
}
}
} else {
return (rc);
}
}
return (NSCD_SUCCESS);
}
/*
* This function should only be called when nscd is
* not a daemon.
*/
void
{
int i;
char *me = "nsc_info";
if (ctx) {
return;
}
if (dbname) {
if (rc == NSCD_INVALID_ARGUMENT) {
return;
} else if (rc == NSCD_NO_MEMORY) {
(me, "%s: unable to create cache context - no memory\n",
dbname);
return;
}
return;
}
return;
for (i = 0; i < CACHE_CTX_COUNT; i++) {
}
}
static void
return;
(void) print_stats(&stats);
}
static void
return;
(void) print_stats(&stats);
}
#ifdef NSCD_DEBUG
/*
* This function should only be called when nscd is
* not a daemon.
*/
int
char *me = "nsc_dump";
int i;
return (NSCD_CACHE_INVALID_CACHE_NAME);
}
return (NSCD_CACHE_NO_CACHE_CTX);
}
if (enabled == nscd_false)
return (NSCD_CACHE_DISABLED);
return (NSCD_CACHE_NO_CACHE_FOUND);
}
return (NSCD_SUCCESS);
}
#endif /* NSCD_DEBUG */
/*
* These macros are for exclusive use of nsc_lookup
*/
return (retcode);
#define NSC_LOOKUP_NO_CACHE(str) \
(me, "%s: name service lookup (bypassing cache\n", \
str); \
(me, "%s: name service lookup status = %d\n", \
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
int errnum;
char *me = "start_threads";
/*
* kick off the revalidate thread (if necessary)
*/
(me, "thr_create (revalidate thread for %s): %s\n",
exit(1);
}
}
/*
* kick off the reaper thread (if necessary)
*/
(me, "thr_create (reaper thread for %s): %s\n",
exit(1);
}
}
}
/*
* 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)
{
char *me = "nsw_config_in_phdr";
if (off == 0)
return (0);
if (pdbd->o_default_config == 0)
return (0);
(me, "use caller specified nsswitch config\n");
return (1);
} else
return (0);
}
static nss_status_t
{
char *me = "copy_result";
/* return NSS_ERROR if not enough room to copy result */
return (NSS_ERROR);
} else {
char *dst;
return (NSS_SUCCESS);
/* some frontend code expects a terminating NULL char */
(me, "cache data (len = %lld): %s\n",
return (NSS_SUCCESS);
}
}
static int
{
int ttl;
char *me = "get_dns_ttl";
/* if returned, dns ttl is stored in the extended data area */
return (-1);
return (-1);
return (ttl);
}
static int
{
char *me = "check_config";
/* see if the cached config needs update */
(me, "config for context %s, database %s updated\n",
}
return (NOTFOUND);
else {
}
}
/*
* if caller requests lookup using his
* own nsswitch config, bypass cache
*/
}
/* no need of cache if we are dealing with 0 ttls */
return (NOTFOUND);
return (SERVERERROR);
}
return (CONTINUE);
}
/*
* Invalidate cache if database file has been modified.
* See check_files config param for details.
*/
static void
{
char *me = "check_db_file";
if (cfg.check_interval != 0 &&
return;
if (ctx->file_mtime == 0) {
if (ctx->file_mtime == 0) {
}
}
}
}
if (file_modified == nscd_true) {
(me, "%s: file %s has been modified - invalidating cache\n",
}
}
static int
char *dbname;
int cfg_rc;
char whoami[128];
char *me = "lookup_int";
/* extract dbop, dbname, key and cred */
if (status != NSS_SUCCESS) {
return (SERVERERROR);
}
/* get the cache context */
return (NOTFOUND);
else {
}
}
}
(me, "%s:%d: no cache found\n",
return (NOTFOUND);
else {
}
}
}
}
} else {
}
return (cfg_rc);
/*
* Invalidate cache if file has been modified.
*/
/* Lookup the cache table */
for (;;) {
delete = nscd_false;
if (rc != NSCD_SUCCESS) {
/* Either no entry and avoid name service */
if (rc == NSCD_DB_ENTRY_NOT_FOUND ||
return (NOTFOUND);
/* OR memory error */
return (SERVERERROR);
}
/* get the stats from the entry */
/*
* What should we do next ?
*/
switch (this_stats->status) {
case ST_NEW_ENTRY:
break;
case ST_UPDATE_PENDING:
return (NOTFOUND);
else
break;
case ST_LOOKUP_PENDING:
return (NOTFOUND);
}
break;
case ST_DISCARD:
return (NOTFOUND);
}
/* otherwise reuse the entry */
break;
default:
(me, "%s: cached entry needs to be updated\n",
whoami);
} else
break;
}
if (next_action == _NSC_WAIT) {
/* do we have clearance ? */
/* nope. quit */
"%s: no clearance to wait\n");
}
/* yes can wait */
&this_stats->status);
continue;
}
break;
}
if (next_action == _NSC_NSLOOKUP) {
else
"%s: no clearance for lookup\n");
}
/* block any threads accessing this entry */
/* release lock and do name service lookup */
this_stats->status = 0;
/* signal waiting threads */
(me, "%s: name service lookup status = %d\n",
if (status == NSS_SUCCESS) {
int ttl;
/*
* data found in name service
* update cache
*/
if (status != NSS_SUCCESS) {
"%s: failed to update cache\n");
}
/*
* store unpacked key in cache
*/
if (status != NSS_SUCCESS) {
"%s: failed to extract key\n");
}
/* update +ve miss count */
}
/* update +ve ttl */
/* honor the dns ttl less than postive ttl */
/*
* start the revalidation and reaper threads
* if not already started
*/
"%s: cache updated with positive entry\n");
} else if (status == NSS_NOTFOUND) {
/*
* data not found in name service
* update cache
*/
"%s: ERANGE, cache not updated with negative entry\n");
}
if (status != NSS_SUCCESS) {
"%s: failed to update cache\n");
}
/* store unpacked key in cache */
if (status != NSS_SUCCESS) {
"%s: failed to extract key\n");
}
/* update -ve ttl */
/* update -ve miss count */
}
/*
* start the revalidation and reaper threads
* if not already started
*/
"%s: cache updated with negative entry\n");
} else {
/*
* name service lookup failed
*/
else
(me, "%s: name service lookup failed (status=%d, errno=%d)\n",
return (SERVERERROR);
}
} else if (next_action == _NSC_USECACHED) {
/*
* found entry in cache
*/
"%s: no need to update\n");
}
NSS_SUCCESS) {
/* positive hit */
/* update response buffer */
"%s: response buffer insufficient\n");
}
"%s: positive entry in cache\n");
} else {
/* negative hit */
"%s: negative entry in cache\n");
}
}
"%s: cache backend failure\n");
}
/*
* NSCD cache backend lookup function
*/
/*ARGSUSED*/
void
int rc;
return;
switch (rc) {
case SUCCESS:
break;
case NOTFOUND:
break;
case SERVERERROR:
/* status and errno already set in the phdr */
break;
case NOSERVER:
break;
}
}
static nsc_ctx_t *
init_cache_ctx(int i) {
return (NULL);
/* init locks and semaphores */
cache_init_ctx[i](ctx);
cache_ctx_p[i] = ctx;
return (ctx);
}
static void
{
for (;;) {
if (slp < 60)
slp = 60;
if (count != 0) {
if (interval == 0)
interval = 1;
}
} else {
}
}
}
static void
{
int i;
int bufsiz;
char *me = "getxy_keepalive";
/* we won't be here if keep == 0 so need to check that */
(me, "memory allocation failure\n");
exit(1);
}
/* leave pending calls alone */
/* do_revalidate */
entry);
}
}
for (i = 1; i <= keep; i++) {
continue;
/*
* for positive cache, in addition to the packed
* header size, allocate twice the size of the
* existing result (in case the result grows
* process a possible large entry in the /etc files)
*/
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)
*/
(me, "memory allocation failure\n");
exit(1);
}
}
/* launch update thread for each keep hot entry */
for (i = keep; i > 0; i--) {
continue; /* unused slot in table */
(me, "memory allocation failure\n");
exit(1);
}
if (launch_update(largs) < 0)
exit(1);
}
/*
* The update thread will handle freeing of buffer and largs.
* Free the table here.
*/
}
static int
{
char *me = "launch_update";
int errnum;
if (errnum != 0) {
(me, "%s: thread creation failure (%d)\n",
return (-1);
}
return (0);
}
static void
/* update the length of the data buffer */
}
/*
* Invalidate cache
*/
void
int i;
char *me = "nsc_invalidate";
if (ctx) {
return;
}
if (dbname) {
return;
}
(me, "%s: no cache context found\n",
dbname);
return;
}
return;
}
ctxs = cache_ctx_p;
for (i = 0; i < CACHE_CTX_COUNT; i++) {
ctx_invalidate(ctxs[i]);
}
}
/*
* Invalidate cache by context
*/
static void
int i;
char *me = "ctx_invalidate";
continue;
/* leave pending calls alone */
}
}
}
/*
* Free nsc_entry_t
*
* Pre-reqs:
* nscdb->db_mutex lock must be held before calling this function
*/
static void
}
}
static nscd_rc_t
char *me = "lookup_cache";
/* set the search key */
/* lookup the hash table ==> O(1) */
return (NSCD_SUCCESS);
}
}
/* if not found, lookup the AVL tree ==> O(log n) */
/* move it to the hash table */
}
}
return (NSCD_SUCCESS);
}
/* entry not found in the cache */
return (NSCD_DB_ENTRY_NOT_FOUND);
}
/* allocate memory for new entry (stub) */
return (NSCD_NO_MEMORY);
}
/*
* 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.
*/
/*
* Add the entry to the cache.
*/
/* Have we exceeded max entries ? */
(me, "%s: maximum entries exceeded -- "
"deleting least recently used entry\n",
whoami);
break;
}
}
/*
* 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
char *me = "reaper";
for (;;) {
if (nsc_entries == 0) {
/* sleep for atleast 60 seconds */
if (ttl < 60)
ttl = 60;
continue;
}
/*
* 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) {
nodes_per_interval = 256;
} else {
seconds_per_interval = 32;
if (nodes_per_interval < 256)
nodes_per_interval = 256;
}
(me, "%s: total entries = %d, "
"seconds per interval = %d, "
"nodes per interval = %d\n",
if (extra_sleep > 0)
(void) sleep(extra_sleep);
}
}
static uint_t
char *me = "reap_cache";
count = 0;
total_sleep = 0;
if (nodes_togo == 0) {
(void) sleep(seconds_per_interval);
}
/* delete ST_DISCARD and expired nodes */
break;
/*
* 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()
*/
count++;
} else {
}
nodes_togo--;
}
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
*/
else
/* Recommended size is same as the current size. Done */
continue;
}
(me, "%s: resizing hash table from %d to %d\n",
/*
* Dump old hashes because it would be time
* consuming to rehash them.
*/
(me,
"%s: memory allocation failure\n",
/* -1 to try later */
} else {
}
}
/*
* if cache is almost full then reduce it to a safe level by
* evicting LRU entries
*/
/* No limit on number of entries. Done */
if (maxentries == 0)
goto out;
/* what is the percentage of cache used ? */
if (value < _NSC_EVICTION_START_LEVEL)
goto out;
/*
* cache needs to be reduced to a safe level
*/
/*
* Reduce each subcache by 'value' percent
*/
/* Start from LRU entry i.e queue head */
/* Leave nodes with pending updates alone */
count++;
nodes_togo--;
}
}
}
out:
return (total_sleep);
}