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 */
slp_find_das_cached(const char * query)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 */
slp_put_das_cached(const char * query,const char * reply,unsigned int len)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
compare_entries(const void * x1,const void * x2)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
free_cache_entry(void * node,VISIT order,int arg __unused,void * arg1 __unused)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
start_cache_thr()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 *
cache_thr(void * arg __unused)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