#include <sys/param.h>
#include <sys/systm.h>
#include <sys/namei.h>
#include <sys/bio.h>
#include <sys/buf.h>
#include <sys/endian.h>
#include <sys/mount.h>
#include <sys/vnode.h>
#include <sys/malloc.h>
#include <sys/dirent.h>
#include <sys/sdt.h>
#include <sys/sysctl.h>
#include <ufs/ufs/dir.h>
#include <fs/ext2fs/fs.h>
#include <fs/ext2fs/inode.h>
#include <fs/ext2fs/ext2_mount.h>
#include <fs/ext2fs/ext2fs.h>
#include <fs/ext2fs/ext2_dinode.h>
#include <fs/ext2fs/ext2_dir.h>
#include <fs/ext2fs/ext2_extern.h>
#include <fs/ext2fs/fs.h>
SDT_PROVIDER_DECLARE(ext2fs);
SDT_PROBE_DEFINE2(ext2fs, , lookup, trace, "int", "char*");
SDT_PROBE_DEFINE4(ext2fs, , trace, ext2_dirbad_error,
"char*", "ino_t", "doff_t", "char*");
SDT_PROBE_DEFINE5(ext2fs, , trace, ext2_dirbadentry_error,
"char*", "int", "uint32_t", "uint16_t", "uint8_t");
static SYSCTL_NODE(_vfs, OID_AUTO, e2fs, CTLFLAG_RD | CTLFLAG_MPSAFE, 0,
"EXT2FS filesystem");
#undef DIRBLKSIZ
static u_char ext2_ft_to_dt[] = {
DT_UNKNOWN,
DT_REG,
DT_DIR,
DT_CHR,
DT_BLK,
DT_FIFO,
DT_SOCK,
DT_LNK,
};
#define FTTODT(ft) \
((ft) < nitems(ext2_ft_to_dt) ? ext2_ft_to_dt[(ft)] : DT_UNKNOWN)
static u_char dt_to_ext2_ft[] = {
EXT2_FT_UNKNOWN,
EXT2_FT_FIFO,
EXT2_FT_CHRDEV,
EXT2_FT_UNKNOWN,
EXT2_FT_DIR,
EXT2_FT_UNKNOWN,
EXT2_FT_BLKDEV,
EXT2_FT_UNKNOWN,
EXT2_FT_REG_FILE,
EXT2_FT_UNKNOWN,
EXT2_FT_SYMLINK,
EXT2_FT_UNKNOWN,
EXT2_FT_SOCK,
EXT2_FT_UNKNOWN,
EXT2_FT_UNKNOWN,
};
#define DTTOFT(dt) \
((dt) < nitems(dt_to_ext2_ft) ? dt_to_ext2_ft[(dt)] : EXT2_FT_UNKNOWN)
static int ext2_check_direntry(struct vnode *dp,
struct ext2fs_direct_2 *de, int entryoffsetinblock);
static int ext2_is_dot_entry(struct componentname *cnp);
static int ext2_lookup_ino(struct vnode *vdp, struct vnode **vpp,
struct componentname *cnp, ino_t *dd_ino);
static int
ext2_is_dot_entry(struct componentname *cnp)
{
if (cnp->cn_namelen <= 2 && cnp->cn_nameptr[0] == '.' &&
(cnp->cn_nameptr[1] == '.' || cnp->cn_nameptr[1] == '\0'))
return (1);
return (0);
}
int
ext2_readdir(struct vop_readdir_args *ap)
{
struct vnode *vp = ap->a_vp;
struct uio *uio = ap->a_uio;
struct buf *bp;
struct inode *ip;
struct ext2fs_direct_2 *dp, *edp;
uint64_t *cookies;
struct dirent dstdp;
off_t offset, startoffset;
size_t readcnt, skipcnt;
ssize_t startresid;
u_int ncookies;
int DIRBLKSIZ = VTOI(ap->a_vp)->i_e2fs->e2fs_bsize;
int error;
if (uio->uio_offset < 0)
return (EINVAL);
ip = VTOI(vp);
if (ap->a_ncookies != NULL) {
if (uio->uio_resid < 0)
ncookies = 0;
else
ncookies = uio->uio_resid;
if (uio->uio_offset >= ip->i_size)
ncookies = 0;
else if (ip->i_size - uio->uio_offset < ncookies)
ncookies = ip->i_size - uio->uio_offset;
ncookies = ncookies / (offsetof(struct ext2fs_direct_2,
e2d_namlen) + 4) + 1;
cookies = malloc(ncookies * sizeof(*cookies), M_TEMP, M_WAITOK);
*ap->a_ncookies = ncookies;
*ap->a_cookies = cookies;
} else {
ncookies = 0;
cookies = NULL;
}
offset = startoffset = uio->uio_offset;
startresid = uio->uio_resid;
error = 0;
while (error == 0 && uio->uio_resid > 0 &&
uio->uio_offset < ip->i_size) {
error = ext2_blkatoff(vp, uio->uio_offset, NULL, &bp);
if (error)
break;
if (bp->b_offset + bp->b_bcount > ip->i_size)
readcnt = ip->i_size - bp->b_offset;
else
readcnt = bp->b_bcount;
skipcnt = (size_t)(uio->uio_offset - bp->b_offset) &
~(size_t)(DIRBLKSIZ - 1);
offset = bp->b_offset + skipcnt;
dp = (struct ext2fs_direct_2 *)&bp->b_data[skipcnt];
edp = (struct ext2fs_direct_2 *)&bp->b_data[readcnt];
while (error == 0 && uio->uio_resid > 0 && dp < edp) {
if (le16toh(dp->e2d_reclen) <= offsetof(struct ext2fs_direct_2,
e2d_namlen) || (caddr_t)dp + le16toh(dp->e2d_reclen) >
(caddr_t)edp) {
error = EIO;
break;
}
dstdp.d_namlen = dp->e2d_namlen;
dstdp.d_type = FTTODT(dp->e2d_type);
if (offsetof(struct ext2fs_direct_2, e2d_namlen) +
dstdp.d_namlen > le16toh(dp->e2d_reclen)) {
error = EIO;
break;
}
if (offset < startoffset || le32toh(dp->e2d_ino) == 0)
goto nextentry;
dstdp.d_fileno = le32toh(dp->e2d_ino);
dstdp.d_reclen = GENERIC_DIRSIZ(&dstdp);
bcopy(dp->e2d_name, dstdp.d_name, dstdp.d_namlen);
dstdp.d_off = offset + le16toh(dp->e2d_reclen);
dirent_terminate(&dstdp);
if (dstdp.d_reclen > uio->uio_resid) {
if (uio->uio_resid == startresid)
error = EINVAL;
else
error = EJUSTRETURN;
break;
}
error = uiomove((caddr_t)&dstdp, dstdp.d_reclen, uio);
if (error)
break;
if (cookies != NULL) {
KASSERT(ncookies > 0,
("ext2_readdir: cookies buffer too small"));
*cookies = offset + le16toh(dp->e2d_reclen);
cookies++;
ncookies--;
}
nextentry:
offset += le16toh(dp->e2d_reclen);
dp = (struct ext2fs_direct_2 *)((caddr_t)dp +
le16toh(dp->e2d_reclen));
}
bqrelse(bp);
uio->uio_offset = offset;
}
uio->uio_offset = offset;
if (error == EJUSTRETURN)
error = 0;
if (ap->a_ncookies != NULL) {
if (error == 0) {
*ap->a_ncookies -= ncookies;
} else {
free(*ap->a_cookies, M_TEMP);
*ap->a_ncookies = 0;
*ap->a_cookies = NULL;
}
}
if (error == 0 && ap->a_eofflag)
*ap->a_eofflag = ip->i_size <= uio->uio_offset;
return (error);
}
int
ext2_lookup(struct vop_cachedlookup_args *ap)
{
return (ext2_lookup_ino(ap->a_dvp, ap->a_vpp, ap->a_cnp, NULL));
}
static int
ext2_lookup_ino(struct vnode *vdp, struct vnode **vpp, struct componentname *cnp,
ino_t *dd_ino)
{
struct inode *dp;
struct buf *bp;
struct ext2fs_direct_2 *ep;
int entryoffsetinblock;
struct ext2fs_searchslot ss;
doff_t i_diroff;
doff_t i_offset;
int numdirpasses;
doff_t endsearch;
doff_t prevoff;
struct vnode *pdp;
struct vnode *tdp;
doff_t enduseful;
u_long bmask;
int error;
struct ucred *cred = cnp->cn_cred;
int flags = cnp->cn_flags;
int nameiop = cnp->cn_nameiop;
ino_t ino, ino1;
int ltype;
int entry_found = 0;
int DIRBLKSIZ = VTOI(vdp)->i_e2fs->e2fs_bsize;
if (vpp != NULL)
*vpp = NULL;
dp = VTOI(vdp);
bmask = VFSTOEXT2(vdp->v_mount)->um_mountp->mnt_stat.f_iosize - 1;
restart:
bp = NULL;
ss.slotoffset = -1;
i_diroff = dp->i_diroff;
ss.slotstatus = FOUND;
ss.slotfreespace = ss.slotsize = ss.slotneeded = 0;
if ((nameiop == CREATE || nameiop == RENAME) &&
(flags & ISLASTCN)) {
ss.slotstatus = NONE;
ss.slotneeded = EXT2_DIR_REC_LEN(cnp->cn_namelen);
}
if (!ext2_is_dot_entry(cnp) && ext2_htree_has_idx(dp)) {
numdirpasses = 1;
entryoffsetinblock = 0;
switch (ext2_htree_lookup(dp, cnp->cn_nameptr, cnp->cn_namelen,
&bp, &entryoffsetinblock, &i_offset, &prevoff,
&enduseful, &ss)) {
case 0:
ep = (struct ext2fs_direct_2 *)((char *)bp->b_data +
(i_offset & bmask));
goto foundentry;
case ENOENT:
i_offset = roundup2(dp->i_size, DIRBLKSIZ);
goto notfound;
default:
break;
}
}
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 = ext2_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 (bp != NULL)
brelse(bp);
error = ext2_blkatoff(vdp, (off_t)i_offset, NULL, &bp);
if (error != 0)
return (error);
entryoffsetinblock = 0;
if (ss.slotstatus == NONE) {
ss.slotoffset = -1;
ss.slotfreespace = 0;
}
error = ext2_search_dirblock(dp, bp->b_data, &entry_found,
cnp->cn_nameptr, cnp->cn_namelen,
&entryoffsetinblock, &i_offset, &prevoff,
&enduseful, &ss);
if (error != 0) {
brelse(bp);
return (error);
}
if (entry_found) {
ep = (struct ext2fs_direct_2 *)((char *)bp->b_data +
(entryoffsetinblock & bmask));
foundentry:
ino = le32toh(ep->e2d_ino);
goto found;
}
}
notfound:
if (numdirpasses == 2) {
numdirpasses--;
i_offset = 0;
endsearch = i_diroff;
goto searchloop;
}
if (bp != NULL)
brelse(bp);
if ((nameiop == CREATE || nameiop == RENAME) &&
(flags & ISLASTCN) && dp->i_nlink != 0) {
if ((error = VOP_ACCESS(vdp, VWRITE, cred, curthread)) != 0)
return (error);
if (ss.slotstatus == NONE) {
dp->i_offset = roundup2(dp->i_size, DIRBLKSIZ);
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 = 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 (entryoffsetinblock + EXT2_DIR_REC_LEN(ep->e2d_namlen) >
dp->i_size) {
ext2_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 ((error = VOP_ACCESS(vdp, VWRITE, cred, curthread)) != 0)
return (error);
dp->i_offset = i_offset;
if ((dp->i_offset & (DIRBLKSIZ - 1)) == 0)
dp->i_count = 0;
else
dp->i_count = dp->i_offset - prevoff;
if (dd_ino != NULL)
return (0);
if (dp->i_number == ino) {
vref(vdp);
*vpp = vdp;
return (0);
}
if ((error = VFS_VGET(vdp->v_mount, ino, LK_EXCLUSIVE,
&tdp)) != 0)
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;
return (0);
}
if (nameiop == RENAME && (flags & ISLASTCN)) {
if ((error = VOP_ACCESS(vdp, VWRITE, cred, curthread)) != 0)
return (error);
dp->i_offset = 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);
*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 (VN_IS_DOOMED(pdp)) {
if (error == 0)
vput(tdp);
error = ENOENT;
}
if (error)
return (error);
error = ext2_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);
}
*vpp = vdp;
} else {
if ((error = VFS_VGET(vdp->v_mount, ino, cnp->cn_lkflags,
&tdp)) != 0)
return (error);
*vpp = tdp;
}
if (cnp->cn_flags & MAKEENTRY)
cache_enter(vdp, *vpp, cnp);
return (0);
}
int
ext2_search_dirblock(struct inode *ip, void *data, int *foundp,
const char *name, int namelen, int *entryoffsetinblockp,
doff_t *offp, doff_t *prevoffp, doff_t *endusefulp,
struct ext2fs_searchslot *ssp)
{
struct vnode *vdp;
struct ext2fs_direct_2 *ep, *top;
uint32_t bsize = ip->i_e2fs->e2fs_bsize;
int offset = *entryoffsetinblockp;
int namlen;
vdp = ITOV(ip);
ep = (struct ext2fs_direct_2 *)((char *)data + offset);
top = (struct ext2fs_direct_2 *)((char *)data + bsize);
while (ep < top) {
if (ext2_check_direntry(vdp, ep, offset)) {
int i;
ext2_dirbad(ip, *offp, "mangled entry");
i = bsize - (offset & (bsize - 1));
*offp += i;
offset += i;
ep = (struct ext2fs_direct_2 *)((char *)data + offset);
continue;
}
if (ssp->slotstatus != FOUND) {
int size = le16toh(ep->e2d_reclen);
if (ep->e2d_ino != 0)
size -= EXT2_DIR_REC_LEN(ep->e2d_namlen);
else if (ext2_is_dirent_tail(ip, ep))
size -= sizeof(struct ext2fs_direct_tail);
if (size > 0) {
if (size >= ssp->slotneeded) {
ssp->slotstatus = FOUND;
ssp->slotoffset = *offp;
ssp->slotsize = le16toh(ep->e2d_reclen);
} else if (ssp->slotstatus == NONE) {
ssp->slotfreespace += size;
if (ssp->slotoffset == -1)
ssp->slotoffset = *offp;
if (ssp->slotfreespace >= ssp->slotneeded) {
ssp->slotstatus = COMPACT;
ssp->slotsize = *offp +
le16toh(ep->e2d_reclen) -
ssp->slotoffset;
}
}
}
}
if (ep->e2d_ino != 0) {
namlen = ep->e2d_namlen;
if (namlen == namelen &&
!bcmp(name, ep->e2d_name, (unsigned)namlen)) {
*foundp = 1;
return (0);
}
}
*prevoffp = *offp;
*offp += le16toh(ep->e2d_reclen);
offset += le16toh(ep->e2d_reclen);
*entryoffsetinblockp = offset;
if (ep->e2d_ino != 0)
*endusefulp = *offp;
ep = (struct ext2fs_direct_2 *)((char *)data + offset);
}
return (0);
}
void
ext2_dirbad(struct inode *ip, doff_t offset, char *how)
{
SDT_PROBE4(ext2fs, , trace, ext2_dirbad_error,
ITOV(ip)->v_mount->mnt_stat.f_mntonname, ip->i_number, offset, how);
}
static int
ext2_check_direntry(struct vnode *dp, struct ext2fs_direct_2 *de,
int entryoffsetinblock)
{
struct m_ext2fs *fs = VTOI(dp)->i_e2fs;
char *error_msg = NULL;
if (le16toh(de->e2d_reclen) < EXT2_DIR_REC_LEN(1))
error_msg = "rec_len is smaller than minimal";
else if (le16toh(de->e2d_reclen) % 4 != 0)
error_msg = "rec_len % 4 != 0";
else if (le16toh(de->e2d_reclen) < EXT2_DIR_REC_LEN(de->e2d_namlen))
error_msg = "reclen is too small for name_len";
else if (entryoffsetinblock + le16toh(de->e2d_reclen)> fs->e2fs_bsize)
error_msg = "directory entry across blocks";
else if (le32toh(de->e2d_ino) > fs->e2fs->e2fs_icount)
error_msg = "directory entry inode out of bounds";
else if (le32toh(de->e2d_ino) == EXT2_ROOTINO &&
((de->e2d_namlen != 1 && de->e2d_namlen != 2) ||
(de->e2d_name[0] != '.') ||
(de->e2d_namlen == 2 && de->e2d_name[1] != '.')))
error_msg = "bad root directory entry";
if (error_msg != NULL) {
SDT_PROBE5(ext2fs, , trace, ext2_dirbadentry_error,
error_msg, entryoffsetinblock,
le32toh(de->e2d_ino), le16toh(de->e2d_reclen),
de->e2d_namlen);
}
return (error_msg == NULL ? 0 : EINVAL);
}
static int
ext2_add_first_entry(struct vnode *dvp, struct ext2fs_direct_2 *entry,
struct componentname *cnp)
{
struct inode *dp;
struct iovec aiov;
struct uio auio;
char* buf = NULL;
int dirblksize, error;
dp = VTOI(dvp);
dirblksize = dp->i_e2fs->e2fs_bsize;
if (dp->i_offset & (dirblksize - 1))
panic("ext2_add_first_entry: bad directory offset");
if (EXT2_HAS_RO_COMPAT_FEATURE(dp->i_e2fs,
EXT2F_ROCOMPAT_METADATA_CKSUM)) {
entry->e2d_reclen = htole16(dirblksize -
sizeof(struct ext2fs_direct_tail));
buf = malloc(dirblksize, M_TEMP, M_WAITOK);
memcpy(buf, entry, EXT2_DIR_REC_LEN(entry->e2d_namlen));
ext2_init_dirent_tail(EXT2_DIRENT_TAIL(buf, dirblksize));
ext2_dirent_csum_set(dp, (struct ext2fs_direct_2 *)buf);
auio.uio_offset = dp->i_offset;
auio.uio_resid = dirblksize;
aiov.iov_len = auio.uio_resid;
aiov.iov_base = (caddr_t)buf;
} else {
entry->e2d_reclen = htole16(dirblksize);
auio.uio_offset = dp->i_offset;
auio.uio_resid = EXT2_DIR_REC_LEN(entry->e2d_namlen);
aiov.iov_len = auio.uio_resid;
aiov.iov_base = (caddr_t)entry;
}
auio.uio_iov = &aiov;
auio.uio_iovcnt = 1;
auio.uio_rw = UIO_WRITE;
auio.uio_segflg = UIO_SYSSPACE;
auio.uio_td = (struct thread *)0;
error = VOP_WRITE(dvp, &auio, IO_SYNC, cnp->cn_cred);
if (error)
goto out;
dp->i_size = roundup2(dp->i_size, dirblksize);
dp->i_flag |= IN_CHANGE;
out:
free(buf, M_TEMP);
return (error);
}
int
ext2_direnter(struct inode *ip, struct vnode *dvp, struct componentname *cnp)
{
struct inode *dp;
struct ext2fs_direct_2 newdir;
int DIRBLKSIZ = ip->i_e2fs->e2fs_bsize;
int error;
dp = VTOI(dvp);
newdir.e2d_ino = htole32(ip->i_number);
if (EXT2_HAS_INCOMPAT_FEATURE(ip->i_e2fs,
EXT2F_INCOMPAT_FTYPE)) {
newdir.e2d_namlen = cnp->cn_namelen;
newdir.e2d_type = DTTOFT(IFTODT(ip->i_mode));
} else
newdir.e2d_namlen = htole16(cnp->cn_namelen);
bcopy(cnp->cn_nameptr, newdir.e2d_name, (unsigned)cnp->cn_namelen + 1);
if (ext2_htree_has_idx(dp)) {
error = ext2_htree_add_entry(dvp, &newdir, cnp);
if (error) {
dp->i_flag &= ~IN_E3INDEX;
dp->i_flag |= IN_CHANGE | IN_UPDATE;
}
return (error);
}
if (EXT2_HAS_COMPAT_FEATURE(ip->i_e2fs, EXT2F_COMPAT_DIRHASHINDEX) &&
!ext2_htree_has_idx(dp)) {
if ((dp->i_size / DIRBLKSIZ) == 1 &&
dp->i_offset == DIRBLKSIZ) {
return ext2_htree_create_index(dvp, cnp, &newdir);
}
}
if (dp->i_count == 0)
return ext2_add_first_entry(dvp, &newdir, cnp);
error = ext2_add_entry(dvp, &newdir);
if (!error && dp->i_endoff && dp->i_endoff < dp->i_size)
error = ext2_truncate(dvp, (off_t)dp->i_endoff, IO_SYNC,
cnp->cn_cred, curthread);
return (error);
}
int
ext2_add_entry(struct vnode *dvp, struct ext2fs_direct_2 *entry)
{
struct ext2fs_direct_2 *ep, *nep;
struct inode *dp;
struct buf *bp;
u_int dsize;
int error, loc, newentrysize, spacefree;
char *dirbuf;
dp = VTOI(dvp);
if (dp->i_offset + dp->i_count > dp->i_size)
dp->i_size = dp->i_offset + dp->i_count;
if ((error = ext2_blkatoff(dvp, (off_t)dp->i_offset, &dirbuf,
&bp)) != 0)
return (error);
newentrysize = EXT2_DIR_REC_LEN(entry->e2d_namlen);
ep = (struct ext2fs_direct_2 *)dirbuf;
dsize = EXT2_DIR_REC_LEN(ep->e2d_namlen);
spacefree = le16toh(ep->e2d_reclen) - dsize;
for (loc = le16toh(ep->e2d_reclen); loc < dp->i_count; ) {
nep = (struct ext2fs_direct_2 *)(dirbuf + loc);
if (le32toh(ep->e2d_ino)) {
ep->e2d_reclen = htole16(dsize);
ep = (struct ext2fs_direct_2 *)((char *)ep + dsize);
} else {
spacefree += dsize;
}
dsize = EXT2_DIR_REC_LEN(nep->e2d_namlen);
spacefree += le16toh(nep->e2d_reclen) - dsize;
loc += le16toh(nep->e2d_reclen);
bcopy((caddr_t)nep, (caddr_t)ep, dsize);
}
if (ep->e2d_ino == 0) {
if (spacefree + dsize < newentrysize)
panic("ext2_direnter: compact1");
entry->e2d_reclen = htole16(spacefree + dsize);
} else {
if (spacefree < newentrysize)
panic("ext2_direnter: compact2");
entry->e2d_reclen = htole16(spacefree);
ep->e2d_reclen = htole16(dsize);
ep = (struct ext2fs_direct_2 *)((char *)ep + dsize);
}
bcopy((caddr_t)entry, (caddr_t)ep, (u_int)newentrysize);
ext2_dirent_csum_set(dp, (struct ext2fs_direct_2 *)bp->b_data);
if (DOINGASYNC(dvp)) {
bdwrite(bp);
error = 0;
} else {
error = bwrite(bp);
}
dp->i_flag |= IN_CHANGE | IN_UPDATE;
return (error);
}
int
ext2_dirremove(struct vnode *dvp, struct componentname *cnp)
{
struct inode *dp;
struct ext2fs_direct_2 *ep, *rep;
struct buf *bp;
int error;
dp = VTOI(dvp);
if (dp->i_count == 0) {
if ((error =
ext2_blkatoff(dvp, (off_t)dp->i_offset, (char **)&ep,
&bp)) != 0)
return (error);
ep->e2d_ino = 0;
ext2_dirent_csum_set(dp, (struct ext2fs_direct_2 *)bp->b_data);
error = bwrite(bp);
dp->i_flag |= IN_CHANGE | IN_UPDATE;
return (error);
}
if ((error = ext2_blkatoff(dvp, (off_t)(dp->i_offset - dp->i_count),
(char **)&ep, &bp)) != 0)
return (error);
if (dp->i_count == 0)
rep = ep;
else
rep = (struct ext2fs_direct_2 *)((char *)ep +
le16toh(ep->e2d_reclen));
ep->e2d_reclen += rep->e2d_reclen;
ext2_dirent_csum_set(dp, (struct ext2fs_direct_2 *)bp->b_data);
if (DOINGASYNC(dvp) && dp->i_count != 0)
bdwrite(bp);
else
error = bwrite(bp);
dp->i_flag |= IN_CHANGE | IN_UPDATE;
return (error);
}
int
ext2_dirrewrite(struct inode *dp, struct inode *ip, struct componentname *cnp)
{
struct buf *bp;
struct ext2fs_direct_2 *ep;
struct vnode *vdp = ITOV(dp);
int error;
if ((error = ext2_blkatoff(vdp, (off_t)dp->i_offset, (char **)&ep,
&bp)) != 0)
return (error);
ep->e2d_ino = htole32(ip->i_number);
if (EXT2_HAS_INCOMPAT_FEATURE(ip->i_e2fs,
EXT2F_INCOMPAT_FTYPE))
ep->e2d_type = DTTOFT(IFTODT(ip->i_mode));
else
ep->e2d_type = EXT2_FT_UNKNOWN;
ext2_dirent_csum_set(dp, (struct ext2fs_direct_2 *)bp->b_data);
error = bwrite(bp);
dp->i_flag |= IN_CHANGE | IN_UPDATE;
return (error);
}
int
ext2_dirempty(struct inode *ip, ino_t parentino, struct ucred *cred)
{
off_t off;
struct dirtemplate dbuf;
struct ext2fs_direct_2 *dp = (struct ext2fs_direct_2 *)&dbuf;
int error, namlen;
ssize_t count;
#define MINDIRSIZ (sizeof(struct dirtemplate) / 2)
for (off = 0; off < ip->i_size; off += le16toh(dp->e2d_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->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] == '.' && le32toh(dp->e2d_ino) == parentino)
continue;
return (0);
}
return (1);
}
int
ext2_checkpath(struct inode *source, struct inode *target, struct ucred *cred)
{
struct vnode *vp;
int error, namlen;
struct dirtemplate dirbuf;
vp = ITOV(target);
if (target->i_number == source->i_number) {
error = EEXIST;
goto out;
}
if (target->i_number == EXT2_ROOTINO) {
error = 0;
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 | IO_NOMACCHECK, cred, NOCRED, NULL,
NULL);
if (error != 0)
break;
namlen = dirbuf.dotdot_type;
if (namlen != 2 ||
dirbuf.dotdot_name[0] != '.' ||
dirbuf.dotdot_name[1] != '.') {
error = ENOTDIR;
break;
}
if (le32toh(dirbuf.dotdot_ino) == source->i_number) {
error = EINVAL;
break;
}
if (le32toh(dirbuf.dotdot_ino) == EXT2_ROOTINO)
break;
vput(vp);
if ((error = VFS_VGET(vp->v_mount, le32toh(dirbuf.dotdot_ino),
LK_EXCLUSIVE, &vp)) != 0) {
vp = NULL;
break;
}
}
out:
if (error == ENOTDIR)
SDT_PROBE2(ext2fs, , lookup, trace, 1,
"checkpath: .. not a directory");
if (vp != NULL)
vput(vp);
return (error);
}