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