2N/A/*
2N/A * CDDL HEADER START
2N/A *
2N/A * The contents of this file are subject to the terms of the
2N/A * Common Development and Distribution License, Version 1.0 only
2N/A * (the "License"). You may not use this file except in compliance
2N/A * with the License.
2N/A *
2N/A * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
2N/A * or http://www.opensolaris.org/os/licensing.
2N/A * See the License for the specific language governing permissions
2N/A * and limitations under the License.
2N/A *
2N/A * When distributing Covered Code, include this CDDL HEADER in each
2N/A * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
2N/A * If applicable, add the following below this CDDL HEADER, with the
2N/A * fields enclosed by brackets "[]" replaced with your own identifying
2N/A * information: Portions Copyright [yyyy] [name of copyright owner]
2N/A *
2N/A * CDDL HEADER END
2N/A */
2N/A/*
2N/A * Copyright 2004 Sun Microsystems, Inc. All rights reserved.
2N/A * Use is subject to license terms.
2N/A */
2N/A
2N/A#pragma ident "%Z%%M% %I% %E% SMI"
2N/A
2N/A/*
2N/A * This module contains a cache used to optimized scope and DA
2N/A * discovery. Entries live for a short duration only (about 10 seconds),
2N/A * although their lifetime can be advanced somewhat by frequent use.
2N/A * The intent is that the canonical source for DAs will always be slpd,
2N/A * so the short lifetime of cache entries is designed to force clients
2N/A * to consult slpd frequently so as to pick up the latest DA state
2N/A * quickly.
2N/A *
2N/A * The cache is managed by a thread which monitors calls into the cache.
2N/A * If the cache has been unused for a certain amount of time, the thread
2N/A * frees the cache and exits.
2N/A *
2N/A * The cache is keyed on the queries sent to slpd to access slpd's DA
2N/A * table. Associated with each query is a reply (in the format of an
2N/A * on-the-wire SLP SRVRPLY message).
2N/A * The cache is accessed by the following two functions:
2N/A *
2N/A * slp_find_das_cached: searches the cache
2N/A * slp_put_das_cached: adds a reply to the cache
2N/A *
2N/A * All parameters added to the cache are copied in first, and all results
2N/A * read from the cache are copied out, so all memory must be freed by
2N/A * the caller.
2N/A */
2N/A
2N/A#include <stdio.h>
2N/A#include <stdlib.h>
2N/A#include <thread.h>
2N/A#include <synch.h>
2N/A#include <syslog.h>
2N/A#include <string.h>
2N/A#include <sys/types.h>
2N/A#include <time.h>
2N/A#include <errno.h>
2N/A#include <slp-internal.h>
2N/A
2N/A/* These constants control the behaviour of the cache */
2N/A#define MAX_LIFETIME 25 /* max lifetime, in seconds */
2N/A#define ADVANCE_PER_USE 5 /* seconds lifetime is extended on each use */
2N/A#define INIT_LIFETIME 10 /* cache entries start with this lifetime */
2N/A
2N/A/* Management thread components */
2N/A#define IDLE_TIMEOUT 30 /* thread will exit after this idle time */
2N/Astatic int cache_thr_running;
2N/Astatic mutex_t start_lock = DEFAULTMUTEX;
2N/Astatic int cache_called;
2N/Astatic cond_t cache_called_cond;
2N/Astatic mutex_t cache_called_lock = DEFAULTMUTEX;
2N/Astatic SLPError start_cache_thr();
2N/Astatic void cache_thr();
2N/A
2N/A/* The cache and cache synchronization */
2N/Astatic void *da_cache;
2N/Astatic mutex_t cache_lock = DEFAULTMUTEX;
2N/Astruct cache_entry {
2N/A const char *query;
2N/A const char *reply;
2N/A unsigned int reply_len;
2N/A time_t max_life;
2N/A time_t expires;
2N/A};
2N/Atypedef struct cache_entry cache_entry_t;
2N/A
2N/A/* cache management and searching */
2N/Astatic int compare_entries(const void *, const void *);
2N/Astatic void free_cache_entry(void *, VISIT);
2N/A
2N/A/*
2N/A * Searches the cache for the reply to 'query'. Returns the reply if
2N/A * found, otherwise NULL.
2N/A * The caller must free the result.
2N/A */
2N/Achar *slp_find_das_cached(const char *query) {
2N/A cache_entry_t ce[1], **ans;
2N/A char *reply = NULL;
2N/A time_t now;
2N/A
2N/A if (!cache_thr_running) {
2N/A if (start_cache_thr() != SLP_OK) {
2N/A return (NULL);
2N/A }
2N/A }
2N/A
2N/A (void) mutex_lock(&cache_lock);
2N/A ce->query = query;
2N/A
2N/A ans = slp_tfind(ce, &da_cache, compare_entries);
2N/A if (ans) {
2N/A now = time(NULL);
2N/A if ((*ans)->expires < now || (*ans)->max_life < now) {
2N/A goto done;
2N/A }
2N/A
2N/A /* copy out the reply */
2N/A if (!(reply = malloc((*ans)->reply_len))) {
2N/A slp_err(LOG_CRIT, 0, "slp_find_das_cached",
2N/A "out of memory");
2N/A goto done;
2N/A }
2N/A (void) memcpy(reply, (*ans)->reply, (*ans)->reply_len);
2N/A (*ans)->expires += ADVANCE_PER_USE;
2N/A }
2N/A
2N/A /* notify cache thread of call */
2N/A (void) mutex_lock(&cache_called_lock);
2N/A cache_called = 1;
2N/A (void) cond_signal(&cache_called_cond);
2N/A (void) mutex_unlock(&cache_called_lock);
2N/A
2N/Adone:
2N/A (void) mutex_unlock(&cache_lock);
2N/A return (reply);
2N/A}
2N/A
2N/A/*
2N/A * Adds 'reply' to the cache under the index 'query'. Both parameters
2N/A * are copied in first, so the caller may free them after the call.
2N/A * 'len' is the length of 'reply' in bytes.
2N/A */
2N/Avoid slp_put_das_cached(const char *query, const char *reply,
2N/A unsigned int len) {
2N/A cache_entry_t *ce, **ce2;
2N/A time_t now;
2N/A
2N/A if (!cache_thr_running) {
2N/A if (start_cache_thr() != SLP_OK) {
2N/A return;
2N/A }
2N/A }
2N/A
2N/A /* create the cache entry for this reply */
2N/A if (!(ce = malloc(sizeof (*ce)))) {
2N/A slp_err(LOG_CRIT, 0, "slp_put_das_cached", "out of memory");
2N/A return;
2N/A }
2N/A
2N/A if (!(ce->query = strdup(query))) {
2N/A free(ce);
2N/A slp_err(LOG_CRIT, 0, "slp_put_das_cached", "out of memory");
2N/A return;
2N/A }
2N/A
2N/A if (!(ce->reply = malloc(len))) {
2N/A free((void *) (ce->query));
2N/A free(ce);
2N/A slp_err(LOG_CRIT, 0, "slp_put_das_cached", "out of memory");
2N/A return;
2N/A }
2N/A (void) memcpy((void *) (ce->reply), reply, len);
2N/A ce->reply_len = len;
2N/A now = time(NULL);
2N/A ce->max_life = now + MAX_LIFETIME;
2N/A ce->expires = now + INIT_LIFETIME;
2N/A
2N/A /* write to the cache */
2N/A (void) mutex_lock(&cache_lock);
2N/A ce2 = slp_tsearch((void *) ce, &da_cache, compare_entries);
2N/A if (ce != *ce2) {
2N/A /* overwrite existing entry */
2N/A free((void *) ((*ce2)->query));
2N/A free((void *) ((*ce2)->reply));
2N/A free(*ce2);
2N/A *ce2 = ce;
2N/A }
2N/A
2N/A (void) mutex_unlock(&cache_lock);
2N/A}
2N/A
2N/Astatic int compare_entries(const void *x1, const void *x2) {
2N/A cache_entry_t *e1 = (cache_entry_t *)x1;
2N/A cache_entry_t *e2 = (cache_entry_t *)x2;
2N/A
2N/A return (strcasecmp(e1->query, e2->query));
2N/A}
2N/A
2N/Astatic void free_cache_entry(void *node, VISIT order) {
2N/A if (order == endorder || order == leaf) {
2N/A cache_entry_t *ce = *(cache_entry_t **)node;
2N/A
2N/A free((void *) (ce->query));
2N/A free((void *) (ce->reply));
2N/A free(ce);
2N/A free(node);
2N/A }
2N/A}
2N/A
2N/Astatic SLPError start_cache_thr() {
2N/A int terr;
2N/A SLPError err = SLP_OK;
2N/A
2N/A (void) mutex_lock(&start_lock);
2N/A
2N/A if (cache_thr_running) {
2N/A goto start_done;
2N/A }
2N/A
2N/A (void) cond_init(&cache_called_cond, 0, NULL);
2N/A
2N/A if ((terr = thr_create(
2N/A 0, NULL, (void *(*)(void *)) cache_thr,
2N/A NULL, 0, NULL)) != 0) {
2N/A slp_err(LOG_CRIT, 0, "start_cache_thr",
2N/A "could not start thread: %s", strerror(terr));
2N/A err = SLP_INTERNAL_SYSTEM_ERROR;
2N/A goto start_done;
2N/A }
2N/A cache_thr_running = 1;
2N/A
2N/Astart_done:
2N/A (void) mutex_unlock(&start_lock);
2N/A return (err);
2N/A}
2N/A
2N/Astatic void cache_thr() {
2N/A timestruc_t timeout;
2N/A timeout.tv_nsec = 0;
2N/A
2N/A (void) mutex_lock(&cache_called_lock);
2N/A cache_called = 0;
2N/A
2N/A while (cache_called == 0) {
2N/A int err;
2N/A
2N/A timeout.tv_sec = IDLE_TIMEOUT;
2N/A err = cond_reltimedwait(&cache_called_cond,
2N/A &cache_called_lock, &timeout);
2N/A
2N/A if (err == ETIME) {
2N/A (void) mutex_lock(&cache_lock);
2N/A /* free cache */
2N/A if (da_cache) {
2N/A slp_twalk(da_cache,
2N/A (void (*)(void *, VISIT, int, void *))free_cache_entry,
2N/A 0, NULL);
2N/A }
2N/A da_cache = NULL;
2N/A (void) mutex_unlock(&cache_lock);
2N/A cache_thr_running = 0;
2N/A (void) mutex_unlock(&cache_called_lock);
2N/A thr_exit(NULL);
2N/A } else {
2N/A cache_called = 0;
2N/A }
2N/A }
2N/A}