1 1.19 simonb /* $NetBSD: ulfs_dirhash.c,v 1.19 2022/08/07 02:33:47 simonb Exp $ */ 2 1.17 dholland /* from NetBSD: ufs_dirhash.c,v 1.37 2014/12/20 00:28:05 christos Exp */ 3 1.1 dholland 4 1.1 dholland /* 5 1.1 dholland * Copyright (c) 2001, 2002 Ian Dowse. All rights reserved. 6 1.1 dholland * 7 1.1 dholland * Redistribution and use in source and binary forms, with or without 8 1.1 dholland * modification, are permitted provided that the following conditions 9 1.1 dholland * are met: 10 1.1 dholland * 1. Redistributions of source code must retain the above copyright 11 1.1 dholland * notice, this list of conditions and the following disclaimer. 12 1.1 dholland * 2. Redistributions in binary form must reproduce the above copyright 13 1.1 dholland * notice, this list of conditions and the following disclaimer in the 14 1.1 dholland * documentation and/or other materials provided with the distribution. 15 1.1 dholland * 16 1.1 dholland * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 17 1.1 dholland * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 1.1 dholland * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19 1.1 dholland * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 20 1.1 dholland * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 1.1 dholland * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 22 1.1 dholland * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 23 1.1 dholland * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 24 1.1 dholland * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 25 1.1 dholland * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 26 1.1 dholland * SUCH DAMAGE. 27 1.1 dholland * 28 1.1 dholland * $FreeBSD: src/sys/ufs/ufs/ufs_dirhash.c,v 1.3.2.8 2004/12/08 11:54:13 dwmalone Exp $ 29 1.1 dholland */ 30 1.1 dholland 31 1.1 dholland #include <sys/cdefs.h> 32 1.19 simonb __KERNEL_RCSID(0, "$NetBSD: ulfs_dirhash.c,v 1.19 2022/08/07 02:33:47 simonb Exp $"); 33 1.1 dholland 34 1.1 dholland /* 35 1.3 dholland * This implements a hash-based lookup scheme for ULFS directories. 36 1.1 dholland */ 37 1.1 dholland 38 1.1 dholland #include <sys/param.h> 39 1.1 dholland #include <sys/systm.h> 40 1.1 dholland #include <sys/kernel.h> 41 1.1 dholland #include <sys/kmem.h> 42 1.1 dholland #include <sys/types.h> 43 1.1 dholland #include <sys/hash.h> 44 1.1 dholland #include <sys/proc.h> 45 1.1 dholland #include <sys/buf.h> 46 1.1 dholland #include <sys/vnode.h> 47 1.1 dholland #include <sys/mount.h> 48 1.1 dholland #include <sys/pool.h> 49 1.1 dholland #include <sys/sysctl.h> 50 1.1 dholland #include <sys/atomic.h> 51 1.1 dholland 52 1.9 dholland #include <ufs/lfs/lfs.h> 53 1.9 dholland #include <ufs/lfs/lfs_accessors.h> 54 1.2 dholland #include <ufs/lfs/ulfs_inode.h> 55 1.2 dholland #include <ufs/lfs/ulfs_dirhash.h> 56 1.2 dholland #include <ufs/lfs/ulfsmount.h> 57 1.2 dholland #include <ufs/lfs/ulfs_bswap.h> 58 1.2 dholland #include <ufs/lfs/ulfs_extern.h> 59 1.1 dholland 60 1.19 simonb /* 61 1.19 simonb * Defaults for dirhash cache sizes: 62 1.19 simonb * - use up to 1/64th of system memory. 63 1.19 simonb * - disable dirhash (set the cache size to 0 bytes) if the 64 1.19 simonb * calculated value of hash is less than 2MB. 65 1.19 simonb * - cap maximum size of the dirhash cache at 32MB. 66 1.19 simonb */ 67 1.19 simonb #define DIRHASH_DEFAULT_DIVIDER 64 68 1.19 simonb #define MIN_DEFAULT_DIRHASH_MEM (2 * 1024 * 1024) 69 1.19 simonb #define MAX_DEFAULT_DIRHASH_MEM (32 * 1024 * 1024) 70 1.19 simonb 71 1.19 simonb 72 1.1 dholland #define WRAPINCR(val, limit) (((val) + 1 == (limit)) ? 0 : ((val) + 1)) 73 1.1 dholland #define WRAPDECR(val, limit) (((val) == 0) ? ((limit) - 1) : ((val) - 1)) 74 1.7 dholland #define OFSFMT(ip) ((ip)->i_lfs->um_maxsymlinklen <= 0) 75 1.1 dholland #define BLKFREE2IDX(n) ((n) > DH_NFSTATS ? DH_NFSTATS : (n)) 76 1.1 dholland 77 1.3 dholland static u_int ulfs_dirhashminblks = 5; 78 1.19 simonb static u_int ulfs_dirhashmaxmem = 0; 79 1.3 dholland static u_int ulfs_dirhashmem; 80 1.3 dholland static u_int ulfs_dirhashcheck = 0; 81 1.1 dholland 82 1.3 dholland static int ulfsdirhash_hash(struct dirhash *dh, const char *name, int namelen); 83 1.3 dholland static void ulfsdirhash_adjfree(struct dirhash *dh, doff_t offset, int diff, 84 1.1 dholland int dirblksiz); 85 1.3 dholland static void ulfsdirhash_delslot(struct dirhash *dh, int slot); 86 1.3 dholland static int ulfsdirhash_findslot(struct dirhash *dh, const char *name, 87 1.1 dholland int namelen, doff_t offset); 88 1.14 dholland static doff_t ulfsdirhash_getprev(struct lfs *fs, LFS_DIRHEADER *dp, 89 1.10 dholland doff_t offset, int dirblksiz); 90 1.3 dholland static int ulfsdirhash_recycle(int wanted); 91 1.1 dholland 92 1.3 dholland static pool_cache_t ulfsdirhashblk_cache; 93 1.3 dholland static pool_cache_t ulfsdirhash_cache; 94 1.1 dholland 95 1.3 dholland #define DIRHASHLIST_LOCK() mutex_enter(&ulfsdirhash_lock) 96 1.3 dholland #define DIRHASHLIST_UNLOCK() mutex_exit(&ulfsdirhash_lock) 97 1.1 dholland #define DIRHASH_LOCK(dh) mutex_enter(&(dh)->dh_lock) 98 1.1 dholland #define DIRHASH_UNLOCK(dh) mutex_exit(&(dh)->dh_lock) 99 1.1 dholland #define DIRHASH_BLKALLOC() \ 100 1.3 dholland pool_cache_get(ulfsdirhashblk_cache, PR_NOWAIT) 101 1.1 dholland #define DIRHASH_BLKFREE(ptr) \ 102 1.3 dholland pool_cache_put(ulfsdirhashblk_cache, ptr) 103 1.1 dholland 104 1.1 dholland /* Dirhash list; recently-used entries are near the tail. */ 105 1.3 dholland static TAILQ_HEAD(, dirhash) ulfsdirhash_list; 106 1.1 dholland 107 1.3 dholland /* Protects: ulfsdirhash_list, `dh_list' field, ulfs_dirhashmem. */ 108 1.3 dholland static kmutex_t ulfsdirhash_lock; 109 1.1 dholland 110 1.3 dholland static struct sysctllog *ulfsdirhash_sysctl_log; 111 1.1 dholland 112 1.1 dholland /* 113 1.1 dholland * Locking order: 114 1.3 dholland * ulfsdirhash_lock 115 1.1 dholland * dh_lock 116 1.1 dholland * 117 1.1 dholland * The dh_lock mutex should be acquired either via the inode lock, or via 118 1.3 dholland * ulfsdirhash_lock. Only the owner of the inode may free the associated 119 1.1 dholland * dirhash, but anything can steal its memory and set dh_hash to NULL. 120 1.1 dholland */ 121 1.1 dholland 122 1.1 dholland /* 123 1.1 dholland * Attempt to build up a hash table for the directory contents in 124 1.1 dholland * inode 'ip'. Returns 0 on success, or -1 of the operation failed. 125 1.1 dholland */ 126 1.1 dholland int 127 1.3 dholland ulfsdirhash_build(struct inode *ip) 128 1.1 dholland { 129 1.9 dholland struct lfs *fs = ip->i_lfs; 130 1.1 dholland struct dirhash *dh; 131 1.1 dholland struct buf *bp = NULL; 132 1.14 dholland LFS_DIRHEADER *ep; 133 1.1 dholland struct vnode *vp; 134 1.1 dholland doff_t bmask, pos; 135 1.1 dholland int dirblocks, i, j, memreqd, nblocks, narrays, nslots, slot; 136 1.7 dholland int dirblksiz = ip->i_lfs->um_dirblksiz; 137 1.1 dholland 138 1.1 dholland /* Check if we can/should use dirhash. */ 139 1.1 dholland if (ip->i_dirhash == NULL) { 140 1.19 simonb if (ulfs_dirhashmaxmem == 0 || 141 1.19 simonb ip->i_size < (ulfs_dirhashminblks * dirblksiz) || 142 1.19 simonb OFSFMT(ip)) 143 1.1 dholland return (-1); 144 1.1 dholland } else { 145 1.1 dholland /* Hash exists, but sysctls could have changed. */ 146 1.3 dholland if (ip->i_size < (ulfs_dirhashminblks * dirblksiz) || 147 1.3 dholland ulfs_dirhashmem > ulfs_dirhashmaxmem) { 148 1.3 dholland ulfsdirhash_free(ip); 149 1.1 dholland return (-1); 150 1.1 dholland } 151 1.1 dholland /* Check if hash exists and is intact (note: unlocked read). */ 152 1.1 dholland if (ip->i_dirhash->dh_hash != NULL) 153 1.1 dholland return (0); 154 1.1 dholland /* Free the old, recycled hash and build a new one. */ 155 1.3 dholland ulfsdirhash_free(ip); 156 1.1 dholland } 157 1.1 dholland 158 1.1 dholland /* Don't hash removed directories. */ 159 1.1 dholland if (ip->i_nlink == 0) 160 1.1 dholland return (-1); 161 1.1 dholland 162 1.1 dholland vp = ip->i_vnode; 163 1.1 dholland /* Allocate 50% more entries than this dir size could ever need. */ 164 1.1 dholland KASSERT(ip->i_size >= dirblksiz); 165 1.13 dholland nslots = ip->i_size / LFS_DIRECTSIZ(fs, 1); 166 1.1 dholland nslots = (nslots * 3 + 1) / 2; 167 1.1 dholland narrays = howmany(nslots, DH_NBLKOFF); 168 1.1 dholland nslots = narrays * DH_NBLKOFF; 169 1.1 dholland dirblocks = howmany(ip->i_size, dirblksiz); 170 1.1 dholland nblocks = (dirblocks * 3 + 1) / 2; 171 1.1 dholland 172 1.1 dholland memreqd = sizeof(*dh) + narrays * sizeof(*dh->dh_hash) + 173 1.1 dholland narrays * DH_NBLKOFF * sizeof(**dh->dh_hash) + 174 1.1 dholland nblocks * sizeof(*dh->dh_blkfree); 175 1.1 dholland 176 1.3 dholland while (atomic_add_int_nv(&ulfs_dirhashmem, memreqd) > 177 1.3 dholland ulfs_dirhashmaxmem) { 178 1.3 dholland atomic_add_int(&ulfs_dirhashmem, -memreqd); 179 1.3 dholland if (memreqd > ulfs_dirhashmaxmem / 2) 180 1.1 dholland return (-1); 181 1.1 dholland /* Try to free some space. */ 182 1.3 dholland if (ulfsdirhash_recycle(memreqd) != 0) 183 1.1 dholland return (-1); 184 1.1 dholland else 185 1.1 dholland DIRHASHLIST_UNLOCK(); 186 1.1 dholland } 187 1.1 dholland 188 1.1 dholland /* 189 1.1 dholland * Use non-blocking mallocs so that we will revert to a linear 190 1.1 dholland * lookup on failure rather than potentially blocking forever. 191 1.1 dholland */ 192 1.3 dholland dh = pool_cache_get(ulfsdirhash_cache, PR_NOWAIT); 193 1.1 dholland if (dh == NULL) { 194 1.3 dholland atomic_add_int(&ulfs_dirhashmem, -memreqd); 195 1.1 dholland return (-1); 196 1.1 dholland } 197 1.1 dholland memset(dh, 0, sizeof(*dh)); 198 1.1 dholland mutex_init(&dh->dh_lock, MUTEX_DEFAULT, IPL_NONE); 199 1.1 dholland DIRHASH_LOCK(dh); 200 1.1 dholland dh->dh_hashsz = narrays * sizeof(dh->dh_hash[0]); 201 1.1 dholland dh->dh_hash = kmem_zalloc(dh->dh_hashsz, KM_NOSLEEP); 202 1.1 dholland dh->dh_blkfreesz = nblocks * sizeof(dh->dh_blkfree[0]); 203 1.1 dholland dh->dh_blkfree = kmem_zalloc(dh->dh_blkfreesz, KM_NOSLEEP); 204 1.1 dholland if (dh->dh_hash == NULL || dh->dh_blkfree == NULL) 205 1.1 dholland goto fail; 206 1.1 dholland for (i = 0; i < narrays; i++) { 207 1.1 dholland if ((dh->dh_hash[i] = DIRHASH_BLKALLOC()) == NULL) 208 1.1 dholland goto fail; 209 1.1 dholland for (j = 0; j < DH_NBLKOFF; j++) 210 1.1 dholland dh->dh_hash[i][j] = DIRHASH_EMPTY; 211 1.1 dholland } 212 1.1 dholland 213 1.1 dholland /* Initialise the hash table and block statistics. */ 214 1.1 dholland dh->dh_narrays = narrays; 215 1.1 dholland dh->dh_hlen = nslots; 216 1.1 dholland dh->dh_nblk = nblocks; 217 1.1 dholland dh->dh_dirblks = dirblocks; 218 1.1 dholland for (i = 0; i < dirblocks; i++) 219 1.1 dholland dh->dh_blkfree[i] = dirblksiz / DIRALIGN; 220 1.1 dholland for (i = 0; i < DH_NFSTATS; i++) 221 1.1 dholland dh->dh_firstfree[i] = -1; 222 1.1 dholland dh->dh_firstfree[DH_NFSTATS] = 0; 223 1.1 dholland dh->dh_seqopt = 0; 224 1.1 dholland dh->dh_seqoff = 0; 225 1.1 dholland dh->dh_score = DH_SCOREINIT; 226 1.1 dholland ip->i_dirhash = dh; 227 1.1 dholland 228 1.3 dholland bmask = VFSTOULFS(vp->v_mount)->um_mountp->mnt_stat.f_iosize - 1; 229 1.1 dholland pos = 0; 230 1.1 dholland while (pos < ip->i_size) { 231 1.18 ad preempt_point(); 232 1.18 ad 233 1.1 dholland /* If necessary, get the next directory block. */ 234 1.1 dholland if ((pos & bmask) == 0) { 235 1.1 dholland if (bp != NULL) 236 1.1 dholland brelse(bp, 0); 237 1.3 dholland if (ulfs_blkatoff(vp, (off_t)pos, NULL, &bp, false) != 0) 238 1.1 dholland goto fail; 239 1.1 dholland } 240 1.1 dholland 241 1.1 dholland /* Add this entry to the hash. */ 242 1.14 dholland ep = (LFS_DIRHEADER *)((char *)bp->b_data + (pos & bmask)); 243 1.10 dholland if (lfs_dir_getreclen(fs, ep) == 0 || lfs_dir_getreclen(fs, ep) > 244 1.1 dholland dirblksiz - (pos & (dirblksiz - 1))) { 245 1.1 dholland /* Corrupted directory. */ 246 1.1 dholland brelse(bp, 0); 247 1.1 dholland goto fail; 248 1.1 dholland } 249 1.10 dholland if (lfs_dir_getino(fs, ep) != 0) { 250 1.3 dholland /* Add the entry (simplified ulfsdirhash_add). */ 251 1.11 dholland slot = ulfsdirhash_hash(dh, lfs_dir_nameptr(fs, ep), 252 1.9 dholland lfs_dir_getnamlen(fs, ep)); 253 1.1 dholland while (DH_ENTRY(dh, slot) != DIRHASH_EMPTY) 254 1.1 dholland slot = WRAPINCR(slot, dh->dh_hlen); 255 1.1 dholland dh->dh_hused++; 256 1.1 dholland DH_ENTRY(dh, slot) = pos; 257 1.9 dholland ulfsdirhash_adjfree(dh, pos, -LFS_DIRSIZ(fs, ep), 258 1.1 dholland dirblksiz); 259 1.1 dholland } 260 1.10 dholland pos += lfs_dir_getreclen(fs, ep); 261 1.1 dholland } 262 1.1 dholland 263 1.1 dholland if (bp != NULL) 264 1.1 dholland brelse(bp, 0); 265 1.1 dholland DIRHASHLIST_LOCK(); 266 1.3 dholland TAILQ_INSERT_TAIL(&ulfsdirhash_list, dh, dh_list); 267 1.1 dholland dh->dh_onlist = 1; 268 1.1 dholland DIRHASH_UNLOCK(dh); 269 1.1 dholland DIRHASHLIST_UNLOCK(); 270 1.1 dholland return (0); 271 1.1 dholland 272 1.1 dholland fail: 273 1.17 dholland ip->i_dirhash = NULL; 274 1.1 dholland DIRHASH_UNLOCK(dh); 275 1.1 dholland if (dh->dh_hash != NULL) { 276 1.1 dholland for (i = 0; i < narrays; i++) 277 1.1 dholland if (dh->dh_hash[i] != NULL) 278 1.1 dholland DIRHASH_BLKFREE(dh->dh_hash[i]); 279 1.1 dholland kmem_free(dh->dh_hash, dh->dh_hashsz); 280 1.1 dholland } 281 1.1 dholland if (dh->dh_blkfree != NULL) 282 1.1 dholland kmem_free(dh->dh_blkfree, dh->dh_blkfreesz); 283 1.1 dholland mutex_destroy(&dh->dh_lock); 284 1.3 dholland pool_cache_put(ulfsdirhash_cache, dh); 285 1.3 dholland atomic_add_int(&ulfs_dirhashmem, -memreqd); 286 1.1 dholland return (-1); 287 1.1 dholland } 288 1.1 dholland 289 1.1 dholland /* 290 1.1 dholland * Free any hash table associated with inode 'ip'. 291 1.1 dholland */ 292 1.1 dholland void 293 1.3 dholland ulfsdirhash_free(struct inode *ip) 294 1.1 dholland { 295 1.1 dholland struct dirhash *dh; 296 1.1 dholland int i, mem; 297 1.1 dholland 298 1.1 dholland if ((dh = ip->i_dirhash) == NULL) 299 1.1 dholland return; 300 1.1 dholland 301 1.17 dholland ip->i_dirhash = NULL; 302 1.17 dholland 303 1.1 dholland if (dh->dh_onlist) { 304 1.1 dholland DIRHASHLIST_LOCK(); 305 1.1 dholland if (dh->dh_onlist) 306 1.3 dholland TAILQ_REMOVE(&ulfsdirhash_list, dh, dh_list); 307 1.1 dholland DIRHASHLIST_UNLOCK(); 308 1.1 dholland } 309 1.1 dholland 310 1.1 dholland /* The dirhash pointed to by 'dh' is exclusively ours now. */ 311 1.1 dholland mem = sizeof(*dh); 312 1.1 dholland if (dh->dh_hash != NULL) { 313 1.1 dholland for (i = 0; i < dh->dh_narrays; i++) 314 1.1 dholland DIRHASH_BLKFREE(dh->dh_hash[i]); 315 1.1 dholland kmem_free(dh->dh_hash, dh->dh_hashsz); 316 1.1 dholland kmem_free(dh->dh_blkfree, dh->dh_blkfreesz); 317 1.1 dholland mem += dh->dh_hashsz; 318 1.1 dholland mem += dh->dh_narrays * DH_NBLKOFF * sizeof(**dh->dh_hash); 319 1.1 dholland mem += dh->dh_nblk * sizeof(*dh->dh_blkfree); 320 1.1 dholland } 321 1.1 dholland mutex_destroy(&dh->dh_lock); 322 1.3 dholland pool_cache_put(ulfsdirhash_cache, dh); 323 1.1 dholland 324 1.3 dholland atomic_add_int(&ulfs_dirhashmem, -mem); 325 1.1 dholland } 326 1.1 dholland 327 1.1 dholland /* 328 1.1 dholland * Find the offset of the specified name within the given inode. 329 1.1 dholland * Returns 0 on success, ENOENT if the entry does not exist, or 330 1.1 dholland * EJUSTRETURN if the caller should revert to a linear search. 331 1.1 dholland * 332 1.1 dholland * If successful, the directory offset is stored in *offp, and a 333 1.1 dholland * pointer to a struct buf containing the entry is stored in *bpp. If 334 1.1 dholland * prevoffp is non-NULL, the offset of the previous entry within 335 1.1 dholland * the DIRBLKSIZ-sized block is stored in *prevoffp (if the entry 336 1.1 dholland * is the first in a block, the start of the block is used). 337 1.1 dholland */ 338 1.1 dholland int 339 1.3 dholland ulfsdirhash_lookup(struct inode *ip, const char *name, int namelen, doff_t *offp, 340 1.1 dholland struct buf **bpp, doff_t *prevoffp) 341 1.1 dholland { 342 1.9 dholland struct lfs *fs = ip->i_lfs; 343 1.1 dholland struct dirhash *dh, *dh_next; 344 1.14 dholland LFS_DIRHEADER *dp; 345 1.1 dholland struct vnode *vp; 346 1.1 dholland struct buf *bp; 347 1.1 dholland doff_t blkoff, bmask, offset, prevoff; 348 1.1 dholland int i, slot; 349 1.7 dholland int dirblksiz = ip->i_lfs->um_dirblksiz; 350 1.1 dholland 351 1.1 dholland if ((dh = ip->i_dirhash) == NULL) 352 1.1 dholland return (EJUSTRETURN); 353 1.1 dholland 354 1.1 dholland /* 355 1.1 dholland * Move this dirhash towards the end of the list if it has a 356 1.1 dholland * score higher than the next entry, and acquire the dh_lock. 357 1.1 dholland * Optimise the case where it's already the last by performing 358 1.1 dholland * an unlocked read of the TAILQ_NEXT pointer. 359 1.1 dholland * 360 1.1 dholland * In both cases, end up holding just dh_lock. 361 1.1 dholland */ 362 1.1 dholland if (TAILQ_NEXT(dh, dh_list) != NULL) { 363 1.1 dholland DIRHASHLIST_LOCK(); 364 1.1 dholland DIRHASH_LOCK(dh); 365 1.1 dholland /* 366 1.1 dholland * If the new score will be greater than that of the next 367 1.1 dholland * entry, then move this entry past it. With both mutexes 368 1.1 dholland * held, dh_next won't go away, but its dh_score could 369 1.1 dholland * change; that's not important since it is just a hint. 370 1.1 dholland */ 371 1.1 dholland if (dh->dh_hash != NULL && 372 1.1 dholland (dh_next = TAILQ_NEXT(dh, dh_list)) != NULL && 373 1.1 dholland dh->dh_score >= dh_next->dh_score) { 374 1.1 dholland KASSERT(dh->dh_onlist); 375 1.3 dholland TAILQ_REMOVE(&ulfsdirhash_list, dh, dh_list); 376 1.3 dholland TAILQ_INSERT_AFTER(&ulfsdirhash_list, dh_next, dh, 377 1.1 dholland dh_list); 378 1.1 dholland } 379 1.1 dholland DIRHASHLIST_UNLOCK(); 380 1.1 dholland } else { 381 1.1 dholland /* Already the last, though that could change as we wait. */ 382 1.1 dholland DIRHASH_LOCK(dh); 383 1.1 dholland } 384 1.1 dholland if (dh->dh_hash == NULL) { 385 1.1 dholland DIRHASH_UNLOCK(dh); 386 1.3 dholland ulfsdirhash_free(ip); 387 1.1 dholland return (EJUSTRETURN); 388 1.1 dholland } 389 1.1 dholland 390 1.1 dholland /* Update the score. */ 391 1.1 dholland if (dh->dh_score < DH_SCOREMAX) 392 1.1 dholland dh->dh_score++; 393 1.1 dholland 394 1.1 dholland vp = ip->i_vnode; 395 1.3 dholland bmask = VFSTOULFS(vp->v_mount)->um_mountp->mnt_stat.f_iosize - 1; 396 1.1 dholland blkoff = -1; 397 1.1 dholland bp = NULL; 398 1.1 dholland restart: 399 1.3 dholland slot = ulfsdirhash_hash(dh, name, namelen); 400 1.1 dholland 401 1.1 dholland if (dh->dh_seqopt) { 402 1.1 dholland /* 403 1.1 dholland * Sequential access optimisation. dh_seqoff contains the 404 1.1 dholland * offset of the directory entry immediately following 405 1.1 dholland * the last entry that was looked up. Check if this offset 406 1.1 dholland * appears in the hash chain for the name we are looking for. 407 1.1 dholland */ 408 1.1 dholland for (i = slot; (offset = DH_ENTRY(dh, i)) != DIRHASH_EMPTY; 409 1.1 dholland i = WRAPINCR(i, dh->dh_hlen)) 410 1.1 dholland if (offset == dh->dh_seqoff) 411 1.1 dholland break; 412 1.1 dholland if (offset == dh->dh_seqoff) { 413 1.1 dholland /* 414 1.1 dholland * We found an entry with the expected offset. This 415 1.1 dholland * is probably the entry we want, but if not, the 416 1.1 dholland * code below will turn off seqoff and retry. 417 1.1 dholland */ 418 1.1 dholland slot = i; 419 1.1 dholland } else 420 1.1 dholland dh->dh_seqopt = 0; 421 1.1 dholland } 422 1.1 dholland 423 1.1 dholland for (; (offset = DH_ENTRY(dh, slot)) != DIRHASH_EMPTY; 424 1.1 dholland slot = WRAPINCR(slot, dh->dh_hlen)) { 425 1.1 dholland if (offset == DIRHASH_DEL) 426 1.1 dholland continue; 427 1.1 dholland 428 1.1 dholland if (offset < 0 || offset >= ip->i_size) 429 1.3 dholland panic("ulfsdirhash_lookup: bad offset in hash array"); 430 1.1 dholland if ((offset & ~bmask) != blkoff) { 431 1.1 dholland if (bp != NULL) 432 1.1 dholland brelse(bp, 0); 433 1.1 dholland blkoff = offset & ~bmask; 434 1.3 dholland if (ulfs_blkatoff(vp, (off_t)blkoff, 435 1.1 dholland NULL, &bp, false) != 0) { 436 1.1 dholland DIRHASH_UNLOCK(dh); 437 1.1 dholland return (EJUSTRETURN); 438 1.1 dholland } 439 1.1 dholland } 440 1.14 dholland dp = (LFS_DIRHEADER *)((char *)bp->b_data + (offset & bmask)); 441 1.10 dholland if (lfs_dir_getreclen(fs, dp) == 0 || lfs_dir_getreclen(fs, dp) > 442 1.1 dholland dirblksiz - (offset & (dirblksiz - 1))) { 443 1.1 dholland /* Corrupted directory. */ 444 1.1 dholland DIRHASH_UNLOCK(dh); 445 1.1 dholland brelse(bp, 0); 446 1.1 dholland return (EJUSTRETURN); 447 1.1 dholland } 448 1.9 dholland if (lfs_dir_getnamlen(fs, dp) == namelen && 449 1.11 dholland memcmp(lfs_dir_nameptr(fs, dp), name, namelen) == 0) { 450 1.1 dholland /* Found. Get the prev offset if needed. */ 451 1.1 dholland if (prevoffp != NULL) { 452 1.1 dholland if (offset & (dirblksiz - 1)) { 453 1.10 dholland prevoff = ulfsdirhash_getprev(fs, dp, 454 1.1 dholland offset, dirblksiz); 455 1.1 dholland if (prevoff == -1) { 456 1.1 dholland brelse(bp, 0); 457 1.1 dholland return (EJUSTRETURN); 458 1.1 dholland } 459 1.1 dholland } else 460 1.1 dholland prevoff = offset; 461 1.1 dholland *prevoffp = prevoff; 462 1.1 dholland } 463 1.1 dholland 464 1.1 dholland /* Check for sequential access, and update offset. */ 465 1.1 dholland if (dh->dh_seqopt == 0 && dh->dh_seqoff == offset) 466 1.1 dholland dh->dh_seqopt = 1; 467 1.9 dholland dh->dh_seqoff = offset + LFS_DIRSIZ(fs, dp); 468 1.1 dholland DIRHASH_UNLOCK(dh); 469 1.1 dholland 470 1.1 dholland *bpp = bp; 471 1.1 dholland *offp = offset; 472 1.1 dholland return (0); 473 1.1 dholland } 474 1.1 dholland 475 1.1 dholland if (dh->dh_hash == NULL) { 476 1.1 dholland DIRHASH_UNLOCK(dh); 477 1.1 dholland if (bp != NULL) 478 1.1 dholland brelse(bp, 0); 479 1.3 dholland ulfsdirhash_free(ip); 480 1.1 dholland return (EJUSTRETURN); 481 1.1 dholland } 482 1.1 dholland /* 483 1.1 dholland * When the name doesn't match in the seqopt case, go back 484 1.1 dholland * and search normally. 485 1.1 dholland */ 486 1.1 dholland if (dh->dh_seqopt) { 487 1.1 dholland dh->dh_seqopt = 0; 488 1.1 dholland goto restart; 489 1.1 dholland } 490 1.1 dholland } 491 1.1 dholland DIRHASH_UNLOCK(dh); 492 1.1 dholland if (bp != NULL) 493 1.1 dholland brelse(bp, 0); 494 1.1 dholland return (ENOENT); 495 1.1 dholland } 496 1.1 dholland 497 1.1 dholland /* 498 1.1 dholland * Find a directory block with room for 'slotneeded' bytes. Returns 499 1.1 dholland * the offset of the directory entry that begins the free space. 500 1.1 dholland * This will either be the offset of an existing entry that has free 501 1.1 dholland * space at the end, or the offset of an entry with d_ino == 0 at 502 1.1 dholland * the start of a DIRBLKSIZ block. 503 1.1 dholland * 504 1.1 dholland * To use the space, the caller may need to compact existing entries in 505 1.1 dholland * the directory. The total number of bytes in all of the entries involved 506 1.1 dholland * in the compaction is stored in *slotsize. In other words, all of 507 1.1 dholland * the entries that must be compacted are exactly contained in the 508 1.1 dholland * region beginning at the returned offset and spanning *slotsize bytes. 509 1.1 dholland * 510 1.1 dholland * Returns -1 if no space was found, indicating that the directory 511 1.1 dholland * must be extended. 512 1.1 dholland */ 513 1.1 dholland doff_t 514 1.3 dholland ulfsdirhash_findfree(struct inode *ip, int slotneeded, int *slotsize) 515 1.1 dholland { 516 1.9 dholland struct lfs *fs = ip->i_lfs; 517 1.14 dholland LFS_DIRHEADER *dp; 518 1.1 dholland struct dirhash *dh; 519 1.1 dholland struct buf *bp; 520 1.1 dholland doff_t pos, slotstart; 521 1.1 dholland int dirblock, error, freebytes, i; 522 1.7 dholland int dirblksiz = ip->i_lfs->um_dirblksiz; 523 1.1 dholland 524 1.1 dholland if ((dh = ip->i_dirhash) == NULL) 525 1.1 dholland return (-1); 526 1.1 dholland 527 1.1 dholland DIRHASH_LOCK(dh); 528 1.1 dholland if (dh->dh_hash == NULL) { 529 1.1 dholland DIRHASH_UNLOCK(dh); 530 1.3 dholland ulfsdirhash_free(ip); 531 1.1 dholland return (-1); 532 1.1 dholland } 533 1.1 dholland 534 1.1 dholland /* Find a directory block with the desired free space. */ 535 1.1 dholland dirblock = -1; 536 1.1 dholland for (i = howmany(slotneeded, DIRALIGN); i <= DH_NFSTATS; i++) 537 1.1 dholland if ((dirblock = dh->dh_firstfree[i]) != -1) 538 1.1 dholland break; 539 1.1 dholland if (dirblock == -1) { 540 1.1 dholland DIRHASH_UNLOCK(dh); 541 1.1 dholland return (-1); 542 1.1 dholland } 543 1.1 dholland 544 1.1 dholland KASSERT(dirblock < dh->dh_nblk && 545 1.1 dholland dh->dh_blkfree[dirblock] >= howmany(slotneeded, DIRALIGN)); 546 1.1 dholland pos = dirblock * dirblksiz; 547 1.3 dholland error = ulfs_blkatoff(ip->i_vnode, (off_t)pos, (void *)&dp, &bp, false); 548 1.1 dholland if (error) { 549 1.1 dholland DIRHASH_UNLOCK(dh); 550 1.1 dholland return (-1); 551 1.1 dholland } 552 1.1 dholland /* Find the first entry with free space. */ 553 1.1 dholland for (i = 0; i < dirblksiz; ) { 554 1.10 dholland if (lfs_dir_getreclen(fs, dp) == 0) { 555 1.1 dholland DIRHASH_UNLOCK(dh); 556 1.1 dholland brelse(bp, 0); 557 1.1 dholland return (-1); 558 1.1 dholland } 559 1.10 dholland if (lfs_dir_getino(fs, dp) == 0 || lfs_dir_getreclen(fs, dp) > LFS_DIRSIZ(fs, dp)) 560 1.1 dholland break; 561 1.10 dholland i += lfs_dir_getreclen(fs, dp); 562 1.10 dholland dp = LFS_NEXTDIR(fs, dp); 563 1.1 dholland } 564 1.1 dholland if (i > dirblksiz) { 565 1.1 dholland DIRHASH_UNLOCK(dh); 566 1.1 dholland brelse(bp, 0); 567 1.1 dholland return (-1); 568 1.1 dholland } 569 1.1 dholland slotstart = pos + i; 570 1.1 dholland 571 1.1 dholland /* Find the range of entries needed to get enough space */ 572 1.1 dholland freebytes = 0; 573 1.1 dholland while (i < dirblksiz && freebytes < slotneeded) { 574 1.10 dholland freebytes += lfs_dir_getreclen(fs, dp); 575 1.10 dholland if (lfs_dir_getino(fs, dp) != 0) 576 1.9 dholland freebytes -= LFS_DIRSIZ(fs, dp); 577 1.10 dholland if (lfs_dir_getreclen(fs, dp) == 0) { 578 1.1 dholland DIRHASH_UNLOCK(dh); 579 1.1 dholland brelse(bp, 0); 580 1.1 dholland return (-1); 581 1.1 dholland } 582 1.10 dholland i += lfs_dir_getreclen(fs, dp); 583 1.10 dholland dp = LFS_NEXTDIR(fs, dp); 584 1.1 dholland } 585 1.1 dholland if (i > dirblksiz) { 586 1.1 dholland DIRHASH_UNLOCK(dh); 587 1.1 dholland brelse(bp, 0); 588 1.1 dholland return (-1); 589 1.1 dholland } 590 1.1 dholland if (freebytes < slotneeded) 591 1.3 dholland panic("ulfsdirhash_findfree: free mismatch"); 592 1.1 dholland DIRHASH_UNLOCK(dh); 593 1.1 dholland brelse(bp, 0); 594 1.1 dholland *slotsize = pos + i - slotstart; 595 1.1 dholland return (slotstart); 596 1.1 dholland } 597 1.1 dholland 598 1.1 dholland /* 599 1.1 dholland * Return the start of the unused space at the end of a directory, or 600 1.1 dholland * -1 if there are no trailing unused blocks. 601 1.1 dholland */ 602 1.1 dholland doff_t 603 1.3 dholland ulfsdirhash_enduseful(struct inode *ip) 604 1.1 dholland { 605 1.1 dholland struct dirhash *dh; 606 1.1 dholland int i; 607 1.7 dholland int dirblksiz = ip->i_lfs->um_dirblksiz; 608 1.1 dholland 609 1.1 dholland if ((dh = ip->i_dirhash) == NULL) 610 1.1 dholland return (-1); 611 1.1 dholland 612 1.1 dholland DIRHASH_LOCK(dh); 613 1.1 dholland if (dh->dh_hash == NULL) { 614 1.1 dholland DIRHASH_UNLOCK(dh); 615 1.3 dholland ulfsdirhash_free(ip); 616 1.1 dholland return (-1); 617 1.1 dholland } 618 1.1 dholland 619 1.1 dholland if (dh->dh_blkfree[dh->dh_dirblks - 1] != dirblksiz / DIRALIGN) { 620 1.1 dholland DIRHASH_UNLOCK(dh); 621 1.1 dholland return (-1); 622 1.1 dholland } 623 1.1 dholland 624 1.1 dholland for (i = dh->dh_dirblks - 1; i >= 0; i--) 625 1.1 dholland if (dh->dh_blkfree[i] != dirblksiz / DIRALIGN) 626 1.1 dholland break; 627 1.1 dholland DIRHASH_UNLOCK(dh); 628 1.1 dholland return ((doff_t)(i + 1) * dirblksiz); 629 1.1 dholland } 630 1.1 dholland 631 1.1 dholland /* 632 1.1 dholland * Insert information into the hash about a new directory entry. dirp 633 1.4 dholland * points to a struct lfs_direct containing the entry, and offset specifies 634 1.1 dholland * the offset of this entry. 635 1.1 dholland */ 636 1.1 dholland void 637 1.14 dholland ulfsdirhash_add(struct inode *ip, LFS_DIRHEADER *dirp, doff_t offset) 638 1.1 dholland { 639 1.9 dholland struct lfs *fs = ip->i_lfs; 640 1.1 dholland struct dirhash *dh; 641 1.1 dholland int slot; 642 1.7 dholland int dirblksiz = ip->i_lfs->um_dirblksiz; 643 1.1 dholland 644 1.1 dholland if ((dh = ip->i_dirhash) == NULL) 645 1.1 dholland return; 646 1.1 dholland 647 1.1 dholland DIRHASH_LOCK(dh); 648 1.1 dholland if (dh->dh_hash == NULL) { 649 1.1 dholland DIRHASH_UNLOCK(dh); 650 1.3 dholland ulfsdirhash_free(ip); 651 1.1 dholland return; 652 1.1 dholland } 653 1.1 dholland 654 1.1 dholland KASSERT(offset < dh->dh_dirblks * dirblksiz); 655 1.1 dholland /* 656 1.1 dholland * Normal hash usage is < 66%. If the usage gets too high then 657 1.1 dholland * remove the hash entirely and let it be rebuilt later. 658 1.1 dholland */ 659 1.1 dholland if (dh->dh_hused >= (dh->dh_hlen * 3) / 4) { 660 1.1 dholland DIRHASH_UNLOCK(dh); 661 1.3 dholland ulfsdirhash_free(ip); 662 1.1 dholland return; 663 1.1 dholland } 664 1.1 dholland 665 1.1 dholland /* Find a free hash slot (empty or deleted), and add the entry. */ 666 1.11 dholland slot = ulfsdirhash_hash(dh, lfs_dir_nameptr(fs, dirp), 667 1.11 dholland lfs_dir_getnamlen(fs, dirp)); 668 1.1 dholland while (DH_ENTRY(dh, slot) >= 0) 669 1.1 dholland slot = WRAPINCR(slot, dh->dh_hlen); 670 1.1 dholland if (DH_ENTRY(dh, slot) == DIRHASH_EMPTY) 671 1.1 dholland dh->dh_hused++; 672 1.1 dholland DH_ENTRY(dh, slot) = offset; 673 1.1 dholland 674 1.1 dholland /* Update the per-block summary info. */ 675 1.9 dholland ulfsdirhash_adjfree(dh, offset, -LFS_DIRSIZ(fs, dirp), dirblksiz); 676 1.1 dholland DIRHASH_UNLOCK(dh); 677 1.1 dholland } 678 1.1 dholland 679 1.1 dholland /* 680 1.1 dholland * Remove the specified directory entry from the hash. The entry to remove 681 1.1 dholland * is defined by the name in `dirp', which must exist at the specified 682 1.1 dholland * `offset' within the directory. 683 1.1 dholland */ 684 1.1 dholland void 685 1.14 dholland ulfsdirhash_remove(struct inode *ip, LFS_DIRHEADER *dirp, doff_t offset) 686 1.1 dholland { 687 1.9 dholland struct lfs *fs = ip->i_lfs; 688 1.1 dholland struct dirhash *dh; 689 1.1 dholland int slot; 690 1.7 dholland int dirblksiz = ip->i_lfs->um_dirblksiz; 691 1.1 dholland 692 1.1 dholland if ((dh = ip->i_dirhash) == NULL) 693 1.1 dholland return; 694 1.1 dholland 695 1.1 dholland DIRHASH_LOCK(dh); 696 1.1 dholland if (dh->dh_hash == NULL) { 697 1.1 dholland DIRHASH_UNLOCK(dh); 698 1.3 dholland ulfsdirhash_free(ip); 699 1.1 dholland return; 700 1.1 dholland } 701 1.1 dholland 702 1.1 dholland KASSERT(offset < dh->dh_dirblks * dirblksiz); 703 1.1 dholland /* Find the entry */ 704 1.11 dholland slot = ulfsdirhash_findslot(dh, lfs_dir_nameptr(fs, dirp), 705 1.9 dholland lfs_dir_getnamlen(fs, dirp), offset); 706 1.1 dholland 707 1.1 dholland /* Remove the hash entry. */ 708 1.3 dholland ulfsdirhash_delslot(dh, slot); 709 1.1 dholland 710 1.1 dholland /* Update the per-block summary info. */ 711 1.9 dholland ulfsdirhash_adjfree(dh, offset, LFS_DIRSIZ(fs, dirp), dirblksiz); 712 1.1 dholland DIRHASH_UNLOCK(dh); 713 1.1 dholland } 714 1.1 dholland 715 1.1 dholland /* 716 1.1 dholland * Change the offset associated with a directory entry in the hash. Used 717 1.1 dholland * when compacting directory blocks. 718 1.1 dholland */ 719 1.1 dholland void 720 1.14 dholland ulfsdirhash_move(struct inode *ip, LFS_DIRHEADER *dirp, doff_t oldoff, 721 1.1 dholland doff_t newoff) 722 1.1 dholland { 723 1.9 dholland struct lfs *fs = ip->i_lfs; 724 1.1 dholland struct dirhash *dh; 725 1.1 dholland int slot; 726 1.1 dholland 727 1.1 dholland if ((dh = ip->i_dirhash) == NULL) 728 1.1 dholland return; 729 1.1 dholland DIRHASH_LOCK(dh); 730 1.1 dholland if (dh->dh_hash == NULL) { 731 1.1 dholland DIRHASH_UNLOCK(dh); 732 1.3 dholland ulfsdirhash_free(ip); 733 1.1 dholland return; 734 1.1 dholland } 735 1.1 dholland 736 1.7 dholland KASSERT(oldoff < dh->dh_dirblks * ip->i_lfs->um_dirblksiz && 737 1.7 dholland newoff < dh->dh_dirblks * ip->i_lfs->um_dirblksiz); 738 1.1 dholland /* Find the entry, and update the offset. */ 739 1.11 dholland slot = ulfsdirhash_findslot(dh, lfs_dir_nameptr(fs, dirp), 740 1.9 dholland lfs_dir_getnamlen(fs, dirp), oldoff); 741 1.1 dholland DH_ENTRY(dh, slot) = newoff; 742 1.1 dholland DIRHASH_UNLOCK(dh); 743 1.1 dholland } 744 1.1 dholland 745 1.1 dholland /* 746 1.1 dholland * Inform dirhash that the directory has grown by one block that 747 1.1 dholland * begins at offset (i.e. the new length is offset + DIRBLKSIZ). 748 1.1 dholland */ 749 1.1 dholland void 750 1.3 dholland ulfsdirhash_newblk(struct inode *ip, doff_t offset) 751 1.1 dholland { 752 1.1 dholland struct dirhash *dh; 753 1.1 dholland int block; 754 1.7 dholland int dirblksiz = ip->i_lfs->um_dirblksiz; 755 1.1 dholland 756 1.1 dholland if ((dh = ip->i_dirhash) == NULL) 757 1.1 dholland return; 758 1.1 dholland DIRHASH_LOCK(dh); 759 1.1 dholland if (dh->dh_hash == NULL) { 760 1.1 dholland DIRHASH_UNLOCK(dh); 761 1.3 dholland ulfsdirhash_free(ip); 762 1.1 dholland return; 763 1.1 dholland } 764 1.1 dholland 765 1.1 dholland KASSERT(offset == dh->dh_dirblks * dirblksiz); 766 1.1 dholland block = offset / dirblksiz; 767 1.1 dholland if (block >= dh->dh_nblk) { 768 1.1 dholland /* Out of space; must rebuild. */ 769 1.1 dholland DIRHASH_UNLOCK(dh); 770 1.3 dholland ulfsdirhash_free(ip); 771 1.1 dholland return; 772 1.1 dholland } 773 1.1 dholland dh->dh_dirblks = block + 1; 774 1.1 dholland 775 1.1 dholland /* Account for the new free block. */ 776 1.1 dholland dh->dh_blkfree[block] = dirblksiz / DIRALIGN; 777 1.1 dholland if (dh->dh_firstfree[DH_NFSTATS] == -1) 778 1.1 dholland dh->dh_firstfree[DH_NFSTATS] = block; 779 1.1 dholland DIRHASH_UNLOCK(dh); 780 1.1 dholland } 781 1.1 dholland 782 1.1 dholland /* 783 1.1 dholland * Inform dirhash that the directory is being truncated. 784 1.1 dholland */ 785 1.1 dholland void 786 1.3 dholland ulfsdirhash_dirtrunc(struct inode *ip, doff_t offset) 787 1.1 dholland { 788 1.1 dholland struct dirhash *dh; 789 1.1 dholland int block, i; 790 1.7 dholland int dirblksiz = ip->i_lfs->um_dirblksiz; 791 1.1 dholland 792 1.1 dholland if ((dh = ip->i_dirhash) == NULL) 793 1.1 dholland return; 794 1.1 dholland 795 1.1 dholland DIRHASH_LOCK(dh); 796 1.1 dholland if (dh->dh_hash == NULL) { 797 1.1 dholland DIRHASH_UNLOCK(dh); 798 1.3 dholland ulfsdirhash_free(ip); 799 1.1 dholland return; 800 1.1 dholland } 801 1.1 dholland 802 1.1 dholland KASSERT(offset <= dh->dh_dirblks * dirblksiz); 803 1.1 dholland block = howmany(offset, dirblksiz); 804 1.1 dholland /* 805 1.1 dholland * If the directory shrinks to less than 1/8 of dh_nblk blocks 806 1.1 dholland * (about 20% of its original size due to the 50% extra added in 807 1.3 dholland * ulfsdirhash_build) then free it, and let the caller rebuild 808 1.1 dholland * if necessary. 809 1.1 dholland */ 810 1.1 dholland if (block < dh->dh_nblk / 8 && dh->dh_narrays > 1) { 811 1.1 dholland DIRHASH_UNLOCK(dh); 812 1.3 dholland ulfsdirhash_free(ip); 813 1.1 dholland return; 814 1.1 dholland } 815 1.1 dholland 816 1.1 dholland /* 817 1.1 dholland * Remove any `first free' information pertaining to the 818 1.1 dholland * truncated blocks. All blocks we're removing should be 819 1.1 dholland * completely unused. 820 1.1 dholland */ 821 1.1 dholland if (dh->dh_firstfree[DH_NFSTATS] >= block) 822 1.1 dholland dh->dh_firstfree[DH_NFSTATS] = -1; 823 1.1 dholland for (i = block; i < dh->dh_dirblks; i++) 824 1.1 dholland if (dh->dh_blkfree[i] != dirblksiz / DIRALIGN) 825 1.3 dholland panic("ulfsdirhash_dirtrunc: blocks in use"); 826 1.1 dholland for (i = 0; i < DH_NFSTATS; i++) 827 1.1 dholland if (dh->dh_firstfree[i] >= block) 828 1.3 dholland panic("ulfsdirhash_dirtrunc: first free corrupt"); 829 1.1 dholland dh->dh_dirblks = block; 830 1.1 dholland DIRHASH_UNLOCK(dh); 831 1.1 dholland } 832 1.1 dholland 833 1.1 dholland /* 834 1.1 dholland * Debugging function to check that the dirhash information about 835 1.1 dholland * a directory block matches its actual contents. Panics if a mismatch 836 1.1 dholland * is detected. 837 1.1 dholland * 838 1.1 dholland * On entry, `sbuf' should point to the start of an in-core 839 1.1 dholland * DIRBLKSIZ-sized directory block, and `offset' should contain the 840 1.1 dholland * offset from the start of the directory of that block. 841 1.1 dholland */ 842 1.1 dholland void 843 1.3 dholland ulfsdirhash_checkblock(struct inode *ip, char *sbuf, doff_t offset) 844 1.1 dholland { 845 1.9 dholland struct lfs *fs = ip->i_lfs; 846 1.1 dholland struct dirhash *dh; 847 1.14 dholland LFS_DIRHEADER *dp; 848 1.1 dholland int block, ffslot, i, nfree; 849 1.7 dholland int dirblksiz = ip->i_lfs->um_dirblksiz; 850 1.1 dholland 851 1.3 dholland if (!ulfs_dirhashcheck) 852 1.1 dholland return; 853 1.1 dholland if ((dh = ip->i_dirhash) == NULL) 854 1.1 dholland return; 855 1.1 dholland 856 1.1 dholland DIRHASH_LOCK(dh); 857 1.1 dholland if (dh->dh_hash == NULL) { 858 1.1 dholland DIRHASH_UNLOCK(dh); 859 1.3 dholland ulfsdirhash_free(ip); 860 1.1 dholland return; 861 1.1 dholland } 862 1.1 dholland 863 1.1 dholland block = offset / dirblksiz; 864 1.1 dholland if ((offset & (dirblksiz - 1)) != 0 || block >= dh->dh_dirblks) 865 1.3 dholland panic("ulfsdirhash_checkblock: bad offset"); 866 1.1 dholland 867 1.1 dholland nfree = 0; 868 1.10 dholland for (i = 0; i < dirblksiz; i += lfs_dir_getreclen(fs, dp)) { 869 1.14 dholland dp = (LFS_DIRHEADER *)(sbuf + i); 870 1.10 dholland if (lfs_dir_getreclen(fs, dp) == 0 || i + lfs_dir_getreclen(fs, dp) > dirblksiz) 871 1.3 dholland panic("ulfsdirhash_checkblock: bad dir"); 872 1.1 dholland 873 1.10 dholland if (lfs_dir_getino(fs, dp) == 0) { 874 1.1 dholland #if 0 875 1.1 dholland /* 876 1.1 dholland * XXX entries with d_ino == 0 should only occur 877 1.1 dholland * at the start of a DIRBLKSIZ block. However the 878 1.3 dholland * ulfs code is tolerant of such entries at other 879 1.1 dholland * offsets, and fsck does not fix them. 880 1.1 dholland */ 881 1.1 dholland if (i != 0) 882 1.3 dholland panic("ulfsdirhash_checkblock: bad dir inode"); 883 1.1 dholland #endif 884 1.10 dholland nfree += lfs_dir_getreclen(fs, dp); 885 1.1 dholland continue; 886 1.1 dholland } 887 1.1 dholland 888 1.1 dholland /* Check that the entry exists (will panic if it doesn't). */ 889 1.11 dholland ulfsdirhash_findslot(dh, lfs_dir_nameptr(fs, dp), 890 1.11 dholland lfs_dir_getnamlen(fs, dp), 891 1.9 dholland offset + i); 892 1.1 dholland 893 1.10 dholland nfree += lfs_dir_getreclen(fs, dp) - LFS_DIRSIZ(fs, dp); 894 1.1 dholland } 895 1.1 dholland if (i != dirblksiz) 896 1.3 dholland panic("ulfsdirhash_checkblock: bad dir end"); 897 1.1 dholland 898 1.1 dholland if (dh->dh_blkfree[block] * DIRALIGN != nfree) 899 1.3 dholland panic("ulfsdirhash_checkblock: bad free count"); 900 1.1 dholland 901 1.1 dholland ffslot = BLKFREE2IDX(nfree / DIRALIGN); 902 1.1 dholland for (i = 0; i <= DH_NFSTATS; i++) 903 1.1 dholland if (dh->dh_firstfree[i] == block && i != ffslot) 904 1.3 dholland panic("ulfsdirhash_checkblock: bad first-free"); 905 1.1 dholland if (dh->dh_firstfree[ffslot] == -1) 906 1.3 dholland panic("ulfsdirhash_checkblock: missing first-free entry"); 907 1.1 dholland DIRHASH_UNLOCK(dh); 908 1.1 dholland } 909 1.1 dholland 910 1.1 dholland /* 911 1.1 dholland * Hash the specified filename into a dirhash slot. 912 1.1 dholland */ 913 1.1 dholland static int 914 1.3 dholland ulfsdirhash_hash(struct dirhash *dh, const char *name, int namelen) 915 1.1 dholland { 916 1.1 dholland u_int32_t hash; 917 1.1 dholland 918 1.1 dholland /* 919 1.1 dholland * We hash the name and then some other bit of data that is 920 1.1 dholland * invariant over the dirhash's lifetime. Otherwise names 921 1.1 dholland * differing only in the last byte are placed close to one 922 1.1 dholland * another in the table, which is bad for linear probing. 923 1.1 dholland */ 924 1.1 dholland hash = hash32_buf(name, namelen, HASH32_BUF_INIT); 925 1.1 dholland hash = hash32_buf(&dh, sizeof(dh), hash); 926 1.1 dholland return (hash % dh->dh_hlen); 927 1.1 dholland } 928 1.1 dholland 929 1.1 dholland /* 930 1.1 dholland * Adjust the number of free bytes in the block containing `offset' 931 1.1 dholland * by the value specified by `diff'. 932 1.1 dholland * 933 1.1 dholland * The caller must ensure we have exclusive access to `dh'; normally 934 1.1 dholland * that means that dh_lock should be held, but this is also called 935 1.3 dholland * from ulfsdirhash_build() where exclusive access can be assumed. 936 1.1 dholland */ 937 1.1 dholland static void 938 1.3 dholland ulfsdirhash_adjfree(struct dirhash *dh, doff_t offset, int diff, int dirblksiz) 939 1.1 dholland { 940 1.1 dholland int block, i, nfidx, ofidx; 941 1.1 dholland 942 1.1 dholland KASSERT(mutex_owned(&dh->dh_lock)); 943 1.1 dholland 944 1.1 dholland /* Update the per-block summary info. */ 945 1.1 dholland block = offset / dirblksiz; 946 1.1 dholland KASSERT(block < dh->dh_nblk && block < dh->dh_dirblks); 947 1.1 dholland ofidx = BLKFREE2IDX(dh->dh_blkfree[block]); 948 1.1 dholland dh->dh_blkfree[block] = (int)dh->dh_blkfree[block] + (diff / DIRALIGN); 949 1.1 dholland nfidx = BLKFREE2IDX(dh->dh_blkfree[block]); 950 1.1 dholland 951 1.1 dholland /* Update the `first free' list if necessary. */ 952 1.1 dholland if (ofidx != nfidx) { 953 1.1 dholland /* If removing, scan forward for the next block. */ 954 1.1 dholland if (dh->dh_firstfree[ofidx] == block) { 955 1.1 dholland for (i = block + 1; i < dh->dh_dirblks; i++) 956 1.1 dholland if (BLKFREE2IDX(dh->dh_blkfree[i]) == ofidx) 957 1.1 dholland break; 958 1.1 dholland dh->dh_firstfree[ofidx] = (i < dh->dh_dirblks) ? i : -1; 959 1.1 dholland } 960 1.1 dholland 961 1.1 dholland /* Make this the new `first free' if necessary */ 962 1.1 dholland if (dh->dh_firstfree[nfidx] > block || 963 1.1 dholland dh->dh_firstfree[nfidx] == -1) 964 1.1 dholland dh->dh_firstfree[nfidx] = block; 965 1.1 dholland } 966 1.1 dholland } 967 1.1 dholland 968 1.1 dholland /* 969 1.1 dholland * Find the specified name which should have the specified offset. 970 1.1 dholland * Returns a slot number, and panics on failure. 971 1.1 dholland * 972 1.1 dholland * `dh' must be locked on entry and remains so on return. 973 1.1 dholland */ 974 1.1 dholland static int 975 1.3 dholland ulfsdirhash_findslot(struct dirhash *dh, const char *name, int namelen, 976 1.1 dholland doff_t offset) 977 1.1 dholland { 978 1.1 dholland int slot; 979 1.1 dholland 980 1.1 dholland KASSERT(mutex_owned(&dh->dh_lock)); 981 1.1 dholland 982 1.1 dholland /* Find the entry. */ 983 1.1 dholland KASSERT(dh->dh_hused < dh->dh_hlen); 984 1.3 dholland slot = ulfsdirhash_hash(dh, name, namelen); 985 1.1 dholland while (DH_ENTRY(dh, slot) != offset && 986 1.1 dholland DH_ENTRY(dh, slot) != DIRHASH_EMPTY) 987 1.1 dholland slot = WRAPINCR(slot, dh->dh_hlen); 988 1.1 dholland if (DH_ENTRY(dh, slot) != offset) 989 1.3 dholland panic("ulfsdirhash_findslot: '%.*s' not found", namelen, name); 990 1.1 dholland 991 1.1 dholland return (slot); 992 1.1 dholland } 993 1.1 dholland 994 1.1 dholland /* 995 1.1 dholland * Remove the entry corresponding to the specified slot from the hash array. 996 1.1 dholland * 997 1.1 dholland * `dh' must be locked on entry and remains so on return. 998 1.1 dholland */ 999 1.1 dholland static void 1000 1.3 dholland ulfsdirhash_delslot(struct dirhash *dh, int slot) 1001 1.1 dholland { 1002 1.1 dholland int i; 1003 1.1 dholland 1004 1.1 dholland KASSERT(mutex_owned(&dh->dh_lock)); 1005 1.1 dholland 1006 1.1 dholland /* Mark the entry as deleted. */ 1007 1.1 dholland DH_ENTRY(dh, slot) = DIRHASH_DEL; 1008 1.1 dholland 1009 1.1 dholland /* If this is the end of a chain of DIRHASH_DEL slots, remove them. */ 1010 1.1 dholland for (i = slot; DH_ENTRY(dh, i) == DIRHASH_DEL; ) 1011 1.1 dholland i = WRAPINCR(i, dh->dh_hlen); 1012 1.1 dholland if (DH_ENTRY(dh, i) == DIRHASH_EMPTY) { 1013 1.1 dholland i = WRAPDECR(i, dh->dh_hlen); 1014 1.1 dholland while (DH_ENTRY(dh, i) == DIRHASH_DEL) { 1015 1.1 dholland DH_ENTRY(dh, i) = DIRHASH_EMPTY; 1016 1.1 dholland dh->dh_hused--; 1017 1.1 dholland i = WRAPDECR(i, dh->dh_hlen); 1018 1.1 dholland } 1019 1.1 dholland KASSERT(dh->dh_hused >= 0); 1020 1.1 dholland } 1021 1.1 dholland } 1022 1.1 dholland 1023 1.1 dholland /* 1024 1.1 dholland * Given a directory entry and its offset, find the offset of the 1025 1.1 dholland * previous entry in the same DIRBLKSIZ-sized block. Returns an 1026 1.1 dholland * offset, or -1 if there is no previous entry in the block or some 1027 1.1 dholland * other problem occurred. 1028 1.1 dholland */ 1029 1.1 dholland static doff_t 1030 1.14 dholland ulfsdirhash_getprev(struct lfs *fs, LFS_DIRHEADER *dirp, 1031 1.10 dholland doff_t offset, int dirblksiz) 1032 1.1 dholland { 1033 1.14 dholland LFS_DIRHEADER *dp; 1034 1.1 dholland char *blkbuf; 1035 1.1 dholland doff_t blkoff, prevoff; 1036 1.1 dholland int entrypos, i; 1037 1.10 dholland unsigned reclen; 1038 1.1 dholland 1039 1.1 dholland blkoff = offset & ~(dirblksiz - 1); /* offset of start of block */ 1040 1.1 dholland entrypos = offset & (dirblksiz - 1); /* entry relative to block */ 1041 1.1 dholland blkbuf = (char *)dirp - entrypos; 1042 1.1 dholland prevoff = blkoff; 1043 1.1 dholland 1044 1.1 dholland /* If `offset' is the start of a block, there is no previous entry. */ 1045 1.1 dholland if (entrypos == 0) 1046 1.1 dholland return (-1); 1047 1.1 dholland 1048 1.1 dholland /* Scan from the start of the block until we get to the entry. */ 1049 1.10 dholland for (i = 0; i < entrypos; i += reclen) { 1050 1.14 dholland dp = (LFS_DIRHEADER *)(blkbuf + i); 1051 1.10 dholland reclen = lfs_dir_getreclen(fs, dp); 1052 1.10 dholland if (reclen == 0 || i + reclen > entrypos) 1053 1.1 dholland return (-1); /* Corrupted directory. */ 1054 1.1 dholland prevoff = blkoff + i; 1055 1.1 dholland } 1056 1.1 dholland return (prevoff); 1057 1.1 dholland } 1058 1.1 dholland 1059 1.1 dholland /* 1060 1.1 dholland * Try to free up `wanted' bytes by stealing memory from existing 1061 1.1 dholland * dirhashes. Returns zero with list locked if successful. 1062 1.1 dholland */ 1063 1.1 dholland static int 1064 1.3 dholland ulfsdirhash_recycle(int wanted) 1065 1.1 dholland { 1066 1.1 dholland struct dirhash *dh; 1067 1.1 dholland doff_t **hash; 1068 1.1 dholland u_int8_t *blkfree; 1069 1.1 dholland int i, mem, narrays; 1070 1.1 dholland size_t hashsz, blkfreesz; 1071 1.1 dholland 1072 1.1 dholland DIRHASHLIST_LOCK(); 1073 1.3 dholland while (wanted + ulfs_dirhashmem > ulfs_dirhashmaxmem) { 1074 1.1 dholland /* Find a dirhash, and lock it. */ 1075 1.3 dholland if ((dh = TAILQ_FIRST(&ulfsdirhash_list)) == NULL) { 1076 1.1 dholland DIRHASHLIST_UNLOCK(); 1077 1.1 dholland return (-1); 1078 1.1 dholland } 1079 1.1 dholland DIRHASH_LOCK(dh); 1080 1.1 dholland KASSERT(dh->dh_hash != NULL); 1081 1.1 dholland 1082 1.1 dholland /* Decrement the score; only recycle if it becomes zero. */ 1083 1.1 dholland if (--dh->dh_score > 0) { 1084 1.1 dholland DIRHASH_UNLOCK(dh); 1085 1.1 dholland DIRHASHLIST_UNLOCK(); 1086 1.1 dholland return (-1); 1087 1.1 dholland } 1088 1.1 dholland 1089 1.1 dholland /* Remove it from the list and detach its memory. */ 1090 1.3 dholland TAILQ_REMOVE(&ulfsdirhash_list, dh, dh_list); 1091 1.1 dholland dh->dh_onlist = 0; 1092 1.1 dholland hash = dh->dh_hash; 1093 1.1 dholland hashsz = dh->dh_hashsz; 1094 1.1 dholland dh->dh_hash = NULL; 1095 1.1 dholland blkfree = dh->dh_blkfree; 1096 1.1 dholland blkfreesz = dh->dh_blkfreesz; 1097 1.1 dholland dh->dh_blkfree = NULL; 1098 1.1 dholland narrays = dh->dh_narrays; 1099 1.1 dholland mem = narrays * sizeof(*dh->dh_hash) + 1100 1.1 dholland narrays * DH_NBLKOFF * sizeof(**dh->dh_hash) + 1101 1.1 dholland dh->dh_nblk * sizeof(*dh->dh_blkfree); 1102 1.1 dholland 1103 1.1 dholland /* Unlock everything, free the detached memory. */ 1104 1.1 dholland DIRHASH_UNLOCK(dh); 1105 1.1 dholland DIRHASHLIST_UNLOCK(); 1106 1.1 dholland 1107 1.1 dholland for (i = 0; i < narrays; i++) 1108 1.1 dholland DIRHASH_BLKFREE(hash[i]); 1109 1.1 dholland kmem_free(hash, hashsz); 1110 1.1 dholland kmem_free(blkfree, blkfreesz); 1111 1.1 dholland 1112 1.1 dholland /* Account for the returned memory, and repeat if necessary. */ 1113 1.1 dholland DIRHASHLIST_LOCK(); 1114 1.3 dholland atomic_add_int(&ulfs_dirhashmem, -mem); 1115 1.1 dholland } 1116 1.1 dholland /* Success. */ 1117 1.1 dholland return (0); 1118 1.1 dholland } 1119 1.1 dholland 1120 1.1 dholland static void 1121 1.3 dholland ulfsdirhash_sysctl_init(void) 1122 1.1 dholland { 1123 1.1 dholland const struct sysctlnode *rnode, *cnode; 1124 1.1 dholland 1125 1.3 dholland sysctl_createv(&ulfsdirhash_sysctl_log, 0, NULL, &rnode, 1126 1.1 dholland CTLFLAG_PERMANENT, 1127 1.3 dholland CTLTYPE_NODE, "ulfs", 1128 1.3 dholland SYSCTL_DESCR("ulfs"), 1129 1.1 dholland NULL, 0, NULL, 0, 1130 1.8 pooka CTL_VFS, CTL_CREATE, CTL_EOL); 1131 1.1 dholland 1132 1.3 dholland sysctl_createv(&ulfsdirhash_sysctl_log, 0, &rnode, &rnode, 1133 1.1 dholland CTLFLAG_PERMANENT, 1134 1.1 dholland CTLTYPE_NODE, "dirhash", 1135 1.1 dholland SYSCTL_DESCR("dirhash"), 1136 1.1 dholland NULL, 0, NULL, 0, 1137 1.1 dholland CTL_CREATE, CTL_EOL); 1138 1.1 dholland 1139 1.3 dholland sysctl_createv(&ulfsdirhash_sysctl_log, 0, &rnode, &cnode, 1140 1.1 dholland CTLFLAG_PERMANENT|CTLFLAG_READWRITE, 1141 1.1 dholland CTLTYPE_INT, "minblocks", 1142 1.1 dholland SYSCTL_DESCR("minimum hashed directory size in blocks"), 1143 1.3 dholland NULL, 0, &ulfs_dirhashminblks, 0, 1144 1.1 dholland CTL_CREATE, CTL_EOL); 1145 1.1 dholland 1146 1.3 dholland sysctl_createv(&ulfsdirhash_sysctl_log, 0, &rnode, &cnode, 1147 1.1 dholland CTLFLAG_PERMANENT|CTLFLAG_READWRITE, 1148 1.1 dholland CTLTYPE_INT, "maxmem", 1149 1.1 dholland SYSCTL_DESCR("maximum dirhash memory usage"), 1150 1.3 dholland NULL, 0, &ulfs_dirhashmaxmem, 0, 1151 1.1 dholland CTL_CREATE, CTL_EOL); 1152 1.1 dholland 1153 1.3 dholland sysctl_createv(&ulfsdirhash_sysctl_log, 0, &rnode, &cnode, 1154 1.1 dholland CTLFLAG_PERMANENT|CTLFLAG_READONLY, 1155 1.1 dholland CTLTYPE_INT, "memused", 1156 1.1 dholland SYSCTL_DESCR("current dirhash memory usage"), 1157 1.3 dholland NULL, 0, &ulfs_dirhashmem, 0, 1158 1.1 dholland CTL_CREATE, CTL_EOL); 1159 1.1 dholland 1160 1.3 dholland sysctl_createv(&ulfsdirhash_sysctl_log, 0, &rnode, &cnode, 1161 1.1 dholland CTLFLAG_PERMANENT|CTLFLAG_READWRITE, 1162 1.1 dholland CTLTYPE_INT, "docheck", 1163 1.1 dholland SYSCTL_DESCR("enable extra sanity checks"), 1164 1.3 dholland NULL, 0, &ulfs_dirhashcheck, 0, 1165 1.1 dholland CTL_CREATE, CTL_EOL); 1166 1.1 dholland } 1167 1.1 dholland 1168 1.1 dholland void 1169 1.3 dholland ulfsdirhash_init(void) 1170 1.1 dholland { 1171 1.1 dholland 1172 1.19 simonb /* 1173 1.19 simonb * Only initialise defaults for the dirhash size if it hasn't 1174 1.19 simonb * hasn't been set. 1175 1.19 simonb */ 1176 1.19 simonb if (ulfs_dirhashmaxmem == 0) { 1177 1.19 simonb /* Use 64-bit math to avoid overflows. */ 1178 1.19 simonb uint64_t physmem_bytes, hash_bytes; 1179 1.19 simonb 1180 1.19 simonb physmem_bytes = ctob((uint64_t)physmem); 1181 1.19 simonb hash_bytes = physmem_bytes / DIRHASH_DEFAULT_DIVIDER; 1182 1.19 simonb 1183 1.19 simonb if (hash_bytes < MIN_DEFAULT_DIRHASH_MEM) 1184 1.19 simonb hash_bytes = 0; 1185 1.19 simonb 1186 1.19 simonb if (hash_bytes > MAX_DEFAULT_DIRHASH_MEM) 1187 1.19 simonb hash_bytes = MAX_DEFAULT_DIRHASH_MEM; 1188 1.19 simonb 1189 1.19 simonb ulfs_dirhashmaxmem = (u_int)hash_bytes; 1190 1.19 simonb } 1191 1.19 simonb 1192 1.3 dholland mutex_init(&ulfsdirhash_lock, MUTEX_DEFAULT, IPL_NONE); 1193 1.3 dholland ulfsdirhashblk_cache = pool_cache_init(DH_NBLKOFF * sizeof(daddr_t), 0, 1194 1.1 dholland 0, 0, "dirhashblk", NULL, IPL_NONE, NULL, NULL, NULL); 1195 1.3 dholland ulfsdirhash_cache = pool_cache_init(sizeof(struct dirhash), 0, 1196 1.1 dholland 0, 0, "dirhash", NULL, IPL_NONE, NULL, NULL, NULL); 1197 1.3 dholland TAILQ_INIT(&ulfsdirhash_list); 1198 1.3 dholland ulfsdirhash_sysctl_init(); 1199 1.1 dholland } 1200 1.1 dholland 1201 1.1 dholland void 1202 1.3 dholland ulfsdirhash_done(void) 1203 1.1 dholland { 1204 1.1 dholland 1205 1.3 dholland KASSERT(TAILQ_EMPTY(&ulfsdirhash_list)); 1206 1.3 dholland pool_cache_destroy(ulfsdirhashblk_cache); 1207 1.3 dholland pool_cache_destroy(ulfsdirhash_cache); 1208 1.3 dholland mutex_destroy(&ulfsdirhash_lock); 1209 1.3 dholland sysctl_teardown(&ulfsdirhash_sysctl_log); 1210 1.1 dholland } 1211