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, Version 1.0 only 6 * (the "License"). You may not use this file except in compliance 7 * with the License. 8 * 9 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE 10 * or http://www.opensolaris.org/os/licensing. 11 * See the License for the specific language governing permissions 12 * and limitations under the License. 13 * 14 * When distributing Covered Code, include this CDDL HEADER in each 15 * file and include the License file at usr/src/OPENSOLARIS.LICENSE. 16 * If applicable, add the following below this CDDL HEADER, with the 17 * fields enclosed by brackets "[]" replaced with your own identifying 18 * information: Portions Copyright [yyyy] [name of copyright owner] 19 * 20 * CDDL HEADER END 21 */ 22 /* 23 * Copyright 2004 Sun Microsystems, Inc. All rights reserved. 24 * Use is subject to license terms. 25 */ 26 27 /* 28 * This module contains a cache used to optimized scope and DA 29 * discovery. Entries live for a short duration only (about 10 seconds), 30 * although their lifetime can be advanced somewhat by frequent use. 31 * The intent is that the canonical source for DAs will always be slpd, 32 * so the short lifetime of cache entries is designed to force clients 33 * to consult slpd frequently so as to pick up the latest DA state 34 * quickly. 35 * 36 * The cache is managed by a thread which monitors calls into the cache. 37 * If the cache has been unused for a certain amount of time, the thread 38 * frees the cache and exits. 39 * 40 * The cache is keyed on the queries sent to slpd to access slpd's DA 41 * table. Associated with each query is a reply (in the format of an 42 * on-the-wire SLP SRVRPLY message). 43 * The cache is accessed by the following two functions: 44 * 45 * slp_find_das_cached: searches the cache 46 * slp_put_das_cached: adds a reply to the cache 47 * 48 * All parameters added to the cache are copied in first, and all results 49 * read from the cache are copied out, so all memory must be freed by 50 * the caller. 51 */ 52 53 #include <stdio.h> 54 #include <stdlib.h> 55 #include <thread.h> 56 #include <synch.h> 57 #include <syslog.h> 58 #include <string.h> 59 #include <sys/types.h> 60 #include <time.h> 61 #include <errno.h> 62 #include <slp-internal.h> 63 64 /* These constants control the behaviour of the cache */ 65 #define MAX_LIFETIME 25 /* max lifetime, in seconds */ 66 #define ADVANCE_PER_USE 5 /* seconds lifetime is extended on each use */ 67 #define INIT_LIFETIME 10 /* cache entries start with this lifetime */ 68 69 /* Management thread components */ 70 #define IDLE_TIMEOUT 30 /* thread will exit after this idle time */ 71 static int cache_thr_running; 72 static mutex_t start_lock = DEFAULTMUTEX; 73 static int cache_called; 74 static cond_t cache_called_cond; 75 static mutex_t cache_called_lock = DEFAULTMUTEX; 76 static SLPError start_cache_thr(); 77 static void *cache_thr(void *); 78 79 /* The cache and cache synchronization */ 80 static void *da_cache; 81 static mutex_t cache_lock = DEFAULTMUTEX; 82 struct cache_entry { 83 const char *query; 84 const char *reply; 85 unsigned int reply_len; 86 time_t max_life; 87 time_t expires; 88 }; 89 typedef struct cache_entry cache_entry_t; 90 91 /* cache management and searching */ 92 static int compare_entries(const void *, const void *); 93 static void free_cache_entry(void *, VISIT, int, void *); 94 95 /* 96 * Searches the cache for the reply to 'query'. Returns the reply if 97 * found, otherwise NULL. 98 * The caller must free the result. 99 */ 100 char *slp_find_das_cached(const char *query) { 101 cache_entry_t ce[1], **ans; 102 char *reply = NULL; 103 time_t now; 104 105 if (!cache_thr_running) { 106 if (start_cache_thr() != SLP_OK) { 107 return (NULL); 108 } 109 } 110 111 (void) mutex_lock(&cache_lock); 112 ce->query = query; 113 114 ans = slp_tfind(ce, &da_cache, compare_entries); 115 if (ans) { 116 now = time(NULL); 117 if ((*ans)->expires < now || (*ans)->max_life < now) { 118 goto done; 119 } 120 121 /* copy out the reply */ 122 if (!(reply = malloc((*ans)->reply_len))) { 123 slp_err(LOG_CRIT, 0, "slp_find_das_cached", 124 "out of memory"); 125 goto done; 126 } 127 (void) memcpy(reply, (*ans)->reply, (*ans)->reply_len); 128 (*ans)->expires += ADVANCE_PER_USE; 129 } 130 131 /* notify cache thread of call */ 132 (void) mutex_lock(&cache_called_lock); 133 cache_called = 1; 134 (void) cond_signal(&cache_called_cond); 135 (void) mutex_unlock(&cache_called_lock); 136 137 done: 138 (void) mutex_unlock(&cache_lock); 139 return (reply); 140 } 141 142 /* 143 * Adds 'reply' to the cache under the index 'query'. Both parameters 144 * are copied in first, so the caller may free them after the call. 145 * 'len' is the length of 'reply' in bytes. 146 */ 147 void slp_put_das_cached(const char *query, const char *reply, 148 unsigned int len) { 149 cache_entry_t *ce, **ce2; 150 time_t now; 151 152 if (!cache_thr_running) { 153 if (start_cache_thr() != SLP_OK) { 154 return; 155 } 156 } 157 158 /* create the cache entry for this reply */ 159 if (!(ce = malloc(sizeof (*ce)))) { 160 slp_err(LOG_CRIT, 0, "slp_put_das_cached", "out of memory"); 161 return; 162 } 163 164 if (!(ce->query = strdup(query))) { 165 free(ce); 166 slp_err(LOG_CRIT, 0, "slp_put_das_cached", "out of memory"); 167 return; 168 } 169 170 if (!(ce->reply = malloc(len))) { 171 free((void *) (ce->query)); 172 free(ce); 173 slp_err(LOG_CRIT, 0, "slp_put_das_cached", "out of memory"); 174 return; 175 } 176 (void) memcpy((void *) (ce->reply), reply, len); 177 ce->reply_len = len; 178 now = time(NULL); 179 ce->max_life = now + MAX_LIFETIME; 180 ce->expires = now + INIT_LIFETIME; 181 182 /* write to the cache */ 183 (void) mutex_lock(&cache_lock); 184 ce2 = slp_tsearch((void *) ce, &da_cache, compare_entries); 185 if (ce != *ce2) { 186 /* overwrite existing entry */ 187 free((void *) ((*ce2)->query)); 188 free((void *) ((*ce2)->reply)); 189 free(*ce2); 190 *ce2 = ce; 191 } 192 193 (void) mutex_unlock(&cache_lock); 194 } 195 196 static int compare_entries(const void *x1, const void *x2) { 197 cache_entry_t *e1 = (cache_entry_t *)x1; 198 cache_entry_t *e2 = (cache_entry_t *)x2; 199 200 return (strcasecmp(e1->query, e2->query)); 201 } 202 203 static void 204 free_cache_entry(void *node, VISIT order, int arg __unused, void *arg1 __unused) 205 { 206 if (order == endorder || order == leaf) { 207 cache_entry_t *ce = *(cache_entry_t **)node; 208 209 free((void *) (ce->query)); 210 free((void *) (ce->reply)); 211 free(ce); 212 free(node); 213 } 214 } 215 216 static SLPError start_cache_thr() { 217 int terr; 218 SLPError err = SLP_OK; 219 220 (void) mutex_lock(&start_lock); 221 222 if (cache_thr_running) { 223 goto start_done; 224 } 225 226 (void) cond_init(&cache_called_cond, 0, NULL); 227 228 if ((terr = thr_create(0, 0, cache_thr, NULL, 0, NULL)) != 0) { 229 slp_err(LOG_CRIT, 0, "start_cache_thr", 230 "could not start thread: %s", strerror(terr)); 231 err = SLP_INTERNAL_SYSTEM_ERROR; 232 goto start_done; 233 } 234 cache_thr_running = 1; 235 236 start_done: 237 (void) mutex_unlock(&start_lock); 238 return (err); 239 } 240 241 static void * 242 cache_thr(void *arg __unused) 243 { 244 timestruc_t timeout; 245 timeout.tv_nsec = 0; 246 247 (void) mutex_lock(&cache_called_lock); 248 cache_called = 0; 249 250 while (cache_called == 0) { 251 int err; 252 253 timeout.tv_sec = IDLE_TIMEOUT; 254 err = cond_reltimedwait(&cache_called_cond, 255 &cache_called_lock, &timeout); 256 257 if (err == ETIME) { 258 (void) mutex_lock(&cache_lock); 259 /* free cache */ 260 if (da_cache) { 261 slp_twalk(da_cache, free_cache_entry, 0, NULL); 262 } 263 da_cache = NULL; 264 (void) mutex_unlock(&cache_lock); 265 cache_thr_running = 0; 266 (void) mutex_unlock(&cache_called_lock); 267 thr_exit(NULL); 268 } else { 269 cache_called = 0; 270 } 271 } 272 return (NULL); 273 } 274