#include <rev_endian_fs.h>
#include <sys/param.h>
#include <sys/namei.h>
#include <sys/buf.h>
#include <sys/file.h>
#include <sys/mount.h>
#include <sys/vnode.h>
#include <sys/quota.h>
#include <ufs/ufs/quota.h>
#include <ufs/ufs/inode.h>
#include <ufs/ufs/dir.h>
#include <ufs/ufs/ufsmount.h>
#include <ufs/ufs/ufs_extern.h>
#if REV_ENDIAN_FS
#include <ufs/ufs/ufs_byte_order.h>
#include <architecture/byte_order.h>
#endif
extern struct nchstats nchstats;
#if DIAGNOSTIC
int dirchk = 1;
#else
int dirchk = 0;
#endif
#define FSFMT(vp) ((vp)->v_mount->mnt_maxsymlinklen <= 0)
int
ufs_lookup(ap)
struct vop_lookup_args *ap;
{
register struct vnode *vdp;
register struct inode *dp;
struct buf *bp;
register 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 = cnp->cn_flags;
int nameiop = cnp->cn_nameiop;
struct proc *p = cnp->cn_proc;
#if REV_ENDIAN_FS
int rev_endian=0;
#endif
bp = NULL;
slotoffset = -1;
*vpp = NULL;
vdp = ap->a_dvp;
dp = VTOI(vdp);
lockparent = flags & LOCKPARENT;
wantparent = flags & (LOCKPARENT|WANTPARENT);
#if REV_ENDIAN_FS
rev_endian=(vdp->v_mount->mnt_flag & MNT_REVEND);
#endif
if ((dp->i_mode & IFMT) != IFDIR)
return (ENOTDIR);
if (error = VOP_ACCESS(vdp, VEXEC, cred, cnp->cn_proc))
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)) {
int vpid;
if (error == ENOENT)
return (error);
pdp = vdp;
dp = VTOI(*vpp);
vdp = *vpp;
vpid = vdp->v_id;
if (pdp == vdp) {
VREF(vdp);
error = 0;
} else if (flags & ISDOTDOT) {
VOP_UNLOCK(pdp, 0, p);
error = vget(vdp, LK_EXCLUSIVE, p);
if (!error && lockparent && (flags & ISLASTCN))
error = vn_lock(pdp, LK_EXCLUSIVE, p);
} else {
error = vget(vdp, LK_EXCLUSIVE, p);
if (!lockparent || error || !(flags & ISLASTCN))
VOP_UNLOCK(pdp, 0, p);
}
if (!error) {
if (vpid == vdp->v_id)
return (0);
vput(vdp);
if (lockparent && pdp != vdp && (flags & ISLASTCN))
VOP_UNLOCK(pdp, 0, p);
}
if (error = vn_lock(pdp, LK_EXCLUSIVE, p))
return (error);
vdp = pdp;
dp = VTOI(pdp);
*vpp = NULL;
}
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;
if (nameiop != LOOKUP || dp->i_diroff == 0 ||
dp->i_diroff > dp->i_size) {
entryoffsetinblock = 0;
dp->i_offset = 0;
numdirpasses = 1;
} else {
dp->i_offset = dp->i_diroff;
if ((entryoffsetinblock = dp->i_offset & bmask) &&
(error = VOP_BLKATOFF(vdp, (off_t)dp->i_offset, NULL, &bp)))
return (error);
numdirpasses = 2;
nchstats.ncs_2passes++;
}
prevoff = dp->i_offset;
endsearch = roundup(dp->i_size, DIRBLKSIZ);
enduseful = 0;
searchloop:
while (dp->i_offset < endsearch) {
if ((dp->i_offset & bmask) == 0) {
if (bp != NULL) {
#if REV_ENDIAN_FS
if (rev_endian)
byte_swap_dir_block_out(bp);
#endif
brelse(bp);
}
if (error =
VOP_BLKATOFF(vdp, (off_t)dp->i_offset, NULL, &bp))
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(FSFMT(vdp), 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) {
# if (BYTE_ORDER == LITTLE_ENDIAN)
if (vdp->v_mount->mnt_maxsymlinklen > 0)
namlen = ep->d_namlen;
else
namlen = ep->d_type;
# else
namlen = ep->d_namlen;
# endif
if (namlen == cnp->cn_namelen &&
!bcmp(cnp->cn_nameptr, ep->d_name,
(unsigned)namlen)) {
if (vdp->v_mount->mnt_maxsymlinklen > 0 &&
ep->d_type == DT_WHT) {
slotstatus = FOUND;
slotoffset = dp->i_offset;
slotsize = ep->d_reclen;
dp->i_reclen = slotsize;
enduseful = dp->i_size;
ap->a_cnp->cn_flags |= ISWHITEOUT;
numdirpasses--;
goto notfound;
}
dp->i_ino = ep->d_ino;
dp->i_reclen = ep->d_reclen;
#if REV_ENDIAN_FS
if (rev_endian)
byte_swap_dir_block_out(bp);
#endif
brelse(bp);
goto found;
}
}
prevoff = dp->i_offset;
dp->i_offset += ep->d_reclen;
entryoffsetinblock += ep->d_reclen;
if (ep->d_ino)
enduseful = dp->i_offset;
}
notfound:
if (numdirpasses == 2) {
numdirpasses--;
dp->i_offset = 0;
endsearch = dp->i_diroff;
goto searchloop;
}
if (bp != NULL) {
#if REV_ENDIAN_FS
if (rev_endian)
byte_swap_dir_block_out(bp);
#endif
brelse(bp);
}
if ((nameiop == CREATE || nameiop == RENAME ||
(nameiop == DELETE &&
(ap->a_cnp->cn_flags & DOWHITEOUT) &&
(ap->a_cnp->cn_flags & ISWHITEOUT))) &&
(flags & ISLASTCN) && dp->i_nlink != 0) {
if (error = VOP_ACCESS(vdp, VWRITE, cred, cnp->cn_proc))
return (error);
if (slotstatus == NONE) {
dp->i_offset = roundup(dp->i_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);
dp->i_flag |= IN_CHANGE | IN_UPDATE;
cnp->cn_flags |= SAVENAME;
if (!lockparent)
VOP_UNLOCK(vdp, 0, p);
return (EJUSTRETURN);
}
if ((cnp->cn_flags & MAKEENTRY) && nameiop != CREATE)
cache_enter(vdp, *vpp, cnp);
return (ENOENT);
found:
if (numdirpasses == 2)
nchstats.ncs_pass2++;
if (entryoffsetinblock + DIRSIZ(FSFMT(vdp), ep) > dp->i_size) {
ufs_dirbad(dp, dp->i_offset, "i_size too small");
dp->i_size = entryoffsetinblock + DIRSIZ(FSFMT(vdp), ep);
dp->i_flag |= IN_CHANGE | IN_UPDATE;
}
if ((flags & ISLASTCN) && nameiop == LOOKUP)
dp->i_diroff = dp->i_offset &~ (DIRBLKSIZ - 1);
if (nameiop == DELETE && (flags & ISLASTCN)) {
if (error = VOP_ACCESS(vdp, VWRITE, cred, cnp->cn_proc))
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);
}
if (error = VFS_VGET(vdp->v_mount, (void *)dp->i_ino, &tdp))
return (error);
if ((dp->i_mode & ISVTX) &&
cred->cr_uid != 0 &&
cred->cr_uid != dp->i_uid &&
tdp->v_type != VLNK &&
VTOI(tdp)->i_uid != cred->cr_uid) {
vput(tdp);
return (EPERM);
}
*vpp = tdp;
if (!lockparent)
VOP_UNLOCK(vdp, 0, p);
return (0);
}
if (nameiop == RENAME && wantparent && (flags & ISLASTCN)) {
if (error = VOP_ACCESS(vdp, VWRITE, cred, cnp->cn_proc))
return (error);
if (dp->i_number == dp->i_ino)
return (EISDIR);
if (error = VFS_VGET(vdp->v_mount, (void *)dp->i_ino, &tdp))
return (error);
*vpp = tdp;
cnp->cn_flags |= SAVENAME;
if (!lockparent)
VOP_UNLOCK(vdp, 0, p);
return (0);
}
pdp = vdp;
if (flags & ISDOTDOT) {
VOP_UNLOCK(pdp, 0, p);
if (error = VFS_VGET(vdp->v_mount, (void *)dp->i_ino, &tdp)) {
vn_lock(pdp, LK_EXCLUSIVE | LK_RETRY, p);
return (error);
}
if (lockparent && (flags & ISLASTCN) &&
(error = vn_lock(pdp, LK_EXCLUSIVE, p))) {
vput(tdp);
return (error);
}
*vpp = tdp;
} else if (dp->i_number == dp->i_ino) {
VREF(vdp);
*vpp = vdp;
} else {
if (error = VFS_VGET(vdp->v_mount, (void *)dp->i_ino, &tdp))
return (error);
if (!lockparent || !(flags & ISLASTCN))
VOP_UNLOCK(pdp, 0, p);
*vpp = tdp;
}
if (cnp->cn_flags & MAKEENTRY)
cache_enter(vdp, *vpp, cnp);
return (0);
}
void
ufs_dirbad(ip, offset, how)
struct inode *ip;
doff_t offset;
char *how;
{
struct mount *mp;
mp = ITOV(ip)->v_mount;
(void)printf("%s: bad dir ino %d 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(dp, ep, entryoffsetinblock)
struct vnode *dp;
register struct direct *ep;
int entryoffsetinblock;
{
register int i;
int namlen;
# if (BYTE_ORDER == LITTLE_ENDIAN)
if (dp->v_mount->mnt_maxsymlinklen > 0)
namlen = ep->d_namlen;
else
namlen = ep->d_type;
# else
namlen = ep->d_namlen;
# endif
if ((ep->d_reclen & 0x3) != 0 ||
ep->d_reclen > DIRBLKSIZ - (entryoffsetinblock & (DIRBLKSIZ - 1)) ||
ep->d_reclen < DIRSIZ(FSFMT(dp), 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);
}
int
ufs_direnter(ip, dvp, cnp)
struct inode *ip;
struct vnode *dvp;
register struct componentname *cnp;
{
register struct inode *dp;
struct direct newdir;
#if DIAGNOSTIC
if ((cnp->cn_flags & HASBUF) == 0)
panic("direnter: missing name");
#endif
dp = VTOI(dvp);
newdir.d_ino = ip->i_number;
newdir.d_namlen = cnp->cn_namelen;
bcopy(cnp->cn_nameptr, newdir.d_name, (unsigned)cnp->cn_namelen + 1);
if (dvp->v_mount->mnt_maxsymlinklen > 0)
newdir.d_type = IFTODT(ip->i_mode);
else {
newdir.d_type = 0;
# if (BYTE_ORDER == LITTLE_ENDIAN)
{ u_char tmp = newdir.d_namlen;
newdir.d_namlen = newdir.d_type;
newdir.d_type = tmp; }
# endif
}
return (ufs_direnter2(dvp, &newdir, cnp->cn_cred, cnp->cn_proc));
}
ufs_direnter2(dvp, dirp, cr, p)
struct vnode *dvp;
struct direct *dirp;
struct ucred *cr;
struct proc *p;
{
int newentrysize;
struct inode *dp;
struct buf *bp;
struct iovec aiov;
struct uio auio;
u_int dsize;
struct direct *ep, *nep;
int error, loc, spacefree;
char *dirbuf;
#if REV_ENDIAN_FS
struct mount *mp=dvp->v_mount;
int rev_endian=(mp->mnt_flag & MNT_REVEND);
#endif
dp = VTOI(dvp);
newentrysize = DIRSIZ(FSFMT(dvp), dirp);
if (dp->i_count == 0) {
if (dp->i_offset & (DIRBLKSIZ - 1))
panic("ufs_direnter2: newblk");
auio.uio_offset = dp->i_offset;
dirp->d_reclen = DIRBLKSIZ;
auio.uio_resid = newentrysize;
aiov.iov_len = newentrysize;
aiov.iov_base = (caddr_t)dirp;
auio.uio_iov = &aiov;
auio.uio_iovcnt = 1;
auio.uio_rw = UIO_WRITE;
auio.uio_segflg = UIO_SYSSPACE;
auio.uio_procp = (struct proc *)0;
error = VOP_WRITE(dvp, &auio, IO_SYNC, cr);
if (DIRBLKSIZ >
VFSTOUFS(dvp->v_mount)->um_mountp->mnt_stat.f_bsize)
panic("ufs_direnter2: frag size");
else if (!error) {
dp->i_size = roundup(dp->i_size, DIRBLKSIZ);
dp->i_flag |= IN_CHANGE;
}
return (error);
}
if (dp->i_offset + dp->i_count > dp->i_size)
dp->i_size = dp->i_offset + dp->i_count;
if (error = VOP_BLKATOFF(dvp, (off_t)dp->i_offset, &dirbuf, &bp))
return (error);
ep = (struct direct *)dirbuf;
dsize = DIRSIZ(FSFMT(dvp), ep);
spacefree = ep->d_reclen - dsize;
for (loc = ep->d_reclen; loc < dp->i_count; ) {
nep = (struct direct *)(dirbuf + loc);
if (ep->d_ino) {
ep->d_reclen = dsize;
ep = (struct direct *)((char *)ep + dsize);
} else {
spacefree += dsize;
}
dsize = DIRSIZ(FSFMT(dvp), nep);
spacefree += nep->d_reclen - dsize;
loc += nep->d_reclen;
bcopy((caddr_t)nep, (caddr_t)ep, dsize);
}
if (ep->d_ino == 0 ||
(ep->d_ino == WINO &&
bcmp(ep->d_name, dirp->d_name, dirp->d_namlen) == 0)) {
if (spacefree + dsize < newentrysize)
panic("ufs_direnter2: compact1");
dirp->d_reclen = spacefree + dsize;
} else {
if (spacefree < newentrysize)
panic("ufs_direnter2: compact2");
dirp->d_reclen = spacefree;
ep->d_reclen = dsize;
ep = (struct direct *)((char *)ep + dsize);
}
bcopy((caddr_t)dirp, (caddr_t)ep, (u_int)newentrysize);
#if REV_ENDIAN_FS
if (rev_endian)
byte_swap_dir_block_out(bp);
#endif
if (mp->mnt_flag & MNT_ASYNC) {
error = 0;
bdwrite(bp);
} else {
error = VOP_BWRITE(bp);
}
dp->i_flag |= IN_CHANGE | IN_UPDATE;
if (!error && dp->i_endoff && dp->i_endoff < dp->i_size)
error = VOP_TRUNCATE(dvp, (off_t)dp->i_endoff, IO_SYNC, cr, p);
return (error);
}
int
ufs_dirremove(dvp, cnp)
struct vnode *dvp;
struct componentname *cnp;
{
register struct inode *dp;
struct direct *ep;
struct buf *bp;
int error;
#if REV_ENDIAN_FS
struct mount *mp=dvp->v_mount;
int rev_endian=(mp->mnt_flag & MNT_REVEND);
#endif
dp = VTOI(dvp);
if (cnp->cn_flags & DOWHITEOUT) {
if (error =
VOP_BLKATOFF(dvp, (off_t)dp->i_offset, (char **)&ep, &bp))
return (error);
ep->d_ino = WINO;
ep->d_type = DT_WHT;
#if REV_ENDIAN_FS
if (rev_endian)
byte_swap_dir_block_out(bp);
#endif
if (mp->mnt_flag & MNT_ASYNC) {
error = 0;
bdwrite(bp);
} else {
error = VOP_BWRITE(bp);
}
dp->i_flag |= IN_CHANGE | IN_UPDATE;
return (error);
}
if (dp->i_count == 0) {
if (error =
VOP_BLKATOFF(dvp, (off_t)dp->i_offset, (char **)&ep, &bp))
return (error);
ep->d_ino = 0;
#if REV_ENDIAN_FS
if (rev_endian)
byte_swap_dir_block_out(bp);
#endif
if (mp->mnt_flag & MNT_ASYNC) {
error = 0;
bdwrite(bp);
} else {
error = VOP_BWRITE(bp);
}
dp->i_flag |= IN_CHANGE | IN_UPDATE;
return (error);
}
if (error = VOP_BLKATOFF(dvp, (off_t)(dp->i_offset - dp->i_count),
(char **)&ep, &bp))
return (error);
ep->d_reclen += dp->i_reclen;
#if REV_ENDIAN_FS
if (rev_endian)
byte_swap_dir_block_out(bp);
#endif
if (mp->mnt_flag & MNT_ASYNC) {
error = 0;
bdwrite(bp);
} else {
error = VOP_BWRITE(bp);
}
dp->i_flag |= IN_CHANGE | IN_UPDATE;
return (error);
}
int
ufs_dirrewrite(dp, ip, cnp)
struct inode *dp, *ip;
struct componentname *cnp;
{
struct buf *bp;
struct direct *ep;
struct vnode *vdp = ITOV(dp);
int error;
if (error = VOP_BLKATOFF(vdp, (off_t)dp->i_offset, (char **)&ep, &bp))
return (error);
ep->d_ino = ip->i_number;
if (vdp->v_mount->mnt_maxsymlinklen > 0)
ep->d_type = IFTODT(ip->i_mode);
#if REV_ENDIAN_FS
if (vdp->v_mount->mnt_flag & MNT_REVEND)
byte_swap_dir_block_out(bp);
#endif
if (vdp->v_mount->mnt_flag & MNT_ASYNC) {
error = 0;
bdwrite(bp);
} else {
error = VOP_BWRITE(bp);
}
dp->i_flag |= IN_CHANGE | IN_UPDATE;
return (error);
}
int
ufs_dirempty(ip, parentino, cred)
register struct inode *ip;
ino_t parentino;
struct ucred *cred;
{
register off_t off;
struct dirtemplate dbuf;
register struct direct *dp = (struct direct *)&dbuf;
int error, count, namlen;
#if REV_ENDIAN_FS
struct vnode *vp=ITOV(ip);
struct mount *mp=vp->v_mount;
int rev_endian=(mp->mnt_flag & MNT_REVEND);
#endif
#define MINDIRSIZ (sizeof (struct dirtemplate) / 2)
for (off = 0; off < ip->i_size; off += dp->d_reclen) {
error = vn_rdwr(UIO_READ, ITOV(ip), (caddr_t)dp, MINDIRSIZ, off,
UIO_SYSSPACE, IO_NODELOCKED, cred, &count, (struct proc *)0);
if (error || count != 0)
return (0);
#if 0
if (rev_endian)
byte_swap_minidir_in(dp);
#endif
if (dp->d_reclen == 0)
return (0);
if (dp->d_ino == 0 || dp->d_ino == WINO)
continue;
# if (BYTE_ORDER == LITTLE_ENDIAN)
if (ITOV(ip)->v_mount->mnt_maxsymlinklen > 0)
namlen = dp->d_namlen;
else
namlen = dp->d_type;
# else
namlen = dp->d_namlen;
# endif
if (namlen > 2)
return (0);
if (dp->d_name[0] != '.')
return (0);
if (namlen == 1)
continue;
if (dp->d_name[1] == '.' && dp->d_ino == parentino)
continue;
return (0);
}
return (1);
}
int
ufs_checkpath(source, target, cred)
struct inode *source, *target;
struct ucred *cred;
{
struct vnode *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, (int *)0, (struct proc *)0);
if (error != 0)
break;
# if (BYTE_ORDER == LITTLE_ENDIAN)
if (vp->v_mount->mnt_maxsymlinklen > 0)
namlen = dirbuf.dotdot_namlen;
else
namlen = dirbuf.dotdot_type;
# else
namlen = dirbuf.dotdot_namlen;
# endif
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;
vput(vp);
if (error = VFS_VGET(vp->v_mount, (void *)dirbuf.dotdot_ino, &vp)) {
vp = NULL;
break;
}
}
out:
if (error == ENOTDIR)
printf("checkpath: .. not a directory\n");
if (vp != NULL)
vput(vp);
return (error);
}