#include <sys/param.h>
#include <sys/systm.h>
#include <sys/kernel.h>
#include <sys/namei.h>
#include <sys/buf.h>
#include <sys/stat.h>
#include <sys/mount.h>
#include <sys/proc.h>
#include <sys/vnode.h>
#include <ufs/ufs/quota.h>
#include <ufs/ufs/inode.h>
#include <ufs/ufs/dir.h>
#ifdef UFS_DIRHASH
#include <ufs/ufs/dirhash.h>
#endif
#include <ufs/ufs/ufsmount.h>
#include <ufs/ufs/ufs_extern.h>
extern struct nchstats nchstats;
#ifdef DIAGNOSTIC
int dirchk = 1;
#else
int dirchk = 0;
#endif
int
ufs_lookup(void *v)
{
struct vop_lookup_args *ap = v;
struct vnode *vdp;
struct inode *dp;
struct buf *bp;
struct direct *ep;
int entryoffsetinblock;
enum {NONE, COMPACT, FOUND} slotstatus;
doff_t slotoffset;
int slotsize;
int slotfreespace;
int slotneeded;
int numdirpasses;
doff_t endsearch;
doff_t prevoff;
struct vnode *pdp;
struct vnode *tdp;
doff_t enduseful;
u_long bmask;
int lockparent;
int wantparent;
int namlen, error;
struct vnode **vpp = ap->a_vpp;
struct componentname *cnp = ap->a_cnp;
struct ucred *cred = cnp->cn_cred;
int flags;
int nameiop = cnp->cn_nameiop;
cnp->cn_flags &= ~PDIRUNLOCK;
flags = cnp->cn_flags;
bp = NULL;
slotoffset = -1;
*vpp = NULL;
vdp = ap->a_dvp;
dp = VTOI(vdp);
lockparent = flags & LOCKPARENT;
wantparent = flags & (LOCKPARENT|WANTPARENT);
if ((DIP(dp, mode) & IFMT) != IFDIR)
return (ENOTDIR);
if ((error = VOP_ACCESS(vdp, VEXEC, cred, cnp->cn_proc)) != 0)
return (error);
if ((flags & ISLASTCN) && (vdp->v_mount->mnt_flag & MNT_RDONLY) &&
(cnp->cn_nameiop == DELETE || cnp->cn_nameiop == RENAME))
return (EROFS);
if ((error = cache_lookup(vdp, vpp, cnp)) >= 0)
return (error);
slotstatus = FOUND;
slotfreespace = slotsize = slotneeded = 0;
if ((nameiop == CREATE || nameiop == RENAME) &&
(flags & ISLASTCN)) {
slotstatus = NONE;
slotneeded = (sizeof(struct direct) - MAXNAMLEN +
cnp->cn_namelen + 3) &~ 3;
}
bmask = VFSTOUFS(vdp->v_mount)->um_mountp->mnt_stat.f_iosize - 1;
#ifdef UFS_DIRHASH
if (ufsdirhash_build(dp) == 0) {
enduseful = DIP(dp, size);
if (slotstatus != FOUND) {
slotoffset = ufsdirhash_findfree(dp, slotneeded,
&slotsize);
if (slotoffset >= 0) {
slotstatus = COMPACT;
enduseful = ufsdirhash_enduseful(dp);
if (enduseful < 0)
enduseful = DIP(dp, size);
}
}
numdirpasses = 1;
entryoffsetinblock = 0;
switch (ufsdirhash_lookup(dp, cnp->cn_nameptr, cnp->cn_namelen,
&dp->i_offset, &bp, nameiop == DELETE ? &prevoff : NULL)) {
case 0:
ep = (struct direct *)((char *)bp->b_data +
(dp->i_offset & bmask));
goto foundentry;
case ENOENT:
#define roundup2(x, y) (((x)+((y)-1))&(~((y)-1)))
dp->i_offset = roundup2(DIP(dp, size), DIRBLKSIZ);
goto notfound;
default:
break;
}
}
#endif
if (nameiop != LOOKUP || dp->i_diroff == 0 ||
dp->i_diroff >= DIP(dp, size)) {
entryoffsetinblock = 0;
dp->i_offset = 0;
numdirpasses = 1;
} else {
dp->i_offset = dp->i_diroff;
if ((entryoffsetinblock = dp->i_offset & bmask) &&
(error = UFS_BUFATOFF(dp, (off_t)dp->i_offset, NULL, &bp)))
return (error);
numdirpasses = 2;
nchstats.ncs_2passes++;
}
prevoff = dp->i_offset;
endsearch = roundup(DIP(dp, size), DIRBLKSIZ);
enduseful = 0;
searchloop:
while (dp->i_offset < endsearch) {
if ((dp->i_offset & bmask) == 0) {
if (bp != NULL)
brelse(bp);
error = UFS_BUFATOFF(dp, (off_t)dp->i_offset, NULL,
&bp);
if (error)
return (error);
entryoffsetinblock = 0;
}
if (slotstatus == NONE &&
(entryoffsetinblock & (DIRBLKSIZ - 1)) == 0) {
slotoffset = -1;
slotfreespace = 0;
}
ep = (struct direct *)((char *)bp->b_data + entryoffsetinblock);
if (ep->d_reclen == 0 ||
(dirchk && ufs_dirbadentry(vdp, ep, entryoffsetinblock))) {
int i;
ufs_dirbad(dp, dp->i_offset, "mangled entry");
i = DIRBLKSIZ - (entryoffsetinblock & (DIRBLKSIZ - 1));
dp->i_offset += i;
entryoffsetinblock += i;
continue;
}
if (slotstatus != FOUND) {
int size = ep->d_reclen;
if (ep->d_ino != 0)
size -= DIRSIZ(ep);
if (size > 0) {
if (size >= slotneeded) {
slotstatus = FOUND;
slotoffset = dp->i_offset;
slotsize = ep->d_reclen;
} else if (slotstatus == NONE) {
slotfreespace += size;
if (slotoffset == -1)
slotoffset = dp->i_offset;
if (slotfreespace >= slotneeded) {
slotstatus = COMPACT;
slotsize = dp->i_offset +
ep->d_reclen - slotoffset;
}
}
}
}
if (ep->d_ino) {
namlen = ep->d_namlen;
if (namlen == cnp->cn_namelen &&
!memcmp(cnp->cn_nameptr, ep->d_name, namlen)) {
#ifdef UFS_DIRHASH
foundentry:
#endif
dp->i_ino = ep->d_ino;
dp->i_reclen = ep->d_reclen;
goto found;
}
}
prevoff = dp->i_offset;
dp->i_offset += ep->d_reclen;
entryoffsetinblock += ep->d_reclen;
if (ep->d_ino)
enduseful = dp->i_offset;
}
#ifdef UFS_DIRHASH
notfound:
#endif
if (numdirpasses == 2) {
numdirpasses--;
dp->i_offset = 0;
endsearch = dp->i_diroff;
goto searchloop;
}
if (bp != NULL)
brelse(bp);
if ((nameiop == CREATE || nameiop == RENAME) &&
(flags & ISLASTCN) && dp->i_effnlink != 0) {
error = VOP_ACCESS(vdp, VWRITE, cred, cnp->cn_proc);
if (error)
return (error);
if (slotstatus == NONE) {
dp->i_offset = roundup(DIP(dp, size), DIRBLKSIZ);
dp->i_count = 0;
enduseful = dp->i_offset;
} else if (nameiop == DELETE) {
dp->i_offset = slotoffset;
if ((dp->i_offset & (DIRBLKSIZ - 1)) == 0)
dp->i_count = 0;
else
dp->i_count = dp->i_offset - prevoff;
} else {
dp->i_offset = slotoffset;
dp->i_count = slotsize;
if (enduseful < slotoffset + slotsize)
enduseful = slotoffset + slotsize;
}
dp->i_endoff = roundup(enduseful, DIRBLKSIZ);
cnp->cn_flags |= SAVENAME;
if (!lockparent) {
VOP_UNLOCK(vdp);
cnp->cn_flags |= PDIRUNLOCK;
}
return (EJUSTRETURN);
}
if ((cnp->cn_flags & MAKEENTRY) && nameiop != CREATE)
cache_enter(vdp, *vpp, cnp);
return (ENOENT);
found:
if (numdirpasses == 2)
nchstats.ncs_pass2++;
if (dp->i_offset + DIRSIZ(ep) > DIP(dp, size)) {
ufs_dirbad(dp, dp->i_offset, "i_ffs_size too small");
DIP_ASSIGN(dp, size, dp->i_offset + DIRSIZ(ep));
dp->i_flag |= IN_CHANGE | IN_UPDATE;
}
brelse(bp);
if ((flags & ISLASTCN) && nameiop == LOOKUP)
dp->i_diroff = dp->i_offset &~ (DIRBLKSIZ - 1);
if (nameiop == DELETE && (flags & ISLASTCN)) {
error = VOP_ACCESS(vdp, VWRITE, cred, cnp->cn_proc);
if (error)
return (error);
if ((dp->i_offset & (DIRBLKSIZ - 1)) == 0)
dp->i_count = 0;
else
dp->i_count = dp->i_offset - prevoff;
if (dp->i_number == dp->i_ino) {
vref(vdp);
*vpp = vdp;
return (0);
}
error = VFS_VGET(vdp->v_mount, dp->i_ino, &tdp);
if (error)
return (error);
if ((DIP(dp, mode) & ISVTX) &&
cred->cr_uid != 0 &&
cred->cr_uid != DIP(dp, uid) &&
!vnoperm(vdp) &&
DIP(VTOI(tdp), uid) != cred->cr_uid) {
vput(tdp);
return (EPERM);
}
*vpp = tdp;
if (!lockparent) {
VOP_UNLOCK(vdp);
cnp->cn_flags |= PDIRUNLOCK;
}
return (0);
}
if (nameiop == RENAME && wantparent &&
(flags & ISLASTCN)) {
error = VOP_ACCESS(vdp, VWRITE, cred, cnp->cn_proc);
if (error)
return (error);
if (dp->i_number == dp->i_ino)
return (EISDIR);
error = VFS_VGET(vdp->v_mount, dp->i_ino, &tdp);
if (error)
return (error);
*vpp = tdp;
cnp->cn_flags |= SAVENAME;
if (!lockparent) {
VOP_UNLOCK(vdp);
cnp->cn_flags |= PDIRUNLOCK;
}
return (0);
}
pdp = vdp;
if (flags & ISDOTDOT) {
VOP_UNLOCK(pdp);
cnp->cn_flags |= PDIRUNLOCK;
error = VFS_VGET(vdp->v_mount, dp->i_ino, &tdp);
if (error) {
if (vn_lock(pdp, LK_EXCLUSIVE | LK_RETRY) == 0)
cnp->cn_flags &= ~PDIRUNLOCK;
return (error);
}
if (lockparent && (flags & ISLASTCN)) {
if ((error = vn_lock(pdp, LK_EXCLUSIVE))) {
vput(tdp);
return (error);
}
cnp->cn_flags &= ~PDIRUNLOCK;
}
*vpp = tdp;
} else if (dp->i_number == dp->i_ino) {
vref(vdp);
*vpp = vdp;
} else {
error = VFS_VGET(vdp->v_mount, dp->i_ino, &tdp);
if (error)
return (error);
if (!lockparent || !(flags & ISLASTCN)) {
VOP_UNLOCK(pdp);
cnp->cn_flags |= PDIRUNLOCK;
}
*vpp = tdp;
}
if (cnp->cn_flags & MAKEENTRY)
cache_enter(vdp, *vpp, cnp);
return (0);
}
void
ufs_dirbad(struct inode *ip, doff_t offset, char *how)
{
struct mount *mp;
mp = ITOV(ip)->v_mount;
(void)printf("%s: bad dir ino %u at offset %d: %s\n",
mp->mnt_stat.f_mntonname, ip->i_number, offset, how);
if ((mp->mnt_stat.f_flags & MNT_RDONLY) == 0)
panic("bad dir");
}
int
ufs_dirbadentry(struct vnode *vdp, struct direct *ep, int entryoffsetinblock)
{
struct inode *dp;
int i;
int namlen;
dp = VTOI(vdp);
namlen = ep->d_namlen;
if ((ep->d_reclen & 0x3) != 0 ||
ep->d_reclen > DIRBLKSIZ - (entryoffsetinblock & (DIRBLKSIZ - 1)) ||
ep->d_reclen < DIRSIZ(ep) || namlen > MAXNAMLEN) {
printf("First bad\n");
goto bad;
}
if (ep->d_ino == 0)
return (0);
for (i = 0; i < namlen; i++)
if (ep->d_name[i] == '\0') {
printf("Second bad\n");
goto bad;
}
if (ep->d_name[i])
goto bad;
return (0);
bad:
return (1);
}
void
ufs_makedirentry(struct inode *ip, struct componentname *cnp,
struct direct *newdirp)
{
#ifdef DIAGNOSTIC
if ((cnp->cn_flags & SAVENAME) == 0)
panic("ufs_makedirentry: missing name");
#endif
newdirp->d_ino = ip->i_number;
newdirp->d_namlen = cnp->cn_namelen;
memset(newdirp->d_name + (cnp->cn_namelen & ~(DIR_ROUNDUP-1)),
0, DIR_ROUNDUP);
memcpy(newdirp->d_name, cnp->cn_nameptr, cnp->cn_namelen);
newdirp->d_type = IFTODT(DIP(ip, mode));
}
int
ufs_direnter(struct vnode *dvp, struct vnode *tvp, struct direct *dirp,
struct componentname *cnp, struct buf *newdirbp)
{
struct ucred *cr;
struct proc *p;
int newentrysize;
struct inode *dp;
struct buf *bp;
u_int dsize;
struct direct *ep, *nep;
int error, ret, blkoff, loc, spacefree, flags;
char *dirbuf;
error = 0;
cr = cnp->cn_cred;
p = cnp->cn_proc;
dp = VTOI(dvp);
newentrysize = DIRSIZ(dirp);
if (dp->i_count == 0) {
if (dp->i_offset & (DIRBLKSIZ - 1))
panic("ufs_direnter: newblk");
flags = B_CLRBUF;
flags |= B_SYNC;
if ((error = UFS_BUF_ALLOC(dp, (off_t)dp->i_offset, DIRBLKSIZ,
cr, flags, &bp)) != 0) {
return (error);
}
DIP_ASSIGN(dp, size, dp->i_offset + DIRBLKSIZ);
dp->i_flag |= IN_CHANGE | IN_UPDATE;
uvm_vnp_setsize(dvp, DIP(dp, size));
dirp->d_reclen = DIRBLKSIZ;
blkoff = dp->i_offset &
(VFSTOUFS(dvp->v_mount)->um_mountp->mnt_stat.f_iosize - 1);
memcpy(bp->b_data + blkoff, dirp, newentrysize);
#ifdef UFS_DIRHASH
if (dp->i_dirhash != NULL) {
ufsdirhash_newblk(dp, dp->i_offset);
ufsdirhash_add(dp, dirp, dp->i_offset);
ufsdirhash_checkblock(dp, (char *)bp->b_data + blkoff,
dp->i_offset);
}
#endif
error = VOP_BWRITE(bp);
ret = UFS_UPDATE(dp, 1);
if (error == 0)
return (ret);
return (error);
}
if (dp->i_offset + dp->i_count > DIP(dp, size))
DIP_ASSIGN(dp, size, dp->i_offset + dp->i_count);
if ((error = UFS_BUFATOFF(dp, (off_t)dp->i_offset, &dirbuf, &bp))
!= 0) {
return (error);
}
ep = (struct direct *)dirbuf;
dsize = ep->d_ino ? DIRSIZ(ep) : 0;
spacefree = ep->d_reclen - dsize;
for (loc = ep->d_reclen; loc < dp->i_count; ) {
nep = (struct direct *)(dirbuf + loc);
ep->d_reclen = dsize;
ep = (struct direct *)((char *)ep + dsize);
loc += nep->d_reclen;
if (nep->d_ino == 0) {
spacefree += nep->d_reclen;
ep->d_ino = 0;
dsize = 0;
continue;
}
dsize = DIRSIZ(nep);
spacefree += nep->d_reclen - dsize;
#ifdef UFS_DIRHASH
if (dp->i_dirhash != NULL)
ufsdirhash_move(dp, nep,
dp->i_offset + ((char *)nep - dirbuf),
dp->i_offset + ((char *)ep - dirbuf));
#endif
memmove(ep, nep, dsize);
}
if (ep->d_ino == 0) {
if (spacefree + dsize < newentrysize)
panic("ufs_direnter: compact1");
dirp->d_reclen = spacefree + dsize;
} else {
if (spacefree < newentrysize)
panic("ufs_direnter: compact2");
dirp->d_reclen = spacefree;
ep->d_reclen = dsize;
ep = (struct direct *)((char *)ep + dsize);
}
#ifdef UFS_DIRHASH
if (dp->i_dirhash != NULL && (ep->d_ino == 0 ||
dirp->d_reclen == spacefree))
ufsdirhash_add(dp, dirp, dp->i_offset + ((char *)ep - dirbuf));
#endif
memcpy(ep, dirp, newentrysize);
#ifdef UFS_DIRHASH
if (dp->i_dirhash != NULL)
ufsdirhash_checkblock(dp, dirbuf -
(dp->i_offset & (DIRBLKSIZ - 1)),
dp->i_offset & ~(DIRBLKSIZ - 1));
#endif
error = VOP_BWRITE(bp);
dp->i_flag |= IN_CHANGE | IN_UPDATE;
if (error == 0 && dp->i_endoff && dp->i_endoff < DIP(dp, size)) {
if (tvp != NULL)
VOP_UNLOCK(tvp);
error = UFS_TRUNCATE(dp, (off_t)dp->i_endoff, IO_SYNC, cr);
#ifdef UFS_DIRHASH
if (error == 0 && dp->i_dirhash != NULL)
ufsdirhash_dirtrunc(dp, dp->i_endoff);
#endif
if (tvp != NULL)
vn_lock(tvp, LK_EXCLUSIVE | LK_RETRY);
}
return (error);
}
int
ufs_dirremove(struct vnode *dvp, struct inode *ip, int flags, int isrmdir)
{
struct inode *dp;
struct direct *ep;
struct buf *bp;
int error;
dp = VTOI(dvp);
if ((error = UFS_BUFATOFF(dp,
(off_t)(dp->i_offset - dp->i_count), (char **)&ep, &bp)) != 0)
return (error);
#ifdef UFS_DIRHASH
if (dp->i_dirhash != NULL)
ufsdirhash_remove(dp, (dp->i_count == 0) ? ep :
(struct direct *)((char *)ep + ep->d_reclen), dp->i_offset);
#endif
if (dp->i_count == 0) {
ep->d_ino = 0;
} else {
ep->d_reclen += dp->i_reclen;
}
#ifdef UFS_DIRHASH
if (dp->i_dirhash != NULL)
ufsdirhash_checkblock(dp, (char *)ep -
((dp->i_offset - dp->i_count) & (DIRBLKSIZ - 1)),
dp->i_offset & ~(DIRBLKSIZ - 1));
#endif
if (ip) {
ip->i_effnlink--;
DIP_ADD(ip, nlink, -1);
ip->i_flag |= IN_CHANGE;
}
if (DOINGASYNC(dvp) && dp->i_count != 0) {
bdwrite(bp);
error = 0;
} else
error = bwrite(bp);
dp->i_flag |= IN_CHANGE | IN_UPDATE;
return (error);
}
int
ufs_dirrewrite(struct inode *dp, struct inode *oip, ufsino_t newinum,
int newtype, int isrmdir)
{
struct buf *bp;
struct direct *ep;
struct vnode *vdp = ITOV(dp);
int error;
error = UFS_BUFATOFF(dp, (off_t)dp->i_offset, (char **)&ep, &bp);
if (error)
return (error);
ep->d_ino = newinum;
ep->d_type = newtype;
oip->i_effnlink--;
DIP_ADD(oip, nlink, -1);
oip->i_flag |= IN_CHANGE;
if (DOINGASYNC(vdp)) {
bdwrite(bp);
error = 0;
} else {
error = VOP_BWRITE(bp);
}
dp->i_flag |= IN_CHANGE | IN_UPDATE;
return (error);
}
int
ufs_dirempty(struct inode *ip, ufsino_t parentino, struct ucred *cred)
{
off_t off, m;
struct dirtemplate dbuf;
struct direct *dp = (struct direct *)&dbuf;
int error, namlen;
size_t count;
#define MINDIRSIZ (sizeof (struct dirtemplate) / 2)
m = DIP(ip, size);
for (off = 0; off < m; off += dp->d_reclen) {
error = vn_rdwr(UIO_READ, ITOV(ip), (caddr_t)dp, MINDIRSIZ, off,
UIO_SYSSPACE, IO_NODELOCKED, cred, &count, curproc);
if (error || count != 0)
return (0);
if (dp->d_reclen == 0)
return (0);
if (dp->d_ino == 0)
continue;
namlen = dp->d_namlen;
if (namlen > 2)
return (0);
if (dp->d_name[0] != '.')
return (0);
if (namlen == 1 && dp->d_ino == ip->i_number)
continue;
if (dp->d_name[1] == '.' && dp->d_ino == parentino)
continue;
return (0);
}
return (1);
}
int
ufs_checkpath(struct inode *source, struct inode *target, struct ucred *cred)
{
struct vnode *nextvp, *vp;
int error, rootino, namlen;
struct dirtemplate dirbuf;
vp = ITOV(target);
if (target->i_number == source->i_number) {
error = EEXIST;
goto out;
}
rootino = ROOTINO;
error = 0;
if (target->i_number == rootino)
goto out;
for (;;) {
if (vp->v_type != VDIR) {
error = ENOTDIR;
break;
}
error = vn_rdwr(UIO_READ, vp, (caddr_t)&dirbuf,
sizeof (struct dirtemplate), (off_t)0, UIO_SYSSPACE,
IO_NODELOCKED, cred, NULL, curproc);
if (error != 0)
break;
namlen = dirbuf.dotdot_namlen;
if (namlen != 2 ||
dirbuf.dotdot_name[0] != '.' ||
dirbuf.dotdot_name[1] != '.') {
error = ENOTDIR;
break;
}
if (dirbuf.dotdot_ino == source->i_number) {
error = EINVAL;
break;
}
if (dirbuf.dotdot_ino == rootino)
break;
VOP_UNLOCK(vp);
error = VFS_VGET(vp->v_mount, dirbuf.dotdot_ino, &nextvp);
vrele(vp);
if (error) {
vp = NULL;
break;
}
vp = nextvp;
}
out:
if (error == ENOTDIR)
printf("checkpath: .. not a directory\n");
if (vp != NULL)
vput(vp);
return (error);
}