/* * 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); }
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); }