xref: /illumos-gate/usr/src/lib/smbsrv/libsmb/common/smb_cache.c (revision 269e59f9a28bf47e0f463e64fc5af4a408b73b21)
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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