1 /* $NetBSD: vfs_dirhash.c,v 1.9 2008/12/28 17:11:26 reinoud Exp $ */ 2 3 /* 4 * Copyright (c) 2008 Reinoud Zandijk 5 * All rights reserved. 6 * 7 * Redistribution and use in source and binary forms, with or without 8 * modification, are permitted provided that the following conditions 9 * are met: 10 * 1. Redistributions of source code must retain the above copyright 11 * notice, this list of conditions and the following disclaimer. 12 * 2. Redistributions in binary form must reproduce the above copyright 13 * notice, this list of conditions and the following disclaimer in the 14 * documentation and/or other materials provided with the distribution. 15 * 16 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 17 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 18 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 19 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 20 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 21 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 22 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 23 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 25 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 * 27 */ 28 29 30 #include <sys/cdefs.h> 31 __KERNEL_RCSID(0, "$NetBSD: vfs_dirhash.c,v 1.9 2008/12/28 17:11:26 reinoud Exp $"); 32 33 /* CLEAN UP! */ 34 #include <sys/param.h> 35 #include <sys/kernel.h> 36 #include <sys/buf.h> 37 #include <sys/dirent.h> 38 #include <sys/hash.h> 39 #include <sys/mutex.h> 40 #include <sys/pool.h> 41 #include <sys/queue.h> 42 #include <sys/vnode.h> 43 #include <sys/sysctl.h> 44 45 #include <sys/dirhash.h> 46 47 #if 1 48 # define DPRINTF(a) ; 49 #else 50 # define DPRINTF(a) printf a; 51 #endif 52 53 /* 54 * The locking protocol of the dirhash structures is fairly simple: 55 * 56 * The global dirhash_queue is protected by the dirhashmutex. This lock is 57 * internal only and is FS/mountpoint/vnode independent. On exit of the 58 * exported functions this mutex is not helt. 59 * 60 * The dirhash structure is considered part of the vnode/inode/udf_node 61 * structure and will thus use the lock that protects that vnode/inode. 62 * 63 * The dirhash entries are considered part of the dirhash structure and thus 64 * are on the same lock. 65 */ 66 67 static struct sysctllog *sysctl_log; 68 static struct pool dirhash_pool; 69 static struct pool dirhash_entry_pool; 70 71 static kmutex_t dirhashmutex; 72 static uint32_t maxdirhashsize = DIRHASH_SIZE; 73 static uint32_t dirhashsize = 0; 74 static TAILQ_HEAD(_dirhash, dirhash) dirhash_queue; 75 76 77 void 78 dirhash_init(void) 79 { 80 const struct sysctlnode *rnode, *cnode; 81 size_t sz; 82 uint32_t max_entries; 83 84 /* initialise dirhash queue */ 85 TAILQ_INIT(&dirhash_queue); 86 87 /* init dirhash pools */ 88 sz = sizeof(struct dirhash); 89 pool_init(&dirhash_pool, sz, 0, 0, 0, 90 "dirhpl", NULL, IPL_NONE); 91 92 sz = sizeof(struct dirhash_entry); 93 pool_init(&dirhash_entry_pool, sz, 0, 0, 0, 94 "dirhepl", NULL, IPL_NONE); 95 96 mutex_init(&dirhashmutex, MUTEX_DEFAULT, IPL_NONE); 97 max_entries = maxdirhashsize / sz; 98 pool_sethiwat(&dirhash_entry_pool, max_entries); 99 dirhashsize = 0; 100 101 /* create sysctl knobs and dials */ 102 sysctl_log = NULL; 103 sysctl_createv(&sysctl_log, 0, NULL, &rnode, 104 CTLFLAG_PERMANENT, 105 CTLTYPE_NODE, "dirhash", NULL, 106 NULL, 0, NULL, 0, 107 CTL_VFS, VFS_GENERIC, CTL_CREATE, CTL_EOL); 108 sysctl_createv(&sysctl_log, 0, &rnode, &cnode, 109 CTLFLAG_PERMANENT, 110 CTLTYPE_INT, "memused", 111 SYSCTL_DESCR("current dirhash memory usage"), 112 NULL, 0, &dirhashsize, 0, 113 CTL_CREATE, CTL_EOL); 114 sysctl_createv(&sysctl_log, 0, &rnode, &cnode, 115 CTLFLAG_PERMANENT | CTLFLAG_READWRITE, 116 CTLTYPE_INT, "maxmem", 117 SYSCTL_DESCR("maximum dirhash memory usage"), 118 NULL, 0, &maxdirhashsize, 0, 119 CTL_CREATE, CTL_EOL); 120 } 121 122 123 #if 0 124 void 125 dirhash_finish(void) 126 { 127 pool_destroy(&dirhash_pool); 128 pool_destroy(&dirhash_entry_pool); 129 130 mutex_destroy(&dirhashmutex); 131 132 /* sysctl_teardown(&sysctl_log); */ 133 } 134 #endif 135 136 137 /* 138 * generic dirhash implementation 139 */ 140 141 void 142 dirhash_purge_entries(struct dirhash *dirh) 143 { 144 struct dirhash_entry *dirh_e; 145 uint32_t hashline; 146 147 if (dirh == NULL) 148 return; 149 150 if (dirh->size == 0) 151 return; 152 153 for (hashline = 0; hashline < DIRHASH_HASHSIZE; hashline++) { 154 dirh_e = LIST_FIRST(&dirh->entries[hashline]); 155 while (dirh_e) { 156 LIST_REMOVE(dirh_e, next); 157 pool_put(&dirhash_entry_pool, dirh_e); 158 dirh_e = LIST_FIRST(&dirh->entries[hashline]); 159 } 160 } 161 dirh_e = LIST_FIRST(&dirh->free_entries); 162 163 while (dirh_e) { 164 LIST_REMOVE(dirh_e, next); 165 pool_put(&dirhash_entry_pool, dirh_e); 166 dirh_e = LIST_FIRST(&dirh->entries[hashline]); 167 } 168 169 dirh->flags &= ~DIRH_COMPLETE; 170 dirh->flags |= DIRH_PURGED; 171 172 dirhashsize -= dirh->size; 173 dirh->size = 0; 174 } 175 176 177 void 178 dirhash_purge(struct dirhash **dirhp) 179 { 180 struct dirhash *dirh = *dirhp; 181 182 if (dirh == NULL) 183 return; 184 185 /* purge its entries */ 186 dirhash_purge_entries(dirh); 187 188 /* recycle */ 189 mutex_enter(&dirhashmutex); 190 TAILQ_REMOVE(&dirhash_queue, dirh, next); 191 mutex_exit(&dirhashmutex); 192 193 pool_put(&dirhash_pool, dirh); 194 *dirhp = NULL; 195 } 196 197 198 void 199 dirhash_get(struct dirhash **dirhp) 200 { 201 struct dirhash *dirh; 202 uint32_t hashline; 203 204 /* if no dirhash was given, allocate one */ 205 dirh = *dirhp; 206 if (dirh == NULL) { 207 dirh = pool_get(&dirhash_pool, PR_WAITOK); 208 memset(dirh, 0, sizeof(struct dirhash)); 209 for (hashline = 0; hashline < DIRHASH_HASHSIZE; hashline++) { 210 LIST_INIT(&dirh->entries[hashline]); 211 } 212 } 213 214 /* implement LRU on the dirhash queue */ 215 mutex_enter(&dirhashmutex); 216 if (*dirhp) { 217 /* remove from queue to be requeued */ 218 TAILQ_REMOVE(&dirhash_queue, dirh, next); 219 } 220 dirh->refcnt++; 221 TAILQ_INSERT_HEAD(&dirhash_queue, dirh, next); 222 mutex_exit(&dirhashmutex); 223 224 *dirhp = dirh; 225 } 226 227 228 void 229 dirhash_put(struct dirhash *dirh) 230 { 231 232 mutex_enter(&dirhashmutex); 233 dirh->refcnt--; 234 mutex_exit(&dirhashmutex); 235 } 236 237 238 void 239 dirhash_enter(struct dirhash *dirh, 240 struct dirent *dirent, uint64_t offset, uint32_t entry_size, int new) 241 { 242 struct dirhash *del_dirh, *prev_dirh; 243 struct dirhash_entry *dirh_e; 244 uint32_t hashvalue, hashline; 245 int entrysize; 246 247 /* make sure we have a dirhash to work on */ 248 KASSERT(dirh); 249 KASSERT(dirh->refcnt > 0); 250 251 /* are we trying to re-enter an entry? */ 252 if (!new && (dirh->flags & DIRH_COMPLETE)) 253 return; 254 255 /* calculate our hash */ 256 hashvalue = hash32_strn(dirent->d_name, dirent->d_namlen, HASH32_STR_INIT); 257 hashline = hashvalue & DIRHASH_HASHMASK; 258 259 /* lookup and insert entry if not there yet */ 260 LIST_FOREACH(dirh_e, &dirh->entries[hashline], next) { 261 /* check for hash collision */ 262 if (dirh_e->hashvalue != hashvalue) 263 continue; 264 if (dirh_e->offset != offset) 265 continue; 266 /* got it already */ 267 KASSERT(dirh_e->d_namlen == dirent->d_namlen); 268 KASSERT(dirh_e->entry_size == entry_size); 269 return; 270 } 271 272 DPRINTF(("dirhash enter %"PRIu64", %d, %d for `%*.*s`\n", 273 offset, entry_size, dirent->d_namlen, 274 dirent->d_namlen, dirent->d_namlen, dirent->d_name)); 275 276 /* check if entry is in free space list */ 277 LIST_FOREACH(dirh_e, &dirh->free_entries, next) { 278 if (dirh_e->offset == offset) { 279 DPRINTF(("\tremoving free entry\n")); 280 LIST_REMOVE(dirh_e, next); 281 pool_put(&dirhash_entry_pool, dirh_e); 282 break; 283 } 284 } 285 286 /* ensure we are not passing the dirhash limit */ 287 entrysize = sizeof(struct dirhash_entry); 288 if (dirhashsize + entrysize > maxdirhashsize) { 289 /* we walk the dirhash_queue, so need to lock it */ 290 mutex_enter(&dirhashmutex); 291 del_dirh = TAILQ_LAST(&dirhash_queue, _dirhash); 292 KASSERT(del_dirh); 293 while (dirhashsize + entrysize > maxdirhashsize) { 294 /* no use trying to delete myself */ 295 if (del_dirh == dirh) 296 break; 297 prev_dirh = TAILQ_PREV(del_dirh, _dirhash, next); 298 if (del_dirh->refcnt == 0) 299 dirhash_purge_entries(del_dirh); 300 del_dirh = prev_dirh; 301 } 302 mutex_exit(&dirhashmutex); 303 } 304 305 /* add to the hashline */ 306 dirh_e = pool_get(&dirhash_entry_pool, PR_WAITOK); 307 memset(dirh_e, 0, sizeof(struct dirhash_entry)); 308 309 dirh_e->hashvalue = hashvalue; 310 dirh_e->offset = offset; 311 dirh_e->d_namlen = dirent->d_namlen; 312 dirh_e->entry_size = entry_size; 313 314 dirh->size += sizeof(struct dirhash_entry); 315 dirhashsize += sizeof(struct dirhash_entry); 316 LIST_INSERT_HEAD(&dirh->entries[hashline], dirh_e, next); 317 } 318 319 320 void 321 dirhash_enter_freed(struct dirhash *dirh, uint64_t offset, 322 uint32_t entry_size) 323 { 324 struct dirhash_entry *dirh_e; 325 326 /* make sure we have a dirhash to work on */ 327 KASSERT(dirh); 328 KASSERT(dirh->refcnt > 0); 329 330 /* check for double entry of free space */ 331 LIST_FOREACH(dirh_e, &dirh->free_entries, next) { 332 KASSERT(dirh_e->offset != offset); 333 } 334 335 DPRINTF(("dirhash enter FREED %"PRIu64", %d\n", 336 offset, entry_size)); 337 dirh_e = pool_get(&dirhash_entry_pool, PR_WAITOK); 338 memset(dirh_e, 0, sizeof(struct dirhash_entry)); 339 340 dirh_e->hashvalue = 0; /* not relevant */ 341 dirh_e->offset = offset; 342 dirh_e->d_namlen = 0; /* not relevant */ 343 dirh_e->entry_size = entry_size; 344 345 /* XXX it might be preferable to append them at the tail */ 346 LIST_INSERT_HEAD(&dirh->free_entries, dirh_e, next); 347 dirh->size += sizeof(struct dirhash_entry); 348 dirhashsize += sizeof(struct dirhash_entry); 349 } 350 351 352 void 353 dirhash_remove(struct dirhash *dirh, struct dirent *dirent, 354 uint64_t offset, uint32_t entry_size) 355 { 356 struct dirhash_entry *dirh_e; 357 uint32_t hashvalue, hashline; 358 359 DPRINTF(("dirhash remove %"PRIu64", %d for `%*.*s`\n", 360 offset, entry_size, 361 dirent->d_namlen, dirent->d_namlen, dirent->d_name)); 362 363 /* make sure we have a dirhash to work on */ 364 KASSERT(dirh); 365 KASSERT(dirh->refcnt > 0); 366 367 /* calculate our hash */ 368 hashvalue = hash32_strn(dirent->d_name, dirent->d_namlen, HASH32_STR_INIT); 369 hashline = hashvalue & DIRHASH_HASHMASK; 370 371 /* lookup entry */ 372 LIST_FOREACH(dirh_e, &dirh->entries[hashline], next) { 373 /* check for hash collision */ 374 if (dirh_e->hashvalue != hashvalue) 375 continue; 376 if (dirh_e->offset != offset) 377 continue; 378 379 /* got it! */ 380 KASSERT(dirh_e->d_namlen == dirent->d_namlen); 381 KASSERT(dirh_e->entry_size == entry_size); 382 LIST_REMOVE(dirh_e, next); 383 dirh->size -= sizeof(struct dirhash_entry); 384 dirhashsize -= sizeof(struct dirhash_entry); 385 386 dirhash_enter_freed(dirh, offset, entry_size); 387 return; 388 } 389 390 /* not found! */ 391 panic("dirhash_remove couldn't find entry in hash table\n"); 392 } 393 394 395 /* 396 * BUGALERT: don't use result longer than needed, never past the node lock. 397 * Call with NULL *result initially and it will return nonzero if again. 398 */ 399 int 400 dirhash_lookup(struct dirhash *dirh, const char *d_name, int d_namlen, 401 struct dirhash_entry **result) 402 { 403 struct dirhash_entry *dirh_e; 404 uint32_t hashvalue, hashline; 405 406 /* make sure we have a dirhash to work on */ 407 KASSERT(dirh); 408 KASSERT(dirh->refcnt > 0); 409 410 /* start where we were */ 411 if (*result) { 412 dirh_e = *result; 413 414 /* retrieve information to avoid recalculation and advance */ 415 hashvalue = dirh_e->hashvalue; 416 dirh_e = LIST_NEXT(*result, next); 417 } else { 418 /* calculate our hash and lookup all entries in hashline */ 419 hashvalue = hash32_strn(d_name, d_namlen, HASH32_STR_INIT); 420 hashline = hashvalue & DIRHASH_HASHMASK; 421 dirh_e = LIST_FIRST(&dirh->entries[hashline]); 422 } 423 424 for (; dirh_e; dirh_e = LIST_NEXT(dirh_e, next)) { 425 /* check for hash collision */ 426 if (dirh_e->hashvalue != hashvalue) 427 continue; 428 if (dirh_e->d_namlen != d_namlen) 429 continue; 430 /* might have an entry in the cache */ 431 *result = dirh_e; 432 return 1; 433 } 434 435 *result = NULL; 436 return 0; 437 } 438 439 440 /* 441 * BUGALERT: don't use result longer than needed, never past the node lock. 442 * Call with NULL *result initially and it will return nonzero if again. 443 */ 444 445 int 446 dirhash_lookup_freed(struct dirhash *dirh, uint32_t min_entrysize, 447 struct dirhash_entry **result) 448 { 449 struct dirhash_entry *dirh_e; 450 451 /* make sure we have a dirhash to work on */ 452 KASSERT(dirh); 453 KASSERT(dirh->refcnt > 0); 454 455 /* start where we were */ 456 if (*result) { 457 dirh_e = LIST_NEXT(*result, next); 458 } else { 459 /* lookup all entries that match */ 460 dirh_e = LIST_FIRST(&dirh->free_entries); 461 } 462 463 for (; dirh_e; dirh_e = LIST_NEXT(dirh_e, next)) { 464 /* check for minimum size */ 465 if (dirh_e->entry_size < min_entrysize) 466 continue; 467 /* might be a candidate */ 468 *result = dirh_e; 469 return 1; 470 } 471 472 *result = NULL; 473 return 0; 474 } 475