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