#include "opt_ufs.h"
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/kernel.h>
#include <sys/buf.h>
#include <sys/proc.h>
#include <sys/namei.h>
#include <sys/stat.h>
#include <sys/mount.h>
#include <sys/vnode.h>
#include <sys/sysctl.h>
#include <vm/vm.h>
#include <vm/vm_extern.h>
#include "quota.h"
#include "inode.h"
#include "dir.h"
#ifdef UFS_DIRHASH
#include "dirhash.h"
#endif
#include "ufsmount.h"
#include "ufs_extern.h"
#include "ffs_extern.h"
#ifdef DIAGNOSTIC
int dirchk = 1;
#else
int dirchk = 0;
#endif
SYSCTL_INT(_debug, OID_AUTO, dircheck, CTLFLAG_RW, &dirchk, 0,
"Enable full validation checks of directory");
#define OFSFMT(vp) ((vp)->v_mount->mnt_maxsymlinklen <= 0)
int
ufs_lookup(struct vop_old_lookup_args *ap)
{
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 = cnp->cn_flags;
int nameiop = cnp->cn_nameiop;
bp = NULL;
slotoffset = -1;
cnp->cn_flags &= ~CNP_PDIRUNLOCK;
*vpp = NULL;
vdp = ap->a_dvp;
dp = VTOI(vdp);
lockparent = flags & CNP_LOCKPARENT;
wantparent = flags & (CNP_LOCKPARENT|CNP_WANTPARENT);
slotstatus = FOUND;
slotfreespace = slotsize = slotneeded = 0;
if (nameiop == NAMEI_CREATE || nameiop == NAMEI_RENAME) {
slotstatus = NONE;
slotneeded = DIRECTSIZ(cnp->cn_namelen);
}
bmask = VFSTOUFS(vdp->v_mount)->um_mountp->mnt_stat.f_iosize - 1;
#ifdef UFS_DIRHASH
if (ufsdirhash_build(dp) == 0) {
enduseful = dp->i_size;
if (slotstatus != FOUND) {
slotoffset = ufsdirhash_findfree(dp, slotneeded,
&slotsize);
if (slotoffset >= 0) {
slotstatus = COMPACT;
enduseful = ufsdirhash_enduseful(dp);
if (enduseful < 0)
enduseful = dp->i_size;
}
}
numdirpasses = 1;
entryoffsetinblock = 0;
switch (ufsdirhash_lookup(dp, cnp->cn_nameptr, cnp->cn_namelen,
&dp->i_offset, &bp, nameiop == NAMEI_DELETE ? &prevoff : NULL)) {
case 0:
ep = (struct direct *)((char *)bp->b_data +
(dp->i_offset & bmask));
goto foundentry;
case ENOENT:
dp->i_offset = roundup2(dp->i_size, DIRBLKSIZ);
goto notfound;
default:
break;
}
}
#endif
if (nameiop != NAMEI_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 = ffs_blkatoff(vdp, (off_t)dp->i_offset, NULL, &bp)))
return (error);
numdirpasses = 2;
}
prevoff = dp->i_offset;
endsearch = roundup2(dp->i_size, DIRBLKSIZ);
enduseful = 0;
searchloop:
while (dp->i_offset < endsearch) {
if ((dp->i_offset & bmask) == 0) {
if (bp != NULL)
brelse(bp);
error =
ffs_blkatoff(vdp, (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 || ep->d_reclen >
DIRBLKSIZ - (entryoffsetinblock & (DIRBLKSIZ - 1)) ||
(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(OFSFMT(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 (OFSFMT(vdp))
namlen = ep->d_type;
else
namlen = ep->d_namlen;
# else
namlen = ep->d_namlen;
# endif
if (namlen == cnp->cn_namelen &&
(cnp->cn_nameptr[0] == ep->d_name[0]) &&
!bcmp(cnp->cn_nameptr, ep->d_name,
(unsigned)namlen)) {
#ifdef UFS_DIRHASH
foundentry:
#endif
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 |= CNP_ISWHITEOUT;
numdirpasses--;
goto notfound;
}
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;
}
notfound:
if (numdirpasses == 2) {
numdirpasses--;
dp->i_offset = 0;
endsearch = dp->i_diroff;
goto searchloop;
}
if (bp != NULL)
brelse(bp);
if ((nameiop == NAMEI_CREATE || nameiop == NAMEI_RENAME ||
(nameiop == NAMEI_DELETE &&
(ap->a_cnp->cn_flags & CNP_DOWHITEOUT) &&
(ap->a_cnp->cn_flags & CNP_ISWHITEOUT))) &&
dp->i_effnlink != 0) {
error = VOP_EACCESS(vdp, VWRITE, cred);
if (error)
return (error);
if (slotstatus == NONE) {
dp->i_offset = roundup2(dp->i_size, DIRBLKSIZ);
dp->i_count = 0;
enduseful = dp->i_offset;
} else if (nameiop == NAMEI_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 = roundup2(enduseful, DIRBLKSIZ);
dp->i_flag |= IN_CHANGE | IN_UPDATE;
if (!lockparent) {
vn_unlock(vdp);
cnp->cn_flags |= CNP_PDIRUNLOCK;
}
return (EJUSTRETURN);
}
return (ENOENT);
found:
if (dp->i_offset + DIRSIZ(OFSFMT(vdp), ep) > dp->i_size) {
ufs_dirbad(dp, dp->i_offset, "i_size too small");
dp->i_size = dp->i_offset + DIRSIZ(OFSFMT(vdp), ep);
dp->i_flag |= IN_CHANGE | IN_UPDATE;
}
brelse(bp);
if (nameiop == NAMEI_LOOKUP)
dp->i_diroff = rounddown2(dp->i_offset, DIRBLKSIZ);
if (nameiop == NAMEI_DELETE) {
error = VOP_EACCESS(vdp, VWRITE, cred);
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);
}
if (flags & CNP_ISDOTDOT)
vn_unlock(vdp);
error = VFS_VGET(vdp->v_mount, NULL, dp->i_ino, &tdp);
if (flags & CNP_ISDOTDOT) {
if (vn_lock(vdp, LK_EXCLUSIVE | LK_RETRY |
LK_FAILRECLAIM) != 0) {
cnp->cn_flags |= CNP_PDIRUNLOCK;
}
}
if (error)
return (error);
if ((dp->i_mode & ISVTX) &&
cred->cr_uid != 0 &&
cred->cr_uid != dp->i_uid &&
VTOI(tdp)->i_uid != cred->cr_uid) {
vput(tdp);
return (EPERM);
}
*vpp = tdp;
if (!lockparent) {
vn_unlock(vdp);
cnp->cn_flags |= CNP_PDIRUNLOCK;
}
return (0);
}
if (nameiop == NAMEI_RENAME && wantparent) {
if ((error = VOP_EACCESS(vdp, VWRITE, cred)) != 0)
return (error);
if (dp->i_number == dp->i_ino)
return (EISDIR);
if (flags & CNP_ISDOTDOT)
vn_unlock(vdp);
error = VFS_VGET(vdp->v_mount, NULL, dp->i_ino, &tdp);
if (flags & CNP_ISDOTDOT) {
if (vn_lock(vdp, LK_EXCLUSIVE | LK_RETRY |
LK_FAILRECLAIM) != 0) {
cnp->cn_flags |= CNP_PDIRUNLOCK;
}
}
if (error)
return (error);
*vpp = tdp;
if (!lockparent) {
vn_unlock(vdp);
cnp->cn_flags |= CNP_PDIRUNLOCK;
}
return (0);
}
pdp = vdp;
if (flags & CNP_ISDOTDOT) {
vn_unlock(pdp);
cnp->cn_flags |= CNP_PDIRUNLOCK;
error = VFS_VGET(vdp->v_mount, NULL, dp->i_ino, &tdp);
if (error) {
if (vn_lock(pdp, LK_EXCLUSIVE | LK_RETRY |
LK_FAILRECLAIM) == 0) {
cnp->cn_flags &= ~CNP_PDIRUNLOCK;
}
return (error);
}
if (lockparent) {
error = vn_lock(pdp, LK_EXCLUSIVE | LK_FAILRECLAIM);
if (error) {
vput(tdp);
return (error);
}
cnp->cn_flags &= ~CNP_PDIRUNLOCK;
}
*vpp = tdp;
} else if (dp->i_number == dp->i_ino) {
vref(vdp);
*vpp = vdp;
} else {
error = VFS_VGET(vdp->v_mount, NULL, dp->i_ino, &tdp);
if (error)
return (error);
if (!lockparent) {
vn_unlock(pdp);
cnp->cn_flags |= CNP_PDIRUNLOCK;
}
*vpp = tdp;
}
return (0);
}
void
ufs_dirbad(struct inode *ip, doff_t offset, char *how)
{
struct mount *mp;
mp = ITOV(ip)->v_mount;
(void)kprintf("%s: bad dir ino %lu at offset %ld: %s\n",
mp->mnt_stat.f_mntfromname, (u_long)ip->i_number, (long)offset, how);
if ((mp->mnt_flag & MNT_RDONLY) == 0)
panic("ufs_dirbad: bad dir");
}
int
ufs_dirbadentry(struct vnode *dp, struct direct *ep, int entryoffsetinblock)
{
int i;
int namlen;
# if (BYTE_ORDER == LITTLE_ENDIAN)
if (OFSFMT(dp))
namlen = ep->d_type;
else
namlen = ep->d_namlen;
# else
namlen = ep->d_namlen;
# endif
if ((ep->d_reclen & 0x3) != 0 ||
ep->d_reclen > DIRBLKSIZ - (entryoffsetinblock & (DIRBLKSIZ - 1)) ||
ep->d_reclen < DIRSIZ(OFSFMT(dp), ep) || namlen > MAXNAMLEN) {
kprintf("First bad\n");
goto bad;
}
if (ep->d_ino == 0)
return (0);
for (i = 0; i < namlen; i++)
if (ep->d_name[i] == '\0') {
kprintf("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)
{
newdirp->d_ino = ip->i_number;
newdirp->d_namlen = cnp->cn_namelen;
bcopy(cnp->cn_nameptr, newdirp->d_name, (unsigned)cnp->cn_namelen + 1);
if (ITOV(ip)->v_mount->mnt_maxsymlinklen > 0)
newdirp->d_type = IFTODT(ip->i_mode);
else {
newdirp->d_type = 0;
# if (BYTE_ORDER == LITTLE_ENDIAN)
{ u_char tmp = newdirp->d_namlen;
newdirp->d_namlen = newdirp->d_type;
newdirp->d_type = tmp; }
# endif
}
}
int
ufs_direnter(struct vnode *dvp, struct vnode *tvp, struct direct *dirp,
struct componentname *cnp, struct buf *newdirbp)
{
struct ucred *cred;
int newentrysize;
struct inode *dp;
struct buf *bp;
uint dsize;
struct direct *ep, *nep;
int error, ret, blkoff, loc, spacefree, flags;
char *dirbuf;
cred = cnp->cn_cred;
KKASSERT(cred != NULL);
dp = VTOI(dvp);
newentrysize = DIRSIZ(OFSFMT(dvp), dirp);
if (dp->i_count == 0) {
if (dp->i_offset & (DIRBLKSIZ - 1))
panic("ufs_direnter: newblk");
nvnode_pager_setsize(dvp, dp->i_offset + DIRBLKSIZ,
DIRBLKSIZ, -1);
flags = B_CLRBUF;
if (!DOINGSOFTDEP(dvp) && !DOINGASYNC(dvp))
flags |= B_SYNC;
if ((error = VOP_BALLOC(dvp, (off_t)dp->i_offset, DIRBLKSIZ,
cred, flags, &bp)) != 0) {
if (DOINGSOFTDEP(dvp) && newdirbp != NULL)
bdwrite(newdirbp);
return (error);
}
dp->i_size = dp->i_offset + DIRBLKSIZ;
dp->i_flag |= IN_CHANGE | IN_UPDATE;
dirp->d_reclen = DIRBLKSIZ;
blkoff = dp->i_offset &
(VFSTOUFS(dvp->v_mount)->um_mountp->mnt_stat.f_iosize - 1);
bcopy((caddr_t)dirp, (caddr_t)bp->b_data + blkoff,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
if (DOINGSOFTDEP(dvp)) {
blkoff += DIRBLKSIZ;
while (blkoff < bp->b_bcount) {
((struct direct *)
(bp->b_data + blkoff))->d_reclen = DIRBLKSIZ;
blkoff += DIRBLKSIZ;
}
softdep_setup_directory_add(bp, dp, dp->i_offset,
dirp->d_ino, newdirbp);
bdwrite(bp);
return (ffs_update(dvp, 0));
}
if (DOINGASYNC(dvp)) {
bdwrite(bp);
return (ffs_update(dvp, 0));
}
error = bwrite(bp);
ret = ffs_update(dvp, 1);
if (error == 0)
return (ret);
return (error);
}
if (dp->i_offset + dp->i_count > dp->i_size)
dp->i_size = dp->i_offset + dp->i_count;
error = ffs_blkatoff(dvp, (off_t)dp->i_offset, &dirbuf, &bp);
if (error) {
if (DOINGSOFTDEP(dvp) && newdirbp != NULL)
bdwrite(newdirbp);
return (error);
}
ep = (struct direct *)dirbuf;
dsize = ep->d_ino ? DIRSIZ(OFSFMT(dvp), 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(OFSFMT(dvp), 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
if (DOINGSOFTDEP(dvp))
softdep_change_directoryentry_offset(dp, dirbuf,
(caddr_t)nep, (caddr_t)ep, dsize);
else
bcopy((caddr_t)nep, (caddr_t)ep, dsize);
}
if (ep->d_ino == 0 ||
(ep->d_ino == UFS_WINO &&
bcmp(ep->d_name, dirp->d_name, dirp->d_namlen) == 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
bcopy((caddr_t)dirp, (caddr_t)ep, (uint)newentrysize);
#ifdef UFS_DIRHASH
if (dp->i_dirhash != NULL)
ufsdirhash_checkblock(dp, dirbuf -
(dp->i_offset & (DIRBLKSIZ - 1)),
rounddown2(dp->i_offset, DIRBLKSIZ));
#endif
if (DOINGSOFTDEP(dvp)) {
softdep_setup_directory_add(bp, dp,
dp->i_offset + (caddr_t)ep - dirbuf, dirp->d_ino, newdirbp);
bdwrite(bp);
} else {
if (DOINGASYNC(dvp)) {
bdwrite(bp);
error = 0;
} else {
error = bwrite(bp);
}
}
dp->i_flag |= IN_CHANGE | IN_UPDATE;
if (error == 0 && dp->i_endoff && dp->i_endoff < dp->i_size) {
if (tvp != NULL)
vn_unlock(tvp);
#ifdef UFS_DIRHASH
if (dp->i_dirhash != NULL)
ufsdirhash_dirtrunc(dp, dp->i_endoff);
#endif
(void)ffs_truncate(dvp, (off_t)dp->i_endoff, IO_SYNC, cred);
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 (flags & CNP_DOWHITEOUT) {
if ((error =
ffs_blkatoff(dvp, (off_t)dp->i_offset, (char **)&ep, &bp)) != 0)
return (error);
ep->d_ino = UFS_WINO;
ep->d_type = DT_WHT;
goto out;
}
if ((error = ffs_blkatoff(dvp,
(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)),
rounddown2(dp->i_offset, DIRBLKSIZ));
#endif
out:
if (DOINGSOFTDEP(dvp)) {
if (ip) {
ip->i_effnlink--;
softdep_change_linkcnt(ip);
softdep_setup_remove(bp, dp, ip, isrmdir);
}
if (softdep_slowdown(dvp)) {
error = bwrite(bp);
} else {
bdwrite(bp);
error = 0;
}
} else {
if (ip) {
ip->i_effnlink--;
ip->i_nlink--;
ip->i_flag |= IN_CHANGE;
}
if (flags & CNP_DOWHITEOUT)
error = bwrite(bp);
else 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, ino_t newinum, int newtype,
int isrmdir)
{
struct buf *bp;
struct direct *ep;
struct vnode *vdp = ITOV(dp);
int error;
error = ffs_blkatoff(vdp, (off_t)dp->i_offset, (char **)&ep, &bp);
if (error)
return (error);
ep->d_ino = newinum;
if (!OFSFMT(vdp))
ep->d_type = newtype;
oip->i_effnlink--;
if (DOINGSOFTDEP(vdp)) {
softdep_change_linkcnt(oip);
softdep_setup_directory_change(bp, dp, oip, newinum, isrmdir);
bdwrite(bp);
} else {
oip->i_nlink--;
oip->i_flag |= IN_CHANGE;
if (DOINGASYNC(vdp)) {
bdwrite(bp);
error = 0;
} else {
error = bwrite(bp);
}
}
dp->i_flag |= IN_CHANGE | IN_UPDATE;
return (error);
}
int
ufs_dirempty(struct inode *ip, ino_t parentino, struct ucred *cred)
{
off_t off;
struct dirtemplate dbuf;
struct direct *dp = (struct direct *)&dbuf;
int error, count, namlen;
#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);
if (error || count != 0)
return (0);
if (dp->d_reclen == 0)
return (0);
if (dp->d_ino == 0 || dp->d_ino == UFS_WINO)
continue;
# if (BYTE_ORDER == LITTLE_ENDIAN)
if (OFSFMT(ITOV(ip)))
namlen = dp->d_type;
else
namlen = dp->d_namlen;
# else
namlen = dp->d_namlen;
# endif
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 *vp;
int error, rootino, namlen;
struct dirtemplate dirbuf;
vp = ITOV(target);
if (target->i_number == source->i_number) {
error = EEXIST;
goto out;
}
rootino = UFS_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);
if (error != 0)
break;
# if (BYTE_ORDER == LITTLE_ENDIAN)
if (OFSFMT(vp))
namlen = dirbuf.dotdot_type;
else
namlen = dirbuf.dotdot_namlen;
# 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);
error = VFS_VGET(vp->v_mount, NULL, dirbuf.dotdot_ino, &vp);
if (error) {
vp = NULL;
break;
}
}
out:
if (error == ENOTDIR)
kprintf("checkpath: .. not a directory\n");
if (vp != NULL)
vput(vp);
return (error);
}