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 (the "License").
6 * You may not use this file except in compliance with the License.
7 *
8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9 * or http://www.opensolaris.org/os/licensing.
10 * See the License for the specific language governing permissions
11 * and limitations under the License.
12 *
13 * When distributing Covered Code, include this CDDL HEADER in each
14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15 * If applicable, add the following below this CDDL HEADER, with the
16 * fields enclosed by brackets "[]" replaced with your own identifying
17 * information: Portions Copyright [yyyy] [name of copyright owner]
18 *
19 * CDDL HEADER END
20 */
21 /*
22 * Copyright 2010 Sun Microsystems, Inc. All rights reserved.
23 * Use is subject to license terms.
24 */
25
26 #include <assert.h>
27 #include <sys/avl.h>
28 #include <smbsrv/libsmb.h>
29
30 /*
31 * Cache lock modes
32 */
33 #define SMB_CACHE_RDLOCK 0
34 #define SMB_CACHE_WRLOCK 1
35
36 #define SMB_CACHE_STATE_NOCACHE 0
37 #define SMB_CACHE_STATE_READY 1
38 #define SMB_CACHE_STATE_REFRESHING 2
39 #define SMB_CACHE_STATE_DESTROYING 3
40
41 static int smb_cache_lock(smb_cache_t *, int);
42 static int smb_cache_rdlock(smb_cache_t *);
43 static int smb_cache_wrlock(smb_cache_t *);
44 static void smb_cache_unlock(smb_cache_t *);
45 static boolean_t smb_cache_wait(smb_cache_t *);
46 static void smb_cache_destroy_nodes(smb_cache_t *);
47
48 /*
49 * Creates an AVL tree and initializes the given cache handle.
50 * Transfers the cache to READY state.
51 *
52 * This function does not populate the cache.
53 *
54 * chandle pointer to a smb_cache_t structure
55 * waittime see smb_cache_refreshing() comments
56 * cmpfn compare function used by AVL tree
57 * freefn if set, it will be used to free any allocated
58 * memory for the node data stored in the cache when
59 * that node is removed.
60 * copyfn this function has to be set and it is used
61 * to provide a copy of the node data stored in the
62 * cache to the caller of smb_cache_iterate or any other
63 * function that is used to access nodes data.
64 * This can typically be 'bcopy' if data is fixed size.
65 * datasz Size of data stored in the cache if it's fixed size.
66 * This size will be passed to the copy function.
67 */
68 void
smb_cache_create(smb_cache_t * chandle,uint32_t waittime,int (* cmpfn)(const void *,const void *),void (* freefn)(void *),void (* copyfn)(const void *,void *,size_t),size_t datasz)69 smb_cache_create(smb_cache_t *chandle, uint32_t waittime,
70 int (*cmpfn) (const void *, const void *),
71 void (*freefn)(void *),
72 void (*copyfn)(const void *, void *, size_t),
73 size_t datasz)
74 {
75 assert(chandle);
76 assert(copyfn);
77
78 (void) mutex_lock(&chandle->ch_mtx);
79 if (chandle->ch_state != SMB_CACHE_STATE_NOCACHE) {
80 (void) mutex_unlock(&chandle->ch_mtx);
81 return;
82 }
83
84 avl_create(&chandle->ch_cache, cmpfn, sizeof (smb_cache_node_t),
85 offsetof(smb_cache_node_t, cn_link));
86
87 chandle->ch_state = SMB_CACHE_STATE_READY;
88 chandle->ch_nops = 0;
89 chandle->ch_wait = waittime;
90 chandle->ch_sequence = random();
91 chandle->ch_datasz = datasz;
92 chandle->ch_free = freefn;
93 chandle->ch_copy = copyfn;
94 (void) mutex_unlock(&chandle->ch_mtx);
95 }
96
97 /*
98 * Destroys the cache.
99 *
100 * Transfers the cache to DESTROYING state while it's waiting for
101 * in-flight operation to finish, this will prevent any new operation
102 * to start. When all entries are removed the cache is transferred to
103 * NOCACHE state.
104 */
105 void
smb_cache_destroy(smb_cache_t * chandle)106 smb_cache_destroy(smb_cache_t *chandle)
107 {
108 (void) mutex_lock(&chandle->ch_mtx);
109 switch (chandle->ch_state) {
110 case SMB_CACHE_STATE_NOCACHE:
111 case SMB_CACHE_STATE_DESTROYING:
112 (void) mutex_unlock(&chandle->ch_mtx);
113 return;
114
115 default:
116 break;
117 }
118
119 chandle->ch_state = SMB_CACHE_STATE_DESTROYING;
120
121 while (chandle->ch_nops > 0)
122 (void) cond_wait(&chandle->ch_cv, &chandle->ch_mtx);
123
124 smb_cache_destroy_nodes(chandle);
125
126 avl_destroy(&chandle->ch_cache);
127 chandle->ch_state = SMB_CACHE_STATE_NOCACHE;
128 (void) mutex_unlock(&chandle->ch_mtx);
129 }
130
131 /*
132 * Removes and frees all the cache entries without destroy
133 * the cache itself.
134 */
135 void
smb_cache_flush(smb_cache_t * chandle)136 smb_cache_flush(smb_cache_t *chandle)
137 {
138 if (smb_cache_wrlock(chandle) == 0) {
139 smb_cache_destroy_nodes(chandle);
140 chandle->ch_sequence++;
141 smb_cache_unlock(chandle);
142 }
143 }
144
145 /*
146 * Based on the specified flag either add or replace given
147 * data. If ADD flag is specified and the item is already in
148 * the cache EEXIST error code is returned.
149 */
150 int
smb_cache_add(smb_cache_t * chandle,const void * data,int flags)151 smb_cache_add(smb_cache_t *chandle, const void *data, int flags)
152 {
153 smb_cache_node_t *newnode;
154 smb_cache_node_t *node;
155 avl_index_t where;
156 int rc = 0;
157
158 assert(data);
159
160 if ((rc = smb_cache_wrlock(chandle)) != 0)
161 return (rc);
162
163 if ((newnode = malloc(sizeof (smb_cache_node_t))) == NULL) {
164 smb_cache_unlock(chandle);
165 return (ENOMEM);
166 }
167
168 newnode->cn_data = (void *)data;
169 node = avl_find(&chandle->ch_cache, newnode, &where);
170 if (node != NULL) {
171 if (flags & SMB_CACHE_REPLACE) {
172 avl_remove(&chandle->ch_cache, node);
173 if (chandle->ch_free)
174 chandle->ch_free(node->cn_data);
175 free(node);
176 } else {
177 free(newnode);
178 smb_cache_unlock(chandle);
179 return (EEXIST);
180 }
181 }
182
183 avl_insert(&chandle->ch_cache, newnode, where);
184 chandle->ch_sequence++;
185
186 smb_cache_unlock(chandle);
187 return (rc);
188 }
189
190 /*
191 * Uses the given 'data' as key to find a cache entry
192 * and remove it. The memory allocated for the found node
193 * and its data is freed.
194 */
195 void
smb_cache_remove(smb_cache_t * chandle,const void * data)196 smb_cache_remove(smb_cache_t *chandle, const void *data)
197 {
198 smb_cache_node_t keynode;
199 smb_cache_node_t *node;
200
201 assert(data);
202
203 if (smb_cache_wrlock(chandle) != 0)
204 return;
205
206 keynode.cn_data = (void *)data;
207 node = avl_find(&chandle->ch_cache, &keynode, NULL);
208 if (node) {
209 chandle->ch_sequence++;
210 avl_remove(&chandle->ch_cache, node);
211 if (chandle->ch_free)
212 chandle->ch_free(node->cn_data);
213 free(node);
214 }
215
216 smb_cache_unlock(chandle);
217 }
218
219 /*
220 * Initializes the given cursor for iterating the cache
221 */
222 void
smb_cache_iterinit(smb_cache_t * chandle,smb_cache_cursor_t * cursor)223 smb_cache_iterinit(smb_cache_t *chandle, smb_cache_cursor_t *cursor)
224 {
225 cursor->cc_sequence = chandle->ch_sequence;
226 cursor->cc_next = NULL;
227 }
228
229 /*
230 * Iterate the cache using the given cursor.
231 *
232 * Data is copied to the given buffer ('data') using the copy function
233 * specified at cache creation time.
234 *
235 * If the cache is modified while an iteration is in progress it causes
236 * the iteration to finish prematurely. This is to avoid the need to lock
237 * the whole cache while it is being iterated.
238 */
239 boolean_t
smb_cache_iterate(smb_cache_t * chandle,smb_cache_cursor_t * cursor,void * data)240 smb_cache_iterate(smb_cache_t *chandle, smb_cache_cursor_t *cursor, void *data)
241 {
242 smb_cache_node_t *node;
243
244 assert(data);
245
246 if (smb_cache_rdlock(chandle) != 0)
247 return (B_FALSE);
248
249 if (cursor->cc_sequence != chandle->ch_sequence) {
250 smb_cache_unlock(chandle);
251 return (B_FALSE);
252 }
253
254 if (cursor->cc_next == NULL)
255 node = avl_first(&chandle->ch_cache);
256 else
257 node = AVL_NEXT(&chandle->ch_cache, cursor->cc_next);
258
259 if (node != NULL)
260 chandle->ch_copy(node->cn_data, data, chandle->ch_datasz);
261
262 cursor->cc_next = node;
263 smb_cache_unlock(chandle);
264
265 return (node != NULL);
266 }
267
268 /*
269 * Returns the number of cache entries
270 */
271 uint32_t
smb_cache_num(smb_cache_t * chandle)272 smb_cache_num(smb_cache_t *chandle)
273 {
274 uint32_t num = 0;
275
276 if (smb_cache_rdlock(chandle) == 0) {
277 num = (uint32_t)avl_numnodes(&chandle->ch_cache);
278 smb_cache_unlock(chandle);
279 }
280
281 return (num);
282 }
283
284 /*
285 * Transfers the cache into REFRESHING state. This function needs
286 * to be called when the whole cache is being populated or refereshed
287 * and not for individual changes.
288 *
289 * Calling this function will ensure any read access to the cache will
290 * be stalled until the update is finished, which is to avoid providing
291 * incomplete, inconsistent or stale information. Read accesses will be
292 * stalled for 'ch_wait' seconds (see smb_cache_lock), which is set at
293 * the cache creation time.
294 *
295 * If it is okay for the cache to be accessed while it's being populated
296 * or refreshed, then there is no need to call this function.
297 *
298 * If another thread is already updating the cache, other callers will wait
299 * until cache is no longer in REFRESHING state. The return code is decided
300 * based on the new state of the cache.
301 *
302 * This function does NOT perform the actual refresh.
303 */
304 int
smb_cache_refreshing(smb_cache_t * chandle)305 smb_cache_refreshing(smb_cache_t *chandle)
306 {
307 int rc = 0;
308
309 (void) mutex_lock(&chandle->ch_mtx);
310 switch (chandle->ch_state) {
311 case SMB_CACHE_STATE_READY:
312 chandle->ch_state = SMB_CACHE_STATE_REFRESHING;
313 rc = 0;
314 break;
315
316 case SMB_CACHE_STATE_REFRESHING:
317 while (chandle->ch_state == SMB_CACHE_STATE_REFRESHING)
318 (void) cond_wait(&chandle->ch_cv,
319 &chandle->ch_mtx);
320
321 if (chandle->ch_state == SMB_CACHE_STATE_READY) {
322 chandle->ch_state = SMB_CACHE_STATE_REFRESHING;
323 rc = 0;
324 } else {
325 rc = ENODATA;
326 }
327 break;
328
329 case SMB_CACHE_STATE_NOCACHE:
330 case SMB_CACHE_STATE_DESTROYING:
331 rc = ENODATA;
332 break;
333
334 default:
335 assert(0);
336 }
337
338 (void) mutex_unlock(&chandle->ch_mtx);
339 return (rc);
340 }
341
342 /*
343 * Transfers the cache from REFRESHING to READY state.
344 *
345 * Nothing will happen if the cache is no longer available
346 * or it is being destroyed.
347 *
348 * This function should only be called if smb_cache_refreshing()
349 * has already been invoked.
350 */
351 void
smb_cache_ready(smb_cache_t * chandle)352 smb_cache_ready(smb_cache_t *chandle)
353 {
354 (void) mutex_lock(&chandle->ch_mtx);
355 switch (chandle->ch_state) {
356 case SMB_CACHE_STATE_REFRESHING:
357 chandle->ch_state = SMB_CACHE_STATE_READY;
358 (void) cond_broadcast(&chandle->ch_cv);
359 break;
360
361 case SMB_CACHE_STATE_NOCACHE:
362 case SMB_CACHE_STATE_DESTROYING:
363 break;
364
365 case SMB_CACHE_STATE_READY:
366 default:
367 assert(0);
368 }
369 (void) mutex_unlock(&chandle->ch_mtx);
370 }
371
372 /*
373 * Lock the cache with the specified mode.
374 * If the cache is in updating state and a read lock is
375 * requested, the lock won't be granted until either the
376 * update is finished or SMB_CACHE_UPDATE_WAIT has passed.
377 *
378 * Whenever a lock is granted, the number of inflight cache
379 * operations is incremented.
380 */
381 static int
smb_cache_lock(smb_cache_t * chandle,int mode)382 smb_cache_lock(smb_cache_t *chandle, int mode)
383 {
384 (void) mutex_lock(&chandle->ch_mtx);
385 switch (chandle->ch_state) {
386 case SMB_CACHE_STATE_NOCACHE:
387 case SMB_CACHE_STATE_DESTROYING:
388 (void) mutex_unlock(&chandle->ch_mtx);
389 return (ENODATA);
390
391 case SMB_CACHE_STATE_REFRESHING:
392 /*
393 * Read operations should wait until the update
394 * is completed.
395 */
396 if (mode == SMB_CACHE_RDLOCK) {
397 if (!smb_cache_wait(chandle)) {
398 (void) mutex_unlock(&chandle->ch_mtx);
399 return (ETIME);
400 }
401 }
402 /* FALLTHROUGH */
403 case SMB_CACHE_STATE_READY:
404 chandle->ch_nops++;
405 break;
406
407 default:
408 assert(0);
409 }
410 (void) mutex_unlock(&chandle->ch_mtx);
411
412 /*
413 * Lock has to be taken outside the mutex otherwise
414 * there could be a deadlock
415 */
416 if (mode == SMB_CACHE_RDLOCK)
417 (void) rw_rdlock(&chandle->ch_cache_lck);
418 else
419 (void) rw_wrlock(&chandle->ch_cache_lck);
420
421 return (0);
422 }
423
424 /*
425 * Lock the cache for reading
426 */
427 static int
smb_cache_rdlock(smb_cache_t * chandle)428 smb_cache_rdlock(smb_cache_t *chandle)
429 {
430 return (smb_cache_lock(chandle, SMB_CACHE_RDLOCK));
431 }
432
433 /*
434 * Lock the cache for modification
435 */
436 static int
smb_cache_wrlock(smb_cache_t * chandle)437 smb_cache_wrlock(smb_cache_t *chandle)
438 {
439 return (smb_cache_lock(chandle, SMB_CACHE_WRLOCK));
440 }
441
442 /*
443 * Unlock the cache
444 */
445 static void
smb_cache_unlock(smb_cache_t * chandle)446 smb_cache_unlock(smb_cache_t *chandle)
447 {
448 (void) mutex_lock(&chandle->ch_mtx);
449 assert(chandle->ch_nops > 0);
450 chandle->ch_nops--;
451 (void) cond_broadcast(&chandle->ch_cv);
452 (void) mutex_unlock(&chandle->ch_mtx);
453
454 (void) rw_unlock(&chandle->ch_cache_lck);
455 }
456
457
458 /*
459 * Waits for ch_wait seconds if cache is in UPDATING state.
460 * Upon wake up returns true if cache is ready to be used,
461 * otherwise it returns false.
462 */
463 static boolean_t
smb_cache_wait(smb_cache_t * chandle)464 smb_cache_wait(smb_cache_t *chandle)
465 {
466 timestruc_t to;
467 int err;
468
469 if (chandle->ch_wait == 0)
470 return (B_TRUE);
471
472 to.tv_sec = chandle->ch_wait;
473 to.tv_nsec = 0;
474 while (chandle->ch_state == SMB_CACHE_STATE_REFRESHING) {
475 err = cond_reltimedwait(&chandle->ch_cv,
476 &chandle->ch_mtx, &to);
477 if (err == ETIME)
478 break;
479 }
480
481 return (chandle->ch_state == SMB_CACHE_STATE_READY);
482 }
483
484 /*
485 * Removes and frees all the cache entries
486 */
487 static void
smb_cache_destroy_nodes(smb_cache_t * chandle)488 smb_cache_destroy_nodes(smb_cache_t *chandle)
489 {
490 void *cookie = NULL;
491 smb_cache_node_t *cnode;
492 avl_tree_t *cache;
493
494 cache = &chandle->ch_cache;
495 while ((cnode = avl_destroy_nodes(cache, &cookie)) != NULL) {
496 if (chandle->ch_free)
497 chandle->ch_free(cnode->cn_data);
498 free(cnode);
499 }
500 }
501