#include <sys/cdefs.h>
#include "opt_ufs.h"
#include "opt_quota.h"
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/kernel.h>
#include <sys/namei.h>
#include <sys/bio.h>
#include <sys/buf.h>
#include <sys/proc.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 <ufs/ufs/extattr.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>
#include <ufs/ffs/ffs_extern.h>
#ifdef DIAGNOSTIC
static int dirchk = 1;
#else
static int dirchk = 0;
#endif
SYSCTL_INT(_debug, OID_AUTO, dircheck, CTLFLAG_RW, &dirchk, 0, "");
static int
ufs_delete_denied(struct vnode *vdp, struct vnode *tdp, struct ucred *cred,
struct thread *td)
{
int error;
#ifdef UFS_ACL
error = VOP_ACCESS(vdp, VEXEC, cred, td);
if (error)
return (error);
error = VOP_ACCESSX(tdp, VDELETE, cred, td);
if (error == 0)
return (0);
error = VOP_ACCESSX(vdp, VDELETE_CHILD, cred, td);
if (error == 0)
return (0);
error = VOP_ACCESSX(vdp, VEXPLICIT_DENY | VDELETE_CHILD, cred, td);
if (error)
return (error);
#endif
error = VOP_ACCESS(vdp, VWRITE, cred, td);
if (error)
return (error);
if ((VTOI(vdp)->i_mode & ISVTX) &&
VOP_ACCESS(vdp, VADMIN, cred, td) &&
VOP_ACCESS(tdp, VADMIN, cred, td))
return (EPERM);
return (0);
}
int
ufs_lookup(
struct vop_cachedlookup_args
*ap)
{
return (ufs_lookup_ino(ap->a_dvp, ap->a_vpp, ap->a_cnp, NULL));
}
int
ufs_lookup_ino(struct vnode *vdp, struct vnode **vpp, struct componentname *cnp,
ino_t *dd_ino)
{
struct inode *dp;
struct buf *bp;
struct direct *ep;
int entryoffsetinblock;
enum {NONE, COMPACT, FOUND} slotstatus;
doff_t slotoffset;
doff_t i_diroff;
doff_t i_offset;
int slotsize;
int slotfreespace;
int slotneeded;
int numdirpasses;
doff_t endsearch;
doff_t prevoff;
struct vnode *pdp;
struct vnode *tdp;
doff_t enduseful;
uint64_t bmask;
int namlen, error;
struct ucred *cred = cnp->cn_cred;
int flags = cnp->cn_flags;
int nameiop = cnp->cn_nameiop;
ino_t ino, ino1;
int ltype;
if (vpp != NULL)
*vpp = NULL;
dp = VTOI(vdp);
if (dp->i_effnlink == 0)
return (ENOENT);
vnode_create_vobject(vdp, DIP(dp, i_size), curthread);
bmask = VFSTOUFS(vdp->v_mount)->um_mountp->mnt_stat.f_iosize - 1;
ASSERT_VOP_LOCKED(vdp, "ufs_lookup1");
if ((nameiop == CREATE || nameiop == DELETE || nameiop == RENAME) &&
(flags & (LOCKPARENT | ISLASTCN)) == (LOCKPARENT | ISLASTCN))
ASSERT_VOP_ELOCKED(vdp, "ufs_lookup2");
restart:
bp = NULL;
slotoffset = -1;
ino = 0;
i_diroff = dp->i_diroff;
slotstatus = FOUND;
slotfreespace = slotsize = slotneeded = 0;
if ((nameiop == CREATE || nameiop == RENAME) &&
(flags & ISLASTCN)) {
slotstatus = NONE;
slotneeded = DIRECTSIZ(cnp->cn_namelen);
}
#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,
&i_offset, &bp, nameiop == DELETE ? &prevoff : NULL)) {
case 0:
ep = (struct direct *)((char *)bp->b_data +
(i_offset & bmask));
goto foundentry;
case ENOENT:
i_offset = roundup2(dp->i_size, DIRBLKSIZ);
goto notfound;
default:
break;
}
}
#endif
if (nameiop != LOOKUP || i_diroff == 0 || i_diroff >= dp->i_size) {
entryoffsetinblock = 0;
i_offset = 0;
numdirpasses = 1;
} else {
i_offset = i_diroff;
if ((entryoffsetinblock = i_offset & bmask) &&
(error = UFS_BLKATOFF(vdp, (off_t)i_offset, NULL, &bp)))
return (error);
numdirpasses = 2;
nchstats.ncs_2passes++;
}
prevoff = i_offset;
endsearch = roundup2(dp->i_size, DIRBLKSIZ);
enduseful = 0;
searchloop:
while (i_offset < endsearch) {
if ((i_offset & bmask) == 0) {
if (bp != NULL)
brelse(bp);
error =
UFS_BLKATOFF(vdp, (off_t)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, i_offset, "mangled entry");
i = DIRBLKSIZ - (entryoffsetinblock & (DIRBLKSIZ - 1));
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 = i_offset;
slotsize = ep->d_reclen;
} else if (slotstatus == NONE) {
slotfreespace += size;
if (slotoffset == -1)
slotoffset = i_offset;
if (slotfreespace >= slotneeded) {
slotstatus = COMPACT;
slotsize = 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 (!OFSFMT(vdp) && ep->d_type == DT_WHT) {
slotstatus = FOUND;
slotoffset = i_offset;
slotsize = ep->d_reclen;
enduseful = dp->i_size;
cnp->cn_flags |= ISWHITEOUT;
numdirpasses--;
goto notfound;
}
ino = ep->d_ino;
goto found;
}
}
prevoff = i_offset;
i_offset += ep->d_reclen;
entryoffsetinblock += ep->d_reclen;
if (ep->d_ino)
enduseful = i_offset;
}
notfound:
if (numdirpasses == 2) {
numdirpasses--;
i_offset = 0;
endsearch = i_diroff;
goto searchloop;
}
if (bp != NULL)
brelse(bp);
if ((nameiop == CREATE || nameiop == RENAME ||
(nameiop == DELETE &&
(cnp->cn_flags & DOWHITEOUT) &&
(cnp->cn_flags & ISWHITEOUT))) &&
(flags & ISLASTCN) && dp->i_effnlink != 0) {
if (flags & WILLBEDIR)
error = VOP_ACCESSX(vdp, VWRITE | VAPPEND, cred, curthread);
else
error = VOP_ACCESS(vdp, VWRITE, cred, curthread);
if (error)
return (error);
if (slotstatus == NONE) {
SET_I_OFFSET(dp, roundup2(dp->i_size, DIRBLKSIZ));
SET_I_COUNT(dp, 0);
enduseful = I_OFFSET(dp);
} else if (nameiop == DELETE) {
SET_I_OFFSET(dp, slotoffset);
if ((I_OFFSET(dp) & (DIRBLKSIZ - 1)) == 0)
SET_I_COUNT(dp, 0);
else
SET_I_COUNT(dp, I_OFFSET(dp) - prevoff);
} else {
SET_I_OFFSET(dp, slotoffset);
SET_I_COUNT(dp, slotsize);
if (enduseful < slotoffset + slotsize)
enduseful = slotoffset + slotsize;
}
SET_I_ENDOFF(dp, roundup2(enduseful, DIRBLKSIZ));
return (EJUSTRETURN);
}
if ((cnp->cn_flags & MAKEENTRY) != 0)
cache_enter(vdp, NULL, cnp);
return (ENOENT);
found:
if (dd_ino != NULL)
*dd_ino = ino;
if (numdirpasses == 2)
nchstats.ncs_pass2++;
if (i_offset + DIRSIZ(OFSFMT(vdp), ep) > dp->i_size) {
ufs_dirbad(dp, i_offset, "i_size too small");
brelse(bp);
return (EIO);
}
brelse(bp);
if ((flags & ISLASTCN) && nameiop == LOOKUP)
dp->i_diroff = rounddown2(i_offset, DIRBLKSIZ);
if (nameiop == DELETE && (flags & ISLASTCN)) {
if (flags & LOCKPARENT)
ASSERT_VOP_ELOCKED(vdp, __FUNCTION__);
if (VOP_ISLOCKED(vdp) == LK_EXCLUSIVE) {
SET_I_OFFSET(dp, i_offset);
if ((I_OFFSET(dp) & (DIRBLKSIZ - 1)) == 0)
SET_I_COUNT(dp, 0);
else
SET_I_COUNT(dp, I_OFFSET(dp) - prevoff);
}
if (dd_ino != NULL)
return (0);
if ((error = VFS_VGET(vdp->v_mount, ino,
LK_EXCLUSIVE, &tdp)) != 0)
return (error);
error = ufs_delete_denied(vdp, tdp, cred, curthread);
if (error) {
vput(tdp);
return (error);
}
if (dp->i_number == ino) {
vref(vdp);
*vpp = vdp;
vput(tdp);
return (0);
}
*vpp = tdp;
return (0);
}
if (nameiop == RENAME && (flags & ISLASTCN)) {
if (flags & WILLBEDIR)
error = VOP_ACCESSX(vdp, VWRITE | VAPPEND, cred, curthread);
else
error = VOP_ACCESS(vdp, VWRITE, cred, curthread);
if (error)
return (error);
SET_I_OFFSET(dp, i_offset);
if (dp->i_number == ino)
return (EISDIR);
if (dd_ino != NULL)
return (0);
if ((error = VFS_VGET(vdp->v_mount, ino,
LK_EXCLUSIVE, &tdp)) != 0)
return (error);
error = ufs_delete_denied(vdp, tdp, cred, curthread);
if (error) {
vput(tdp);
return (error);
}
#ifdef SunOS_doesnt_do_that
if (tdp->v_type == VDIR)
error = VOP_ACCESSX(vdp, VWRITE | VAPPEND, cred, curthread);
else
error = VOP_ACCESS(vdp, VWRITE, cred, curthread);
if (error) {
vput(tdp);
return (error);
}
#endif
*vpp = tdp;
return (0);
}
if (dd_ino != NULL)
return (0);
pdp = vdp;
if (flags & ISDOTDOT) {
error = vn_vget_ino(pdp, ino, cnp->cn_lkflags, &tdp);
if (error)
return (error);
error = ufs_lookup_ino(pdp, NULL, cnp, &ino1);
if (error) {
vput(tdp);
return (error);
}
if (ino1 != ino) {
vput(tdp);
goto restart;
}
*vpp = tdp;
} else if (dp->i_number == ino) {
vref(vdp);
ltype = cnp->cn_lkflags & LK_TYPE_MASK;
if (ltype != VOP_ISLOCKED(vdp)) {
if (ltype == LK_EXCLUSIVE)
vn_lock(vdp, LK_UPGRADE | LK_RETRY);
else
vn_lock(vdp, LK_DOWNGRADE | LK_RETRY);
if (VN_IS_DOOMED(vdp)) {
vrele(vdp);
return (ENOENT);
}
}
*vpp = vdp;
} else {
error = VFS_VGET(pdp->v_mount, ino, cnp->cn_lkflags, &tdp);
if (error == 0 && VTOI(tdp)->i_mode == 0) {
vgone(tdp);
vput(tdp);
error = ENOENT;
}
if (error)
return (error);
*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)
{
(void)printf("%s: bad dir ino %ju at offset %ld: %s\n",
ITOV(ip)->v_mount->mnt_stat.f_mntonname, (uintmax_t)ip->i_number,
(long)offset, how);
}
int
ufs_dirbadentry(struct vnode *dp, struct direct *ep, int entryoffsetinblock)
{
int i, 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 > UFS_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)
{
uint64_t namelen;
namelen = (unsigned)cnp->cn_namelen;
KASSERT(namelen <= UFS_MAXNAMLEN,
("ufs_makedirentry: name too long"));
newdirp->d_ino = ip->i_number;
newdirp->d_namlen = namelen;
*(uint32_t *)(&newdirp->d_name[namelen & ~(DIR_ROUNDUP - 1)]) = 0;
bcopy(cnp->cn_nameptr, newdirp->d_name, namelen);
if (!OFSFMT(ITOV(ip)))
newdirp->d_type = IFTODT(ip->i_mode);
else {
newdirp->d_type = 0;
# if (BYTE_ORDER == LITTLE_ENDIAN)
{ uint8_t 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 *cr;
struct thread *td;
int newentrysize;
struct inode *dp;
struct buf *bp;
uint64_t dsize;
struct direct *ep, *nep;
uint64_t old_isize;
int error, ret, blkoff, loc, spacefree, flags, namlen;
char *dirbuf;
td = curthread;
cr = td->td_ucred;
dp = VTOI(dvp);
newentrysize = DIRSIZ(OFSFMT(dvp), dirp);
if (I_COUNT(dp) == 0) {
if (I_OFFSET(dp) & (DIRBLKSIZ - 1))
panic("ufs_direnter: newblk");
flags = BA_CLRBUF;
if (!DOINGSOFTDEP(dvp) && !DOINGASYNC(dvp))
flags |= IO_SYNC;
#ifdef QUOTA
if ((error = getinoquota(dp)) != 0) {
if (DOINGSOFTDEP(dvp) && newdirbp != NULL)
bdwrite(newdirbp);
return (error);
}
#endif
old_isize = dp->i_size;
vnode_pager_setsize(dvp,
(vm_ooffset_t)I_OFFSET(dp) + DIRBLKSIZ);
if ((error = UFS_BALLOC(dvp, (off_t)I_OFFSET(dp), DIRBLKSIZ,
cr, flags, &bp)) != 0) {
if (DOINGSOFTDEP(dvp) && newdirbp != NULL)
bdwrite(newdirbp);
vnode_pager_setsize(dvp, (vm_ooffset_t)old_isize);
return (error);
}
dp->i_size = I_OFFSET(dp) + DIRBLKSIZ;
DIP_SET(dp, i_size, dp->i_size);
SET_I_ENDOFF(dp, dp->i_size);
UFS_INODE_SET_FLAG(dp, IN_SIZEMOD | IN_CHANGE | IN_UPDATE);
dirp->d_reclen = DIRBLKSIZ;
blkoff = I_OFFSET(dp) &
(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, I_OFFSET(dp));
ufsdirhash_add(dp, dirp, I_OFFSET(dp));
ufsdirhash_checkblock(dp, (char *)bp->b_data + blkoff,
I_OFFSET(dp));
}
#endif
if (DOINGSOFTDEP(dvp)) {
blkoff += DIRBLKSIZ;
while (blkoff < bp->b_bcount) {
((struct direct *)
(bp->b_data + blkoff))->d_reclen = DIRBLKSIZ;
blkoff += DIRBLKSIZ;
}
if (softdep_setup_directory_add(bp, dp, I_OFFSET(dp),
dirp->d_ino, newdirbp, 1))
UFS_INODE_SET_FLAG(dp, IN_NEEDSYNC);
if (newdirbp)
bdwrite(newdirbp);
bdwrite(bp);
return (UFS_UPDATE(dvp, 0));
}
if (DOINGASYNC(dvp)) {
bdwrite(bp);
return (UFS_UPDATE(dvp, 0));
}
error = bwrite(bp);
ret = UFS_UPDATE(dvp, 1);
if (error == 0)
return (ret);
return (error);
}
if (I_OFFSET(dp) + I_COUNT(dp) > dp->i_size) {
dp->i_size = I_OFFSET(dp) + I_COUNT(dp);
DIP_SET(dp, i_size, dp->i_size);
UFS_INODE_SET_FLAG(dp, IN_SIZEMOD | IN_MODIFIED);
}
error = UFS_BLKATOFF(dvp, (off_t)I_OFFSET(dp), &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 < I_COUNT(dp); ) {
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,
I_OFFSET(dp) + ((char *)nep - dirbuf),
I_OFFSET(dp) + ((char *)ep - dirbuf));
#endif
if (DOINGSOFTDEP(dvp))
softdep_change_directoryentry_offset(bp, dp, dirbuf,
(caddr_t)nep, (caddr_t)ep, dsize);
else
bcopy((caddr_t)nep, (caddr_t)ep, dsize);
}
# if (BYTE_ORDER == LITTLE_ENDIAN)
if (OFSFMT(dvp))
namlen = ep->d_type;
else
namlen = ep->d_namlen;
# else
namlen = ep->d_namlen;
# endif
if (ep->d_ino == 0 ||
(ep->d_ino == UFS_WINO && namlen == dirp->d_namlen &&
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, I_OFFSET(dp) + ((char *)ep - dirbuf));
#endif
bcopy((caddr_t)dirp, (caddr_t)ep, (uint64_t)newentrysize);
#ifdef UFS_DIRHASH
if (dp->i_dirhash != NULL)
ufsdirhash_checkblock(dp, dirbuf -
(I_OFFSET(dp) & (DIRBLKSIZ - 1)),
rounddown2(I_OFFSET(dp), DIRBLKSIZ));
#endif
if (DOINGSOFTDEP(dvp)) {
(void) softdep_setup_directory_add(bp, dp,
I_OFFSET(dp) + (caddr_t)ep - dirbuf,
dirp->d_ino, newdirbp, 0);
if (newdirbp != NULL)
bdwrite(newdirbp);
bdwrite(bp);
} else {
if (DOINGASYNC(dvp)) {
bdwrite(bp);
error = 0;
} else {
error = bwrite(bp);
}
}
UFS_INODE_SET_FLAG(dp, IN_CHANGE | IN_UPDATE | (error == 0 &&
I_ENDOFF(dp) != 0 && I_ENDOFF(dp) < dp->i_size ? IN_ENDOFF : 0));
return (error);
}
int
ufs_dirremove(struct vnode *dvp, struct inode *ip, int flags, bool isrmdir)
{
struct inode *dp;
struct direct *ep, *rep;
struct buf *bp;
off_t offset;
int error;
dp = VTOI(dvp);
if (ip) {
ip->i_effnlink--;
UFS_INODE_SET_FLAG(ip, IN_CHANGE);
if (DOINGSOFTDEP(dvp)) {
softdep_setup_unlink(dp, ip);
} else {
ip->i_nlink--;
DIP_SET_NLINK(ip, ip->i_nlink);
UFS_INODE_SET_FLAG(ip, IN_CHANGE);
}
}
if (flags & DOWHITEOUT)
offset = I_OFFSET(dp);
else
offset = I_OFFSET(dp) - I_COUNT(dp);
if ((error = UFS_BLKATOFF(dvp, offset, (char **)&ep, &bp)) != 0) {
if (ip) {
ip->i_effnlink++;
UFS_INODE_SET_FLAG(ip, IN_CHANGE);
if (DOINGSOFTDEP(dvp)) {
softdep_change_linkcnt(ip);
} else {
ip->i_nlink++;
DIP_SET_NLINK(ip, ip->i_nlink);
UFS_INODE_SET_FLAG(ip, IN_CHANGE);
}
}
return (error);
}
if (flags & DOWHITEOUT) {
ep->d_ino = UFS_WINO;
ep->d_type = DT_WHT;
goto out;
}
if (I_COUNT(dp) == 0)
rep = ep;
else
rep = (struct direct *)((char *)ep + ep->d_reclen);
#ifdef UFS_DIRHASH
if (dp->i_dirhash != NULL)
ufsdirhash_remove(dp, rep, I_OFFSET(dp));
#endif
if (ip && rep->d_ino != ip->i_number)
panic("ufs_dirremove: ip %ju does not match dirent ino %ju\n",
(uintmax_t)ip->i_number, (uintmax_t)rep->d_ino);
bzero(&rep->d_name[0], rep->d_namlen);
rep->d_namlen = 0;
rep->d_type = 0;
rep->d_ino = 0;
if (I_COUNT(dp) != 0) {
ep->d_reclen += rep->d_reclen;
rep->d_reclen = 0;
}
#ifdef UFS_DIRHASH
if (dp->i_dirhash != NULL)
ufsdirhash_checkblock(dp, (char *)ep -
((I_OFFSET(dp) - I_COUNT(dp)) & (DIRBLKSIZ - 1)),
rounddown2(I_OFFSET(dp), DIRBLKSIZ));
#endif
out:
error = 0;
if (DOINGSOFTDEP(dvp)) {
if (ip)
softdep_setup_remove(bp, dp, ip, isrmdir);
if (softdep_slowdown(dvp))
error = bwrite(bp);
else
bdwrite(bp);
} else {
if (flags & DOWHITEOUT)
error = bwrite(bp);
else if (DOINGASYNC(dvp))
bdwrite(bp);
else
error = bwrite(bp);
}
UFS_INODE_SET_FLAG(dp, IN_CHANGE | IN_UPDATE);
if (ip != NULL && IS_SNAPSHOT(ip) && ip->i_effnlink == 0)
UFS_SNAPGONE(ip);
return (error);
}
int
ufs_dirrewrite(struct inode *dp, struct inode *oip, ino_t newinum, int newtype,
u_int newparent)
{
struct buf *bp;
struct direct *ep;
struct vnode *vdp = ITOV(dp);
int error;
oip->i_effnlink--;
UFS_INODE_SET_FLAG(oip, IN_CHANGE);
if (DOINGSOFTDEP(vdp)) {
softdep_setup_unlink(dp, oip);
} else {
oip->i_nlink--;
DIP_SET_NLINK(oip, oip->i_nlink);
UFS_INODE_SET_FLAG(oip, IN_CHANGE);
}
error = UFS_BLKATOFF(vdp, (off_t)I_OFFSET(dp), (char **)&ep, &bp);
if (error == 0 && ep->d_namlen == 2 && ep->d_name[1] == '.' &&
ep->d_name[0] == '.' && ep->d_ino != oip->i_number) {
brelse(bp);
error = EIDRM;
}
if (error) {
oip->i_effnlink++;
UFS_INODE_SET_FLAG(oip, IN_CHANGE);
if (DOINGSOFTDEP(vdp)) {
softdep_change_linkcnt(oip);
} else {
oip->i_nlink++;
DIP_SET_NLINK(oip, oip->i_nlink);
UFS_INODE_SET_FLAG(oip, IN_CHANGE);
}
return (error);
}
ep->d_ino = newinum;
if (!OFSFMT(vdp))
ep->d_type = newtype;
if (DOINGSOFTDEP(vdp)) {
softdep_setup_directory_change(bp, dp, oip, newinum,
newparent);
bdwrite(bp);
} else {
if (DOINGASYNC(vdp)) {
bdwrite(bp);
error = 0;
} else {
error = bwrite(bp);
}
}
UFS_INODE_SET_FLAG(dp, IN_CHANGE | IN_UPDATE);
if (IS_SNAPSHOT(oip) && oip->i_effnlink == 0)
UFS_SNAPGONE(oip);
return (error);
}
int
ufs_dirempty(struct inode *ip, ino_t parentino, struct ucred *cred,
int skipwhiteout)
{
doff_t off;
struct dirtemplate dbuf;
struct direct *dp = (struct direct *)&dbuf;
int error, namlen;
ssize_t count;
#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 | IO_NOMACCHECK, cred,
NOCRED, &count, (struct thread *)0);
if (error || count != 0)
return (0);
if (dp->d_reclen == 0)
return (0);
if (dp->d_ino == 0 ||
(skipwhiteout != 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);
}
static int
ufs_dir_dd_ino(struct vnode *vp, struct ucred *cred, ino_t *dd_ino,
struct vnode **dd_vp)
{
struct dirtemplate dirbuf;
struct vnode *ddvp;
int error, namlen;
ASSERT_VOP_LOCKED(vp, "ufs_dir_dd_ino");
*dd_vp = NULL;
if (vp->v_type != VDIR)
return (ENOTDIR);
if ((ddvp = vn_dir_dd_ino(vp)) != NULL) {
KASSERT(ddvp->v_mount == vp->v_mount,
("ufs_dir_dd_ino: Unexpected mount point crossing"));
*dd_ino = VTOI(ddvp)->i_number;
*dd_vp = ddvp;
return (0);
}
error = vn_rdwr(UIO_READ, vp, (caddr_t)&dirbuf,
sizeof (struct dirtemplate), (off_t)0, UIO_SYSSPACE,
IO_NODELOCKED | IO_NOMACCHECK, cred, NOCRED, NULL, NULL);
if (error != 0)
return (error);
#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] != '.')
return (ENOTDIR);
*dd_ino = dirbuf.dotdot_ino;
return (0);
}
int
ufs_checkpath(ino_t source_ino, ino_t parent_ino, struct inode *target,
struct ucred *cred, ino_t *wait_ino)
{
struct mount *mp;
struct vnode *tvp, *vp, *vp1;
int error;
ino_t dd_ino;
vp = tvp = ITOV(target);
mp = vp->v_mount;
*wait_ino = 0;
if (target->i_number == source_ino)
return (EEXIST);
if (target->i_number == parent_ino)
return (0);
if (target->i_number == UFS_ROOTINO)
return (0);
for (;;) {
error = ufs_dir_dd_ino(vp, cred, &dd_ino, &vp1);
if (error != 0)
break;
if (dd_ino == source_ino) {
error = EINVAL;
break;
}
if (dd_ino == UFS_ROOTINO)
break;
if (dd_ino == parent_ino)
break;
if (vp1 == NULL) {
error = VFS_VGET(mp, dd_ino, LK_SHARED | LK_NOWAIT,
&vp1);
if (error != 0) {
*wait_ino = dd_ino;
break;
}
}
KASSERT(dd_ino == VTOI(vp1)->i_number,
("directory %ju reparented\n",
(uintmax_t)VTOI(vp1)->i_number));
if (vp != tvp)
vput(vp);
vp = vp1;
}
if (error == ENOTDIR)
panic("checkpath: .. not a directory\n");
if (vp1 != NULL)
vput(vp1);
if (vp != tvp)
vput(vp);
return (error);
}
#ifdef DIAGNOSTIC
static void
ufs_assert_inode_offset_owner(struct inode *ip, struct iown_tracker *tr,
const char *name, const char *file, int line)
{
char msg[128];
snprintf(msg, sizeof(msg), "at %s@%d", file, line);
ASSERT_VOP_ELOCKED(ITOV(ip), msg);
MPASS((ip->i_mode & IFMT) == IFDIR);
if (curthread == tr->tr_owner && ip->i_lock_gen == tr->tr_gen)
return;
printf("locked at\n");
stack_print(&tr->tr_st);
printf("unlocked at\n");
stack_print(&tr->tr_unlock);
panic("%s ip %p %jd offset owner %p %d gen %d "
"curthread %p %d gen %d at %s@%d\n",
name, ip, (uintmax_t)ip->i_number, tr->tr_owner,
tr->tr_owner->td_tid, tr->tr_gen,
curthread, curthread->td_tid, ip->i_lock_gen,
file, line);
}
static void
ufs_set_inode_offset_owner(struct inode *ip, struct iown_tracker *tr,
const char *file, int line)
{
char msg[128];
snprintf(msg, sizeof(msg), "at %s@%d", file, line);
ASSERT_VOP_ELOCKED(ITOV(ip), msg);
MPASS((ip->i_mode & IFMT) == IFDIR);
tr->tr_owner = curthread;
tr->tr_gen = ip->i_lock_gen;
stack_save(&tr->tr_st);
}
static void
ufs_init_one_tracker(struct iown_tracker *tr)
{
tr->tr_owner = NULL;
stack_zero(&tr->tr_st);
}
void
ufs_init_trackers(struct inode *ip)
{
ufs_init_one_tracker(&ip->i_offset_tracker);
ufs_init_one_tracker(&ip->i_count_tracker);
ufs_init_one_tracker(&ip->i_endoff_tracker);
}
void
ufs_unlock_tracker(struct inode *ip)
{
if (ip->i_count_tracker.tr_gen == ip->i_lock_gen)
stack_save(&ip->i_count_tracker.tr_unlock);
if (ip->i_offset_tracker.tr_gen == ip->i_lock_gen)
stack_save(&ip->i_offset_tracker.tr_unlock);
if (ip->i_endoff_tracker.tr_gen == ip->i_lock_gen)
stack_save(&ip->i_endoff_tracker.tr_unlock);
ip->i_lock_gen++;
}
doff_t
ufs_get_i_offset(struct inode *ip, const char *file, int line)
{
ufs_assert_inode_offset_owner(ip, &ip->i_offset_tracker, "i_offset",
file, line);
return (ip->i_offset);
}
void
ufs_set_i_offset(struct inode *ip, doff_t off, const char *file, int line)
{
ufs_set_inode_offset_owner(ip, &ip->i_offset_tracker, file, line);
ip->i_offset = off;
}
int32_t
ufs_get_i_count(struct inode *ip, const char *file, int line)
{
ufs_assert_inode_offset_owner(ip, &ip->i_count_tracker, "i_count",
file, line);
return (ip->i_count);
}
void
ufs_set_i_count(struct inode *ip, int32_t cnt, const char *file, int line)
{
ufs_set_inode_offset_owner(ip, &ip->i_count_tracker, file, line);
ip->i_count = cnt;
}
doff_t
ufs_get_i_endoff(struct inode *ip, const char *file, int line)
{
ufs_assert_inode_offset_owner(ip, &ip->i_endoff_tracker, "i_endoff",
file, line);
return (ip->i_endoff);
}
void
ufs_set_i_endoff(struct inode *ip, doff_t off, const char *file, int line)
{
ufs_set_inode_offset_owner(ip, &ip->i_endoff_tracker, file, line);
ip->i_endoff = off;
}
#endif