#include <sys/param.h>
#include <sys/systm.h>
#include <sys/namei.h>
#include <sys/buf.h>
#include <sys/mount.h>
#include <sys/vnode.h>
#include <sys/malloc.h>
#include <sys/dirent.h>
#include <ufs/ufs/quota.h>
#include <ufs/ufs/inode.h>
#include <ufs/ufs/ufsmount.h>
#include <ufs/ufs/ufs_extern.h>
#include <ufs/ext2fs/ext2fs_extern.h>
#include <ufs/ext2fs/ext2fs_dir.h>
#include <ufs/ext2fs/ext2fs.h>
extern int dirchk;
static void ext2fs_dirconv2ffs(struct ext2fs_direct *e2dir,
struct dirent *ffsdir);
static int ext2fs_dirbadentry(struct vnode *dp, struct ext2fs_direct *de,
int entryoffsetinblock);
static int ext2fs_search_dirblock(struct inode *, void *, int *,
struct componentname *, int *, doff_t *, doff_t *,
struct ext2fs_searchslot *);
static void
ext2fs_dirconv2ffs(struct ext2fs_direct *e2dir, struct dirent *ffsdir)
{
memset(ffsdir, 0, sizeof(struct dirent));
ffsdir->d_fileno = letoh32(e2dir->e2d_ino);
ffsdir->d_namlen = e2dir->e2d_namlen;
ffsdir->d_type = DT_UNKNOWN;
#ifdef DIAGNOSTIC
#endif
strncpy(ffsdir->d_name, e2dir->e2d_name, ffsdir->d_namlen);
ffsdir->d_reclen = DIRENT_SIZE(ffsdir);
}
int
ext2fs_readdir(void *v)
{
struct vop_readdir_args *ap = v;
struct uio *uio = ap->a_uio;
int error;
size_t e2fs_count, readcnt, entries;
struct vnode *vp = ap->a_vp;
struct m_ext2fs *fs = VTOI(vp)->i_e2fs;
struct ext2fs_direct *dp;
struct dirent dstd;
struct uio auio;
struct iovec aiov;
caddr_t dirbuf;
off_t off = uio->uio_offset;
int e2d_reclen;
if (vp->v_type != VDIR)
return (ENOTDIR);
e2fs_count = uio->uio_resid;
entries = (uio->uio_offset + e2fs_count) & (fs->e2fs_bsize - 1);
if (e2fs_count <= entries)
return (EINVAL);
e2fs_count -= entries;
auio = *uio;
auio.uio_iov = &aiov;
auio.uio_iovcnt = 1;
auio.uio_segflg = UIO_SYSSPACE;
aiov.iov_len = e2fs_count;
auio.uio_resid = e2fs_count;
dirbuf = malloc(e2fs_count, M_TEMP, M_WAITOK | M_ZERO);
aiov.iov_base = dirbuf;
error = VOP_READ(ap->a_vp, &auio, 0, ap->a_cred);
if (error == 0) {
readcnt = e2fs_count - auio.uio_resid;
dp = (struct ext2fs_direct *) dirbuf;
while ((char *) dp < (char *) dirbuf + readcnt) {
e2d_reclen = letoh16(dp->e2d_reclen);
if (e2d_reclen == 0) {
error = EIO;
break;
}
ext2fs_dirconv2ffs(dp, &dstd);
if (memchr(dstd.d_name, '/', dstd.d_namlen) != NULL) {
error = EINVAL;
break;
}
if (dstd.d_reclen > uio->uio_resid) {
break;
}
dstd.d_off = off + e2d_reclen;
if ((error = uiomove((caddr_t)&dstd, dstd.d_reclen, uio)) != 0) {
break;
}
off = off + e2d_reclen;
dp = (struct ext2fs_direct *) ((char *)dp + e2d_reclen);
}
uio->uio_offset = off;
}
free(dirbuf, M_TEMP, e2fs_count);
*ap->a_eofflag = ext2fs_size(VTOI(ap->a_vp)) <= uio->uio_offset;
return (error);
}
int
ext2fs_lookup(void *v)
{
struct vop_lookup_args *ap = v;
struct vnode *vdp;
struct inode *dp;
struct buf *bp;
struct ext2fs_direct *ep;
int entryoffsetinblock;
struct ext2fs_searchslot ss;
int numdirpasses;
doff_t endsearch;
doff_t prevoff;
struct vnode *pdp;
struct vnode *tdp;
doff_t enduseful;
u_long bmask;
int lockparent;
int wantparent;
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;
int dirblksize, entry_found = 0, error;
ss.slotstatus = FOUND;
ss.slotoffset = -1;
ss.slotfreespace = ss.slotsize = ss.slotneeded = 0;
bp = NULL;
*vpp = NULL;
vdp = ap->a_dvp;
dp = VTOI(vdp);
dirblksize = dp->i_e2fs->e2fs_bsize;
lockparent = flags & LOCKPARENT;
wantparent = flags & (LOCKPARENT|WANTPARENT);
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);
if ((nameiop == CREATE || nameiop == RENAME) && (flags & ISLASTCN)) {
ss.slotstatus = NONE;
ss.slotneeded = EXT2FS_DIRSIZ(cnp->cn_namelen);
}
bmask = VFSTOUFS(vdp->v_mount)->um_mountp->mnt_stat.f_iosize - 1;
if (nameiop != LOOKUP || dp->i_diroff == 0 ||
dp->i_diroff > ext2fs_size(dp)) {
entryoffsetinblock = 0;
dp->i_offset = 0;
numdirpasses = 1;
} else {
dp->i_offset = dp->i_diroff;
if ((entryoffsetinblock = dp->i_offset & bmask) &&
(error = ext2fs_bufatoff(dp, (off_t)dp->i_offset,
NULL, &bp)))
return (error);
numdirpasses = 2;
}
prevoff = dp->i_offset;
endsearch = roundup(ext2fs_size(dp), dirblksize);
enduseful = 0;
searchloop:
while (dp->i_offset < endsearch) {
if (bp != NULL)
brelse(bp);
error = ext2fs_bufatoff(dp, (off_t)dp->i_offset, NULL, &bp);
if (error != 0)
return (error);
entryoffsetinblock = 0;
if (ss.slotstatus == NONE) {
ss.slotoffset = -1;
ss.slotfreespace = 0;
}
error = ext2fs_search_dirblock(dp, bp->b_data, &entry_found,
cnp, &entryoffsetinblock, &prevoff, &enduseful, &ss);
if (error) {
brelse(bp);
return (error);
}
if (entry_found) {
ep = (struct ext2fs_direct *)
((char *)bp->b_data + (entryoffsetinblock & bmask));
dp->i_ino = letoh32(ep->e2d_ino);
dp->i_reclen = letoh16(ep->e2d_reclen);
goto found;
}
}
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_e2fs_nlink != 0) {
if (vdp->v_mount->mnt_flag & MNT_RDONLY)
return (EROFS);
if ((error = VOP_ACCESS(vdp, VWRITE, cred, cnp->cn_proc)) != 0)
return (error);
if (ss.slotstatus == NONE) {
dp->i_offset = roundup(ext2fs_size(dp), dirblksize);
dp->i_count = 0;
enduseful = dp->i_offset;
} else {
dp->i_offset = ss.slotoffset;
dp->i_count = ss.slotsize;
if (enduseful < ss.slotoffset + ss.slotsize)
enduseful = ss.slotoffset + ss.slotsize;
}
dp->i_endoff = roundup(enduseful, dirblksize);
dp->i_flag |= IN_CHANGE | IN_UPDATE;
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 (entryoffsetinblock + EXT2FS_DIRSIZ(ep->e2d_namlen)
> ext2fs_size(dp)) {
ufs_dirbad(dp, dp->i_offset, "i_size too small");
error = ext2fs_setsize(dp,
entryoffsetinblock + EXT2FS_DIRSIZ(ep->e2d_namlen));
if (error) {
brelse(bp);
return(error);
}
dp->i_flag |= IN_CHANGE | IN_UPDATE;
}
brelse(bp);
if ((flags & ISLASTCN) && nameiop == LOOKUP)
dp->i_diroff = dp->i_offset &~ (dirblksize - 1);
if (nameiop == DELETE && (flags & ISLASTCN)) {
if ((error = VOP_ACCESS(vdp, VWRITE, cred, cnp->cn_proc)) != 0)
return (error);
if ((dp->i_offset & (dirblksize - 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, dp->i_ino, &tdp)) != 0)
return (error);
if ((dp->i_e2fs_mode & ISVTX) &&
cred->cr_uid != 0 &&
cred->cr_uid != dp->i_e2fs_uid &&
VTOI(tdp)->i_e2fs_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)) {
if ((error = VOP_ACCESS(vdp, VWRITE, cred, cnp->cn_proc)) != 0)
return (error);
if (dp->i_number == dp->i_ino)
return (EISDIR);
if ((error = VFS_VGET(vdp->v_mount, dp->i_ino, &tdp)) != 0)
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;
if ((error = VFS_VGET(vdp->v_mount, dp->i_ino, &tdp)) != 0) {
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)) != 0) {
vput(tdp);
return (error);
}
cnp->cn_flags &= ~PDIRUNLOCK;
}
*vpp = tdp;
} else if (dp->i_number == dp->i_ino) {
vref(vdp);
*vpp = vdp;
} else {
if ((error = VFS_VGET(vdp->v_mount, dp->i_ino, &tdp)) != 0)
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);
}
int
ext2fs_search_dirblock(struct inode *ip, void *data, int *foundp,
struct componentname *cnp, int *entryoffsetinblockp,
doff_t *prevoffp, doff_t *endusefulp, struct ext2fs_searchslot *ssp)
{
struct ext2fs_direct *ep, *lim;
struct vnode *vdp;
int offset = *entryoffsetinblockp;
int dirblksize = ip->i_e2fs->e2fs_bsize;
size_t namlen;
vdp = ITOV(ip);
lim = (struct ext2fs_direct *)
((char *)data + dirblksize - EXT2FS_DIRSIZ(0));
ep = (struct ext2fs_direct *) ((char *)data + offset);
while (ep < lim) {
if (ep->e2d_reclen == 0 ||
(dirchk && ext2fs_dirbadentry(vdp, ep, offset))) {
int i;
ufs_dirbad(ip, ip->i_offset, "mangled entry");
i = dirblksize - (offset & (dirblksize - 1));
ip->i_offset += i;
offset += i;
continue;
}
if (ssp->slotstatus != FOUND) {
int size = letoh16(ep->e2d_reclen);
if (ep->e2d_ino != 0)
size -= EXT2FS_DIRSIZ(ep->e2d_namlen);
if (size > 0) {
if (size >= ssp->slotneeded) {
ssp->slotstatus = FOUND;
ssp->slotoffset = ip->i_offset;
ssp->slotsize = letoh16(ep->e2d_reclen);
} else if (ssp->slotstatus == NONE) {
ssp->slotfreespace += size;
if (ssp->slotoffset == -1)
ssp->slotoffset = ip->i_offset;
if (ssp->slotfreespace >= ssp->slotneeded) {
ssp->slotstatus = COMPACT;
ssp->slotsize = ip->i_offset +
letoh16(ep->e2d_reclen) - ssp->slotoffset;
}
}
}
}
if (ep->e2d_ino) {
namlen = ep->e2d_namlen;
if (namlen == cnp->cn_namelen &&
!memcmp(cnp->cn_nameptr, ep->e2d_name, namlen)) {
*foundp = 1;
return (0);
}
}
*prevoffp = ip->i_offset;
ip->i_offset += letoh16(ep->e2d_reclen);
offset += letoh16(ep->e2d_reclen);
*entryoffsetinblockp = offset;
if (ep->e2d_ino)
*endusefulp = ip->i_offset;
ep = (struct ext2fs_direct *) ((char *)data + offset);
}
return (0);
}
static int
ext2fs_dirbadentry(struct vnode *dp, struct ext2fs_direct *de,
int entryoffsetinblock)
{
int dirblksize = VTOI(dp)->i_e2fs->e2fs_bsize;
char *error_msg = NULL;
int reclen = letoh16(de->e2d_reclen);
int namlen = de->e2d_namlen;
if (reclen < EXT2FS_DIRSIZ(1))
error_msg = "rec_len is smaller than minimal";
else if (reclen % 4 != 0)
error_msg = "rec_len % 4 != 0";
else if (reclen < EXT2FS_DIRSIZ(namlen))
error_msg = "reclen is too small for name_len";
else if (entryoffsetinblock + reclen > dirblksize)
error_msg = "directory entry across blocks";
else if (letoh32(de->e2d_ino) > VTOI(dp)->i_e2fs->e2fs.e2fs_icount)
error_msg = "inode out of bounds";
if (error_msg != NULL) {
printf("bad directory entry: %s\n"
"offset=%d, inode=%u, rec_len=%d, name_len=%d \n",
error_msg, entryoffsetinblock, letoh32(de->e2d_ino),
reclen, namlen);
panic(__func__);
}
return (0);
}
int
ext2fs_direnter(struct inode *ip, struct vnode *dvp,
struct componentname *cnp)
{
struct ext2fs_direct *ep, *nep;
struct inode *dp;
struct buf *bp;
struct ext2fs_direct newdir;
struct iovec aiov;
struct uio auio;
u_int dsize;
int error, loc, newentrysize, spacefree;
char *dirbuf;
int dirblksize = ip->i_e2fs->e2fs_bsize;
#ifdef DIAGNOSTIC
if ((cnp->cn_flags & SAVENAME) == 0)
panic("direnter: missing name");
#endif
dp = VTOI(dvp);
newdir.e2d_ino = htole32(ip->i_number);
newdir.e2d_namlen = cnp->cn_namelen;
if (ip->i_e2fs->e2fs.e2fs_rev > E2FS_REV0 &&
(ip->i_e2fs->e2fs.e2fs_features_incompat & EXT2F_INCOMPAT_FTYPE)) {
newdir.e2d_type = inot2ext2dt(IFTODT(ip->i_e2fs_mode));
} else {
newdir.e2d_type = 0;
}
memcpy(newdir.e2d_name, cnp->cn_nameptr, (unsigned)cnp->cn_namelen + 1);
newentrysize = EXT2FS_DIRSIZ(cnp->cn_namelen);
if (dp->i_count == 0) {
if (dp->i_offset & (dirblksize - 1))
panic("ext2fs_direnter: newblk");
auio.uio_offset = dp->i_offset;
newdir.e2d_reclen = htole16(dirblksize);
auio.uio_resid = newentrysize;
aiov.iov_len = newentrysize;
aiov.iov_base = (caddr_t)&newdir;
auio.uio_iov = &aiov;
auio.uio_iovcnt = 1;
auio.uio_rw = UIO_WRITE;
auio.uio_segflg = UIO_SYSSPACE;
auio.uio_procp = NULL;
error = VOP_WRITE(dvp, &auio, IO_SYNC, cnp->cn_cred);
if (dirblksize >
VFSTOUFS(dvp->v_mount)->um_mountp->mnt_stat.f_bsize)
panic("ext2fs_direnter: frag size");
else if (!error) {
error = ext2fs_setsize(dp,
roundup(ext2fs_size(dp), dirblksize));
if (error)
return (error);
dp->i_flag |= IN_CHANGE;
}
return (error);
}
if ((error = ext2fs_bufatoff(dp, (off_t)dp->i_offset, &dirbuf, &bp))
!= 0)
return (error);
ep = (struct ext2fs_direct *)dirbuf;
dsize = EXT2FS_DIRSIZ(ep->e2d_namlen);
spacefree = letoh16(ep->e2d_reclen) - dsize;
for (loc = letoh16(ep->e2d_reclen); loc < dp->i_count; ) {
nep = (struct ext2fs_direct *)(dirbuf + loc);
if (ep->e2d_ino) {
ep->e2d_reclen = htole16(dsize);
ep = (struct ext2fs_direct *)((char *)ep + dsize);
} else {
spacefree += dsize;
}
dsize = EXT2FS_DIRSIZ(nep->e2d_namlen);
spacefree += letoh16(nep->e2d_reclen) - dsize;
loc += letoh16(nep->e2d_reclen);
memcpy(ep, nep, dsize);
}
if (ep->e2d_ino == 0) {
#ifdef DIAGNOSTIC
if (spacefree + dsize < newentrysize)
panic("ext2fs_direnter: compact1");
#endif
newdir.e2d_reclen = htole16(spacefree + dsize);
} else {
#ifdef DIAGNOSTIC
if (spacefree < newentrysize) {
printf("ext2fs_direnter: compact2 %u %u",
(u_int)spacefree, (u_int)newentrysize);
panic("ext2fs_direnter: compact2");
}
#endif
newdir.e2d_reclen = htole16(spacefree);
ep->e2d_reclen = htole16(dsize);
ep = (struct ext2fs_direct *)((char *)ep + dsize);
}
memcpy(ep, &newdir, newentrysize);
error = VOP_BWRITE(bp);
dp->i_flag |= IN_CHANGE | IN_UPDATE;
if (!error && dp->i_endoff && dp->i_endoff < ext2fs_size(dp))
error = ext2fs_truncate(dp, (off_t)dp->i_endoff, IO_SYNC,
cnp->cn_cred);
return (error);
}
int
ext2fs_dirremove(struct vnode *dvp, struct componentname *cnp)
{
struct inode *dp;
struct ext2fs_direct *ep;
struct buf *bp;
int error;
dp = VTOI(dvp);
if (dp->i_count == 0) {
error = ext2fs_bufatoff(dp, (off_t)dp->i_offset, (char **)&ep,
&bp);
if (error != 0)
return (error);
ep->e2d_ino = 0;
error = VOP_BWRITE(bp);
dp->i_flag |= IN_CHANGE | IN_UPDATE;
return (error);
}
error = ext2fs_bufatoff(dp, (off_t)(dp->i_offset - dp->i_count),
(char **)&ep, &bp);
if (error != 0)
return (error);
ep->e2d_reclen = htole16(letoh16(ep->e2d_reclen) + dp->i_reclen);
error = VOP_BWRITE(bp);
dp->i_flag |= IN_CHANGE | IN_UPDATE;
return (error);
}
int
ext2fs_dirrewrite(struct inode *dp, struct inode *ip,
struct componentname *cnp)
{
struct buf *bp;
struct ext2fs_direct *ep;
int error;
error = ext2fs_bufatoff(dp, (off_t)dp->i_offset, (char **)&ep, &bp);
if (error != 0)
return (error);
ep->e2d_ino = htole32(ip->i_number);
if (ip->i_e2fs->e2fs.e2fs_rev > E2FS_REV0 &&
(ip->i_e2fs->e2fs.e2fs_features_incompat & EXT2F_INCOMPAT_FTYPE)) {
ep->e2d_type = inot2ext2dt(IFTODT(ip->i_e2fs_mode));
} else {
ep->e2d_type = 0;
}
error = VOP_BWRITE(bp);
dp->i_flag |= IN_CHANGE | IN_UPDATE;
return (error);
}
int
ext2fs_dirempty(struct inode *ip, ufsino_t parentino, struct ucred *cred)
{
off_t off;
struct ext2fs_dirtemplate dbuf;
struct ext2fs_direct *dp = (struct ext2fs_direct *)&dbuf;
int error, namlen;
size_t count;
#define MINDIRSIZ (sizeof (struct ext2fs_dirtemplate) / 2)
for (off = 0; off < ext2fs_size(ip); off += letoh16(dp->e2d_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->e2d_reclen == 0)
return (0);
if (dp->e2d_ino == 0)
continue;
namlen = dp->e2d_namlen;
if (namlen > 2)
return (0);
if (dp->e2d_name[0] != '.')
return (0);
if (namlen == 1)
continue;
if (dp->e2d_name[1] == '.' && letoh32(dp->e2d_ino) == parentino)
continue;
return (0);
}
return (1);
}
int
ext2fs_checkpath(struct inode *source, struct inode *target,
struct ucred *cred)
{
struct vnode *vp;
int error, rootino, namlen;
struct ext2fs_dirtemplate dirbuf;
u_int32_t ino;
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 ext2fs_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;
}
ino = letoh32(dirbuf.dotdot_ino);
if (ino == source->i_number) {
error = EINVAL;
break;
}
if (ino == rootino)
break;
vput(vp);
error = VFS_VGET(vp->v_mount, ino, &vp);
if (error != 0) {
vp = NULL;
break;
}
}
out:
if (error == ENOTDIR) {
printf("checkpath: .. not a directory\n");
panic("checkpath");
}
if (vp != NULL)
vput(vp);
return (error);
}