ulfs_dirhash.c revision 1.19 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