Пример #1
0
/*
 * Attempt to build up a hash table for the directory contents in
 * inode 'ip'. Returns 0 on success, or -1 of the operation failed.
 */
int
ufsdirhash_build(struct inode *ip)
{
	struct dirhash *dh;
	struct buf *bp = NULL;
	struct direct *ep;
	struct vnode *vp;
	doff_t bmask, pos;
	int dirblocks, i, j, memreqd, nblocks, narrays, nslots, slot;

	/* Check if we can/should use dirhash. */
	if (ip->i_dirhash == NULL) {
		if (DIP(ip, size) < ufs_mindirhashsize || OFSFMT(ip))
			return (-1);
	} else {
		/* Hash exists, but sysctls could have changed. */
		if (DIP(ip, size) < ufs_mindirhashsize ||
		    ufs_dirhashmem > ufs_dirhashmaxmem) {
			ufsdirhash_free(ip);
			return (-1);
		}
		/* Check if hash exists and is intact (note: unlocked read). */
		if (ip->i_dirhash->dh_hash != NULL)
			return (0);
		/* Free the old, recycled hash and build a new one. */
		ufsdirhash_free(ip);
	}

	/* Don't hash removed directories. */
	if (ip->i_effnlink == 0)
		return (-1);

	vp = ip->i_vnode;
	/* Allocate 50% more entries than this dir size could ever need. */
	DIRHASH_ASSERT(DIP(ip, size) >= DIRBLKSIZ, ("ufsdirhash_build size"));
	nslots = DIP(ip, size) / DIRECTSIZ(1);
	nslots = (nslots * 3 + 1) / 2;
	narrays = howmany(nslots, DH_NBLKOFF);
	nslots = narrays * DH_NBLKOFF;
	dirblocks = howmany(DIP(ip, size), DIRBLKSIZ);
	nblocks = (dirblocks * 3 + 1) / 2;

	memreqd = sizeof(*dh) + narrays * sizeof(*dh->dh_hash) +
	    narrays * DH_NBLKOFF * sizeof(**dh->dh_hash) +
	    nblocks * sizeof(*dh->dh_blkfree);
	DIRHASHLIST_LOCK();
	if (memreqd + ufs_dirhashmem > ufs_dirhashmaxmem) {
		DIRHASHLIST_UNLOCK();
		if (memreqd > ufs_dirhashmaxmem / 2)
			return (-1);

		/* Try to free some space. */
		if (ufsdirhash_recycle(memreqd) != 0)
			return (-1);
		/* Enough was freed, and list has been locked. */
	}
	ufs_dirhashmem += memreqd;
	DIRHASHLIST_UNLOCK();

	/*
	 * Use non-blocking mallocs so that we will revert to a linear
	 * lookup on failure rather than potentially blocking forever.
	 */
	dh = malloc(sizeof(*dh), M_DIRHASH, M_NOWAIT|M_ZERO);
	if (dh == NULL) {
		DIRHASHLIST_LOCK();
		ufs_dirhashmem -= memreqd;
		DIRHASHLIST_UNLOCK();
		return (-1);
	}
	dh->dh_hash = mallocarray(narrays, sizeof(dh->dh_hash[0]),
	    M_DIRHASH, M_NOWAIT|M_ZERO);
	dh->dh_blkfree = mallocarray(nblocks, sizeof(dh->dh_blkfree[0]),
	    M_DIRHASH, M_NOWAIT | M_ZERO);
	if (dh->dh_hash == NULL || dh->dh_blkfree == NULL)
		goto fail;
	for (i = 0; i < narrays; i++) {
		if ((dh->dh_hash[i] = DIRHASH_BLKALLOC()) == NULL)
			goto fail;
		for (j = 0; j < DH_NBLKOFF; j++)
			dh->dh_hash[i][j] = DIRHASH_EMPTY;
	}

	/* Initialise the hash table and block statistics. */
	mtx_init(&dh->dh_mtx, IPL_NONE);
	dh->dh_narrays = narrays;
	dh->dh_hlen = nslots;
	dh->dh_nblk = nblocks;
	dh->dh_dirblks = dirblocks;
	for (i = 0; i < dirblocks; i++)
		dh->dh_blkfree[i] = DIRBLKSIZ / DIRALIGN;
	for (i = 0; i < DH_NFSTATS; i++)
		dh->dh_firstfree[i] = -1;
	dh->dh_firstfree[DH_NFSTATS] = 0;
	dh->dh_seqopt = 0;
	dh->dh_seqoff = 0;
	dh->dh_score = DH_SCOREINIT;
	ip->i_dirhash = dh;

	bmask = VFSTOUFS(vp->v_mount)->um_mountp->mnt_stat.f_iosize - 1;
	pos = 0;
	while (pos < DIP(ip, size)) {
		/* If necessary, get the next directory block. */
		if ((pos & bmask) == 0) {
			if (bp != NULL)
				brelse(bp);
			if (UFS_BUFATOFF(ip, (off_t)pos, NULL, &bp) != 0)
				goto fail;
		}
		/* Add this entry to the hash. */
		ep = (struct direct *)((char *)bp->b_data + (pos & bmask));
		if (ep->d_reclen == 0 || ep->d_reclen >
		    DIRBLKSIZ - (pos & (DIRBLKSIZ - 1))) {
			/* Corrupted directory. */
			brelse(bp);
			goto fail;
		}
		if (ep->d_ino != 0) {
			/* Add the entry (simplified ufsdirhash_add). */
			slot = ufsdirhash_hash(dh, ep->d_name, ep->d_namlen);
			while (DH_ENTRY(dh, slot) != DIRHASH_EMPTY)
				slot = WRAPINCR(slot, dh->dh_hlen);
			dh->dh_hused++;
			DH_ENTRY(dh, slot) = pos;
			ufsdirhash_adjfree(dh, pos, -DIRSIZ(0, ep));
		}
		pos += ep->d_reclen;
	}

	if (bp != NULL)
		brelse(bp);
	DIRHASHLIST_LOCK();
	TAILQ_INSERT_TAIL(&ufsdirhash_list, dh, dh_list);
	dh->dh_onlist = 1;
	DIRHASHLIST_UNLOCK();
	return (0);

fail:
	if (dh->dh_hash != NULL) {
		for (i = 0; i < narrays; i++)
			if (dh->dh_hash[i] != NULL)
				DIRHASH_BLKFREE(dh->dh_hash[i]);
		free(dh->dh_hash, M_DIRHASH, 0);
	}
	if (dh->dh_blkfree != NULL)
		free(dh->dh_blkfree, M_DIRHASH, 0);
	free(dh, M_DIRHASH, 0);
	ip->i_dirhash = NULL;
	DIRHASHLIST_LOCK();
	ufs_dirhashmem -= memreqd;
	DIRHASHLIST_UNLOCK();
	return (-1);
}
Пример #2
0
int
sys_scandir(const char *dirname, struct dirent ***namelist,
    int (*select)(const struct dirent *),
    int (*compar)(const struct dirent **, const struct dirent **))
{
	struct dirent **names;
	struct dirent **names2;
	struct dirent *dp;
	char name[1024];
	int lsname[22];
	int chname[22];
	int name2[22];
	int oid[22];
	char *s;
	size_t n1, n2;
	size_t len, oidlen, namlen;
	int cnt, max;
	int err;
	int i;

	*namelist = NULL;
	if (strlcpy(name, PATH_TO_SYS(dirname), sizeof(name)) >= sizeof(name))
		return (-EINVAL);
	/*
	 * Resolve the path.
	 */
	len = sizeof(oid) / sizeof(int);
	namlen = strlen(name) + 1;
	if (sysctlnametomib(name, oid, &len) != 0)
		return (-errno);
	lsname[0] = 0;	/* Root */
	lsname[1] = 2;	/* Get next */
	memcpy(lsname+2, oid, len * sizeof(int));
	n1 = 2 + len;
	oidlen = len;
	/*
	 * Setup the return list of dirents.
	 */
	cnt = 0;
	max = 64;
	names = malloc(max * sizeof(void *));
	if (names == NULL)
		return (-ENOMEM);

	for (;;) {
		n2 = sizeof(name2);
		if (sysctl(lsname, n1, name2, &n2, 0, 0) < 0) {
			if (errno == ENOENT)
				break;
			goto errout;
		}
		n2 /= sizeof(int);
		if (n2 < oidlen)
			break;
		for (i = 0; i < oidlen; i++)
			if (name2[i] != oid[i])
				goto out;
		chname[0] = 0;	/* root */
		chname[1] = 1;	/* oid name */
		memcpy(chname + 2, name2, n2 * sizeof(int));
		memcpy(lsname + 2, name2, n2 * sizeof(int));
		n1 = 2 + n2;
		/*
		 * scandir() is not supposed to go deeper than the requested
		 * directory but sysctl also doesn't return a node for
		 * 'subdirectories' so we have to find a file in the subdir
		 * and then truncate the name to report it.
	 	 */
		if (n2 > oidlen + 1) {
			/* Skip to the next name after this one. */
			n1 = 2 + oidlen + 1;
			lsname[n1 - 1]++;
		}
		len = sizeof(name);
		if (sysctl(chname, n2 + 2, name, &len, 0, 0) < 0)
			goto errout;
		if (len <= 0 || len < namlen)
			goto out;
		s = name + namlen;
		/* Just keep the first level name. */
		if (strchr(s, '.'))
			*strchr(s, '.') = '\0';
		len = strlen(s) + 1;
		dp = malloc(DIRECTSIZ(len));
		dp->d_reclen = DIRECTSIZ(len);
		dp->d_namlen = len;
		memcpy(&dp->d_name, s, len);
		if (select && !select(dp)) {
			free(dp);
			continue;
		}
		if (cnt == max) {
			max *= 2;
			names2 = realloc(names, max * sizeof(void *));
			if (names2 == NULL) {
				errno = ENOMEM;
				free(dp);
				goto errout;
			}
			names = names2;
		}
		names[cnt++] = dp;
	}
out:
	if (cnt && compar)
		qsort(names, cnt, sizeof(struct dirent *),
		    (int (*)(const void *, const void *))compar);

	*namelist = names;

	return (cnt);

errout:
	err = errno;
	for (i = 0; i < cnt; i++)
		free(names[i]);
	free(names);
	return (-err);
}