#include <stdio.h>
#include <errno.h>
#include <strings.h>
#include <unistd.h>
#include <sys/vfsops.h>
#include <sys/vnode.h>
#include <sys/stat.h>
#include <sys/namecache.h>
#include <sys/queue.h>
#include <sys/systm.h>
#include <sys/dirent.h>
#include <sys/mount.h>
#include <sys/signalvar.h>
#include <sys/resource.h>
#include <sys/buf2.h>
#include <sys/kern_syscall.h>
#include <sys/ktr.h>
#include "dirfs.h"
KTR_INFO_MASTER_EXTERN(dirfs);
KTR_INFO(KTR_DIRFS, dirfs, unsupported, 0,
"DIRFS(func=%s)",
const char *func);
KTR_INFO(KTR_DIRFS, dirfs, nresolve, 0,
"DIRFS(dnp=%p ncp_name=%s parent=%p pfd=%d error=%d)",
dirfs_node_t dnp, char *name, dirfs_node_t pdnp, int pfd, int error);
KTR_INFO(KTR_DIRFS, dirfs, ncreate, 1,
"DIRFS(dnp=%p ncp_name=%s parent=%p pfd=%d error=%d)",
dirfs_node_t dnp, char *name, dirfs_node_t pdnp, int pfd, int error);
KTR_INFO(KTR_DIRFS, dirfs, open, 2,
"DIRFS(dnp=%p dn_name=%s nfd=%d)",
dirfs_node_t dnp, char *name, int fd);
KTR_INFO(KTR_DIRFS, dirfs, close, 3,
"DIRFS(dnp=%p fd=%d opencount=%d writecount=%d vfsync error=%d)",
dirfs_node_t dnp, int fd, int oc, int wc, int error);
KTR_INFO(KTR_DIRFS, dirfs, readdir, 4,
"DIRFS(dnp=%p fd=%d startoff=%jd uio_offset=%jd)",
dirfs_node_t dnp, int fd, off_t startoff, off_t uoff);
KTR_INFO(KTR_DIRFS, dirfs, access, 5,
"DIRFS(dnp=%p error=%d)",
dirfs_node_t dnp, int error);
KTR_INFO(KTR_DIRFS, dirfs, getattr, 6,
"DIRFS(dnp=%p error=%d)",
dirfs_node_t dnp, int error);
KTR_INFO(KTR_DIRFS, dirfs, setattr, 7,
"DIRFS(dnp=%p action=%s error=%d)",
dirfs_node_t dnp, const char *action, int error);
KTR_INFO(KTR_DIRFS, dirfs, fsync, 8,
"DIRFS(dnp=%p error=%d)",
dirfs_node_t dnp, int error);
KTR_INFO(KTR_DIRFS, dirfs, read, 9,
"DIRFS(dnp=%p size=%jd error=%d)",
dirfs_node_t dnp, size_t size, int error);
KTR_INFO(KTR_DIRFS, dirfs, write, 10,
"DIRFS(dnp=%p size=%jd boff=%jd uio_resid=%jd error=%d)",
dirfs_node_t dnp, off_t boff, size_t resid, size_t size, int error);
KTR_INFO(KTR_DIRFS, dirfs, strategy, 11,
"DIRFS(dnp=%p dnp_size=%jd iosize=%jd b_cmd=%d b_error=%d "
"b_resid=%d bio_off=%jd error=%d)",
dirfs_node_t dnp, size_t size, size_t iosize, int cmd, int berror,
int bresid, off_t biooff, int error);
KTR_INFO(KTR_DIRFS, dirfs, nremove, 12,
"DIRFS(dnp=%p pdnp=%p error=%d)",
dirfs_node_t dnp, dirfs_node_t pdnp, int error);
KTR_INFO(KTR_DIRFS, dirfs, nmkdir, 13,
"DIRFS(pdnp=%p dnp=%p nc_name=%p error=%d)",
dirfs_node_t dnp, dirfs_node_t pdnp, char *n, int error);
KTR_INFO(KTR_DIRFS, dirfs, nrmdir, 13,
"DIRFS(pdnp=%p dnp=%p error=%d)",
dirfs_node_t dnp, dirfs_node_t pdnp, int error);
KTR_INFO(KTR_DIRFS, dirfs, nsymlink, 14,
"DIRFS(dnp=%p target=%s symlink=%s error=%d)",
dirfs_node_t dnp, char *tgt, char *lnk, int error);
int dirfs_access(struct vop_access_args *);
int dirfs_getattr(struct vop_getattr_args *);
int dirfs_setattr(struct vop_setattr_args *);
int dirfs_reclaim(struct vop_reclaim_args *);
static __inline
void
dirfs_knote(struct vnode *vp, int flags)
{
if (flags)
KNOTE(&vp->v_pollinfo.vpi_kqinfo.ki_note, flags);
}
static int
dirfs_nresolve(struct vop_nresolve_args *ap)
{
dirfs_node_t pdnp, dnp, d1, d2;
dirfs_mount_t dmp;
struct namecache *ncp;
struct nchandle *nch;
struct vnode *dvp;
struct vnode *vp;
struct mount *mp;
int error;
dbg(3, "called\n");
error = 0;
nch = ap->a_nch;
ncp = nch->ncp;
mp = nch->mount;
dvp = ap->a_dvp;
vp = NULL;
dnp = d1 = d2 = NULL;
pdnp = VP_TO_NODE(dvp);
dmp = VFS_TO_DIRFS(mp);
dirfs_node_lock(pdnp);
TAILQ_FOREACH_MUTABLE(d1, &dmp->dm_fdlist, dn_fdentry, d2) {
if (d1->dn_parent == pdnp &&
(strcmp(d1->dn_name, ncp->nc_name) == 0)) {
dnp = d1;
dirfs_node_ref(dnp);
passive_fd_list_hits++;
break;
}
}
dirfs_node_unlock(pdnp);
if (dnp) {
dirfs_alloc_vp(mp, &vp, LK_CANRECURSE, dnp);
dirfs_node_drop(dmp, dnp);
} else {
passive_fd_list_miss++;
error = dirfs_alloc_file(dmp, &dnp, pdnp, ncp, &vp, NULL, 0);
}
if (vp) {
if (error && error == ENOENT) {
cache_setvp(nch, NULL);
} else {
vn_unlock(vp);
cache_setvp(nch, vp);
vrele(vp);
}
}
KTR_LOG(dirfs_nresolve, dnp, ncp->nc_name, pdnp, pdnp->dn_fd, error);
return error;
}
static int
dirfs_nlookupdotdot(struct vop_nlookupdotdot_args *ap)
{
dbg(3, "called\n");
KTR_LOG(dirfs_unsupported, __func__);
return EOPNOTSUPP;
}
static int
dirfs_ncreate(struct vop_ncreate_args *ap)
{
dirfs_node_t pdnp;
dirfs_node_t dnp;
dirfs_mount_t dmp;
struct namecache *ncp;
struct vnode *dvp;
struct vnode **vpp;
struct vattr *vap;
int perms = 0;
int error;
dbg(3, "called\n");
error = 0;
dnp = NULL;
dvp = ap->a_dvp;
pdnp = VP_TO_NODE(dvp);
dmp = VFS_TO_DIRFS(dvp->v_mount);
vap = ap->a_vap;
ncp = ap->a_nch->ncp;
vpp = ap->a_vpp;
dirfs_node_getperms(pdnp, &perms);
if ((perms & DIRFS_NODE_WR) == 0)
error = EPERM;
error = dirfs_alloc_file(dmp, &dnp, pdnp, ncp, vpp, vap,
(O_CREAT | O_RDWR));
if (error == 0) {
cache_setunresolved(ap->a_nch);
cache_setvp(ap->a_nch, *vpp);
dirfs_knote(dvp, NOTE_WRITE);
}
KTR_LOG(dirfs_ncreate, dnp, ncp->nc_name, pdnp, pdnp->dn_fd, error);
return error;
}
static int
dirfs_nmknod(struct vop_nmknod_args *v)
{
dbg(3, "called\n");
return EOPNOTSUPP;
}
static int
dirfs_open(struct vop_open_args *ap)
{
dirfs_node_t dnp;
dirfs_mount_t dmp;
struct vnode *vp;
int error;
dbg(3, "called\n");
vp = ap->a_vp;
dnp = VP_TO_NODE(vp);
dmp = VFS_TO_DIRFS(vp->v_mount);
error = 0;
if (dmp->dm_root != dnp && dnp->dn_fd == DIRFS_NOFD) {
error = dirfs_open_helper(dmp, dnp, DIRFS_NOFD, NULL);
if (error)
return error;
}
KTR_LOG(dirfs_open, dnp, dnp->dn_name, dnp->dn_fd);
return vop_stdopen(ap);
}
static int
dirfs_close(struct vop_close_args *ap)
{
struct vnode *vp;
dirfs_node_t dnp;
int error;
dbg(3, "called\n");
error = 0;
vp = ap->a_vp;
dnp = VP_TO_NODE(vp);
if (vp->v_type == VREG) {
error = vfsync(vp, 0, 1, NULL, NULL);
if (error)
dbg(9, "vfsync error=%d\n", error);
}
vop_stdclose(ap);
KTR_LOG(dirfs_close, dnp, dnp->dn_fd, vp->v_opencount,
vp->v_writecount, error);
return 0;
}
int
dirfs_access(struct vop_access_args *ap)
{
struct vnode *vp = ap->a_vp;
int error;
dirfs_node_t dnp;
dbg(3, "called\n");
dnp = VP_TO_NODE(vp);
switch (vp->v_type) {
case VDIR:
case VLNK:
case VREG:
if ((ap->a_mode & VWRITE) && (vp->v_mount->mnt_flag & MNT_RDONLY)) {
error = EROFS;
goto out;
}
break;
case VBLK:
case VCHR:
case VSOCK:
case VFIFO:
break;
default:
error = EINVAL;
goto out;
}
error = vop_helper_access(ap, dnp->dn_uid,
dnp->dn_gid, dnp->dn_mode, 0);
out:
KTR_LOG(dirfs_access, dnp, error);
return error;
}
int
dirfs_getattr(struct vop_getattr_args *ap)
{
dirfs_mount_t dmp;
dirfs_node_t dnp;
dirfs_node_t pathnp;
struct vnode *vp;
struct vattr *vap;
char *tmp;
char *pathfree;
int error;
dbg(3, "called\n");
vp = ap->a_vp;
vap = ap->a_vap;
dnp = VP_TO_NODE(vp);
dmp = VFS_TO_DIRFS(vp->v_mount);
KKASSERT(dnp);
if (!dirfs_node_isroot(dnp)) {
pathnp = dirfs_findfd(dmp, dnp, &tmp, &pathfree);
KKASSERT(pathnp->dn_fd != DIRFS_NOFD);
error = dirfs_node_stat(pathnp->dn_fd, tmp, dnp);
dirfs_dropfd(dmp, pathnp, pathfree);
} else {
error = dirfs_node_stat(DIRFS_NOFD, dmp->dm_path, dnp);
}
if (error == 0) {
dirfs_node_lock(dnp);
vap->va_nlink = dnp->dn_links;
vap->va_type = dnp->dn_type;
vap->va_mode = dnp->dn_mode;
vap->va_uid = dnp->dn_uid;
vap->va_gid = dnp->dn_gid;
vap->va_fileid = dnp->dn_ino;
vap->va_size = dnp->dn_size;
vap->va_blocksize = dnp->dn_blocksize;
vap->va_atime.tv_sec = dnp->dn_atime;
vap->va_atime.tv_nsec = dnp->dn_atimensec;
vap->va_mtime.tv_sec = dnp->dn_mtime;
vap->va_mtime.tv_nsec = dnp->dn_mtimensec;
vap->va_ctime.tv_sec = dnp->dn_ctime;
vap->va_ctime.tv_nsec = dnp->dn_ctimensec;
vap->va_bytes = dnp->dn_size;
vap->va_gen = dnp->dn_gen;
vap->va_flags = dnp->dn_flags;
vap->va_fsid = vp->v_mount->mnt_stat.f_fsid.val[0];
dirfs_node_unlock(dnp);
}
KTR_LOG(dirfs_getattr, dnp, error);
return 0;
}
int
dirfs_setattr(struct vop_setattr_args *ap)
{
dirfs_mount_t dmp;
dirfs_node_t dnp;
struct vnode *vp;
struct vattr *vap;
struct ucred *cred;
int error;
int kflags = 0;
#ifdef KTR
const char *msg[6] = {
"invalid",
"chflags",
"chsize",
"chown",
"chmod",
"chtimes"
};
#endif
int msgno;
dbg(3, "called\n");
error = msgno = 0;
vp = ap->a_vp;
vap = ap->a_vap;
cred = ap->a_cred;
dnp = VP_TO_NODE(vp);
dmp = VFS_TO_DIRFS(vp->v_mount);
if ((vap->va_type != VNON) || (vap->va_nlink != VNOVAL) ||
(vap->va_fsid != VNOVAL) || (vap->va_fileid != VNOVAL) ||
(vap->va_blocksize != VNOVAL) || (vap->va_rmajor != VNOVAL) ||
((int)vap->va_bytes != VNOVAL) || (vap->va_gen != VNOVAL)) {
msgno = 0;
error = EINVAL;
goto out;
}
if (error == 0 && (vap->va_flags != VNOVAL)) {
if (vp->v_mount->mnt_flag & MNT_RDONLY) {
error = EROFS;
} else {
error = dirfs_node_chflags(dnp, vap->va_flags, cred);
kflags |= NOTE_ATTRIB;
}
msgno = 1;
goto out;
}
if (error == 0 && (vap->va_size != VNOVAL)) {
if (vp->v_mount->mnt_flag & MNT_RDONLY) {
error = EROFS;
} else {
if (vap->va_size > dnp->dn_size)
kflags |= NOTE_WRITE | NOTE_EXTEND;
else
kflags |= NOTE_WRITE;
error = dirfs_node_chsize(dnp, vap->va_size);
}
dbg(9, "dnp size=%jd vap size=%jd\n", dnp->dn_size, vap->va_size);
msgno = 2;
goto out;
}
if (error == 0 && (vap->va_uid != (uid_t)VNOVAL ||
vap->va_gid != (gid_t)VNOVAL)) {
if (vp->v_mount->mnt_flag & MNT_RDONLY) {
error = EROFS;
} else {
mode_t cur_mode = dnp->dn_mode;
uid_t cur_uid = dnp->dn_uid;
gid_t cur_gid = dnp->dn_gid;
error = vop_helper_chown(ap->a_vp, vap->va_uid,
vap->va_gid, ap->a_cred,
&cur_uid, &cur_gid, &cur_mode);
if (error == 0 &&
(cur_mode != dnp->dn_mode ||
cur_uid != dnp->dn_uid ||
cur_gid != dnp->dn_gid)) {
error = dirfs_node_chown(dmp, dnp, cur_uid,
cur_gid, cur_mode);
kflags |= NOTE_ATTRIB;
}
}
msgno = 3;
goto out;
}
if (error == 0 && (vap->va_mode != (mode_t)VNOVAL)) {
if (vp->v_mount->mnt_flag & MNT_RDONLY) {
error = EROFS;
} else {
mode_t cur_mode = dnp->dn_mode;
uid_t cur_uid = dnp->dn_uid;
gid_t cur_gid = dnp->dn_gid;
error = vop_helper_chmod(ap->a_vp, vap->va_mode,
ap->a_cred,
cur_uid, cur_gid, &cur_mode);
if (error == 0 && cur_mode != dnp->dn_mode) {
error = dirfs_node_chmod(dmp, dnp, cur_mode);
kflags |= NOTE_ATTRIB;
}
}
msgno = 4;
goto out;
}
if (error == 0 && ((vap->va_atime.tv_sec != VNOVAL &&
vap->va_atime.tv_nsec != VNOVAL) ||
(vap->va_mtime.tv_sec != VNOVAL &&
vap->va_mtime.tv_nsec != VNOVAL) )) {
if (vp->v_mount->mnt_flag & MNT_RDONLY) {
error = EROFS;
} else {
error = dirfs_node_chtimes(dnp);
kflags |= NOTE_ATTRIB;
}
msgno = 5;
goto out;
}
out:
KTR_LOG(dirfs_setattr, dnp, msg[msgno], error);
dirfs_knote(vp, kflags);
return error;
}
static int
dirfs_fsync(struct vop_fsync_args *ap)
{
dirfs_node_t dnp = VP_TO_NODE(ap->a_vp);
int error = 0;
dbg(3, "called\n");
vfsync(ap->a_vp, ap->a_waitfor, 1, NULL, NULL);
if (dnp->dn_fd != DIRFS_NOFD) {
if (fsync(dnp->dn_fd) == -1)
error = fsync(dnp->dn_fd);
}
KTR_LOG(dirfs_fsync, dnp, error);
return 0;
}
static int
dirfs_read(struct vop_read_args *ap)
{
struct buf *bp;
struct vnode *vp = ap->a_vp;
struct uio *uio = ap->a_uio;
dirfs_node_t dnp;
off_t base_offset;
size_t offset;
size_t len;
int error;
dbg(3, "called\n");
error = 0;
if (uio->uio_resid == 0) {
dbg(9, "zero len uio->uio_resid\n");
return error;
}
dnp = VP_TO_NODE(vp);
if (uio->uio_offset < 0)
return (EINVAL);
if (vp->v_type != VREG)
return (EINVAL);
while (uio->uio_resid > 0 && uio->uio_offset < dnp->dn_size) {
offset = (size_t)uio->uio_offset & BMASK;
base_offset = (off_t)uio->uio_offset - offset;
bp = getcacheblk(vp, base_offset, BSIZE, 0);
if (bp == NULL) {
lwkt_gettoken(&vp->v_mount->mnt_token);
error = bread(vp, base_offset, BSIZE, &bp);
if (error) {
brelse(bp);
lwkt_reltoken(&vp->v_mount->mnt_token);
dbg(9, "dirfs_read bread error %d\n", error);
break;
}
lwkt_reltoken(&vp->v_mount->mnt_token);
}
len = BSIZE - offset;
if (len > uio->uio_resid)
len = uio->uio_resid;
if (len > dnp->dn_size - uio->uio_offset)
len = (size_t)(dnp->dn_size - uio->uio_offset);
error = uiomovebp(bp, (char *)bp->b_data + offset, len, uio);
bqrelse(bp);
if (error) {
dbg(9, "dirfs_read uiomove error %d\n", error);
break;
}
}
KTR_LOG(dirfs_read, dnp, dnp->dn_size, error);
return(error);
}
static int
dirfs_write (struct vop_write_args *ap)
{
dirfs_node_t dnp;
dirfs_mount_t dmp;
struct buf *bp;
struct vnode *vp = ap->a_vp;
struct uio *uio = ap->a_uio;
struct thread *td = uio->uio_td;
int error;
int kflags = 0;
off_t osize;
off_t nsize;
off_t base_offset;
size_t offset;
size_t len;
struct rlimit limit;
dbg(3, "called\n");
error = 0;
if (uio->uio_resid == 0) {
dbg(9, "zero-length uio->uio_resid\n");
return error;
}
dnp = VP_TO_NODE(vp);
dmp = VFS_TO_DIRFS(vp->v_mount);
if (vp->v_type != VREG)
return (EINVAL);
if (vp->v_type == VREG && td != NULL) {
error = kern_getrlimit(RLIMIT_FSIZE, &limit);
if (error != 0) {
dbg(9, "rlimit failure\n");
return error;
}
if (uio->uio_offset + uio->uio_resid > limit.rlim_cur) {
dbg(9, "file too big\n");
ksignal(td->td_proc, SIGXFSZ);
return (EFBIG);
}
}
if (ap->a_ioflag & IO_APPEND)
uio->uio_offset = dnp->dn_size;
osize = dnp->dn_size;
nsize = uio->uio_offset + uio->uio_resid;
if (nsize > osize && uio->uio_resid) {
KKASSERT(dnp->dn_fd >= 0);
dnp->dn_size = nsize;
ftruncate(dnp->dn_fd, nsize);
nvextendbuf(vp, osize, nsize,
BSIZE, BSIZE, -1, -1, 0);
kflags |= NOTE_EXTEND;
}
while (uio->uio_resid > 0) {
offset = (size_t)uio->uio_offset & BMASK;
base_offset = (off_t)uio->uio_offset - offset;
len = BSIZE - offset;
if (len > uio->uio_resid)
len = uio->uio_resid;
error = bread(vp, base_offset, BSIZE, &bp);
error = uiomovebp(bp, (char *)bp->b_data + offset, len, uio);
if (error) {
brelse(bp);
dbg(9, "WRITE uiomove failed\n");
break;
}
kflags |= NOTE_WRITE;
dbg(9, "WRITE dn_size=%jd uio_offset=%jd uio_resid=%jd base_offset=%jd\n",
dnp->dn_size, uio->uio_offset, uio->uio_resid, base_offset);
if (ap->a_ioflag & IO_SYNC)
bwrite(bp);
else
bdwrite(bp);
}
KTR_LOG(dirfs_write, dnp, base_offset, uio->uio_resid,
dnp->dn_size, error);
if (kflags)
dirfs_knote(vp, kflags);
return error;
}
static int
dirfs_advlock (struct vop_advlock_args *ap)
{
struct vnode *vp = ap->a_vp;
dirfs_node_t dnp = VP_TO_NODE(vp);
dbg(3, "called\n");
return (lf_advlock(ap, &dnp->dn_advlock, dnp->dn_size));
}
static int
dirfs_strategy(struct vop_strategy_args *ap)
{
dirfs_node_t dnp;
dirfs_mount_t dmp;
struct bio *bio = ap->a_bio;
struct buf *bp = bio->bio_buf;
struct vnode *vp = ap->a_vp;
int error;
size_t iosize;
char *tmp;
char *pathfree;
dbg(3, "called\n");
dnp = VP_TO_NODE(vp);
dmp = VFS_TO_DIRFS(vp->v_mount);
error = 0;
if (vp->v_type != VREG) {
dbg(9, "not VREG\n");
bp->b_resid = bp->b_bcount;
bp->b_flags |= B_ERROR | B_INVAL;
bp->b_error = EINVAL;
biodone(bio);
return(0);
}
if (dnp->dn_fd == DIRFS_NOFD) {
print_backtrace(-1);
panic("Meh, no fd to write to. dnp=%p\n", dnp);
}
if (bio->bio_offset + bp->b_bcount > dnp->dn_size)
iosize = dnp->dn_size - bio->bio_offset;
else
iosize = bp->b_bcount;
KKASSERT((ssize_t)iosize >= 0);
switch (bp->b_cmd) {
case BUF_CMD_WRITE:
error = pwrite(dnp->dn_fd, bp->b_data, iosize, bio->bio_offset);
break;
case BUF_CMD_READ:
error = pread(dnp->dn_fd, bp->b_data, iosize, bio->bio_offset);
break;
default:
bp->b_error = error = EINVAL;
bp->b_flags |= B_ERROR;
break;
}
if (error >= 0 && error < bp->b_bcount)
bzero(bp->b_data + error, bp->b_bcount - error);
if (error < 0 && errno != EINTR) {
dbg(9, "error=%d dnp=%p dnp->dn_fd=%d "
"bio->bio_offset=%ld bcount=%d resid=%d iosize=%zd\n",
errno, dnp, dnp->dn_fd, bio->bio_offset, bp->b_bcount,
bp->b_resid, iosize);
bp->b_error = errno;
bp->b_resid = bp->b_bcount;
bp->b_flags |= B_ERROR;
} else {
tmp = dirfs_node_absolute_path(dmp, dnp, &pathfree);
dirfs_node_stat(DIRFS_NOFD, tmp, dnp);
dirfs_dropfd(dmp, NULL, pathfree);
}
KTR_LOG(dirfs_strategy, dnp, dnp->dn_size, iosize, bp->b_cmd,
bp->b_error, bp->b_resid, bio->bio_offset, error);
biodone(bio);
return 0;
}
static int
dirfs_bmap(struct vop_bmap_args *ap)
{
dbg(3, "called\n");
if (ap->a_doffsetp != NULL)
*ap->a_doffsetp = ap->a_loffset;
if (ap->a_runp != NULL)
*ap->a_runp = 0;
if (ap->a_runb != NULL)
*ap->a_runb = 0;
return 0;
}
static int
dirfs_nremove(struct vop_nremove_args *ap)
{
dirfs_node_t dnp, pdnp;
dirfs_node_t pathnp;
dirfs_mount_t dmp;
struct vnode *dvp;
struct nchandle *nch;
struct namecache *ncp;
struct mount *mp;
struct vnode *vp;
int error;
char *tmp;
char *pathfree;
dbg(3, "called\n");
error = 0;
tmp = NULL;
vp = NULL;
dvp = ap->a_dvp;
nch = ap->a_nch;
ncp = nch->ncp;
mp = dvp->v_mount;
dmp = VFS_TO_DIRFS(mp);
lwkt_gettoken(&mp->mnt_token);
cache_vget(nch, ap->a_cred, LK_SHARED, &vp);
vn_unlock(vp);
pdnp = VP_TO_NODE(dvp);
dnp = VP_TO_NODE(vp);
if (vp->v_type == VDIR) {
error = EISDIR;
} else {
pathnp = dirfs_findfd(dmp, dnp, &tmp, &pathfree);
dirfs_node_lock(pdnp);
error = unlinkat(pathnp->dn_fd, tmp, 0);
if (error == 0) {
cache_unlink(nch);
dirfs_knote(vp, NOTE_DELETE);
dirfs_node_setpassive(dmp, dnp, 0);
if (dnp->dn_parent) {
dirfs_node_drop(dmp, dnp->dn_parent);
dnp->dn_parent = NULL;
}
} else {
error = errno;
}
dirfs_node_unlock(pdnp);
dirfs_dropfd(dmp, pathnp, pathfree);
}
dirfs_knote(dvp, NOTE_WRITE);
vrele(vp);
lwkt_reltoken(&mp->mnt_token);
KTR_LOG(dirfs_nremove, dnp, pdnp, error);
return error;
}
static int
dirfs_nlink(struct vop_nlink_args *ap)
{
dbg(3, "called\n");
KTR_LOG(dirfs_unsupported, __func__);
return EOPNOTSUPP;
}
static int
dirfs_nrename(struct vop_nrename_args *ap)
{
dirfs_node_t dnp, fdnp, tdnp;
dirfs_mount_t dmp;
struct namecache *fncp, *tncp;
struct vnode *fdvp, *tdvp, *vp;
struct mount *mp;
char *fpath, *fpathfree;
char *tpath, *tpathfree;
int error;
dbg(3, "called\n");
error = 0;
fdvp = ap->a_fdvp;
tdvp = ap->a_tdvp;
fncp = ap->a_fnch->ncp;
tncp = ap->a_tnch->ncp;
mp = fdvp->v_mount;
dmp = VFS_TO_DIRFS(mp);
fdnp = VP_TO_NODE(fdvp);
tdnp = VP_TO_NODE(tdvp);
dbg(9, "fdnp=%p tdnp=%p from=%s to=%s\n", fdnp, tdnp, fncp->nc_name,
tncp->nc_name);
if (fdvp->v_mount != tdvp->v_mount)
return(EXDEV);
if (fdvp->v_mount != fncp->nc_vp->v_mount)
return(EXDEV);
if (fdvp->v_mount->mnt_flag & MNT_RDONLY)
return (EROFS);
tpath = dirfs_node_absolute_path_plus(dmp, tdnp,
tncp->nc_name, &tpathfree);
fpath = dirfs_node_absolute_path_plus(dmp, fdnp,
fncp->nc_name, &fpathfree);
error = rename(fpath, tpath);
if (error < 0)
error = errno;
if (error == 0) {
vp = fncp->nc_vp;
dnp = VP_TO_NODE(vp);
dirfs_node_setname(dnp, tncp->nc_name, tncp->nc_nlen);
dirfs_knote(fdvp, NOTE_RENAME);
vp = tncp->nc_vp;
if (vp) {
dbg(9, "RENAME2\n");
dnp = VP_TO_NODE(vp);
cache_unlink(ap->a_tnch);
dirfs_knote(vp, NOTE_DELETE);
dirfs_node_setpassive(dmp, dnp, 0);
if (dnp->dn_parent) {
dirfs_node_drop(dmp, dnp->dn_parent);
dnp->dn_parent = NULL;
}
dnp->dn_links = 0;
cache_inval_vp(vp, CINV_DESTROY);
}
cache_rename(ap->a_fnch, ap->a_tnch);
dirfs_knote(fdvp, NOTE_WRITE);
dirfs_knote(tdvp, NOTE_WRITE);
}
dirfs_dropfd(dmp, NULL, fpathfree);
dirfs_dropfd(dmp, NULL, tpathfree);
return error;
}
static int
dirfs_nmkdir(struct vop_nmkdir_args *ap)
{
dirfs_mount_t dmp;
dirfs_node_t dnp, pdnp, dnp1;
struct namecache *ncp;
struct vattr *vap;
struct vnode *dvp;
struct vnode **vpp;
char *tmp, *pathfree;
char *path;
int pfd, error;
int extrapath;
dbg(3, "called\n");
extrapath = error = 0;
dvp = ap->a_dvp;
vpp = ap->a_vpp;
dmp = VFS_TO_DIRFS(dvp->v_mount);
pdnp = VP_TO_NODE(dvp);
ncp = ap->a_nch->ncp;
vap = ap->a_vap;
pathfree = tmp = path = NULL;
dnp = NULL;
dirfs_node_lock(pdnp);
if (pdnp->dn_fd != DIRFS_NOFD) {
pfd = pdnp->dn_fd;
path = ncp->nc_name;
} else {
dnp1 = dirfs_findfd(dmp, pdnp, &tmp, &pathfree);
pfd = dnp1->dn_fd;
path = kmalloc(MAXPATHLEN, M_DIRFS_MISC, M_ZERO | M_WAITOK);
ksnprintf(path, MAXPATHLEN, "%s/%s", tmp, ncp->nc_name);
extrapath = 1;
dirfs_dropfd(dmp, dnp1, pathfree);
}
error = mkdirat(pfd, path, vap->va_mode);
if (error) {
error = errno;
} else {
error = dirfs_alloc_file(dmp, &dnp, pdnp, ncp, vpp,
vap, O_DIRECTORY);
if (error)
error = errno;
cache_setunresolved(ap->a_nch);
cache_setvp(ap->a_nch, *vpp);
dirfs_knote(dvp, NOTE_WRITE | NOTE_LINK);
}
dirfs_node_unlock(pdnp);
if (extrapath)
kfree(path, M_DIRFS_MISC);
KTR_LOG(dirfs_nmkdir, pdnp, dnp, ncp->nc_name, error);
return error;
}
static int
dirfs_nrmdir(struct vop_nrmdir_args *ap)
{
dirfs_node_t dnp, pdnp;
dirfs_mount_t dmp;
struct vnode *dvp;
struct nchandle *nch;
struct namecache *ncp;
struct mount *mp;
struct vnode *vp;
int error;
char *tmp;
char *pathfree;
dbg(3, "called\n");
error = 0;
tmp = NULL;
vp = NULL;
dvp = ap->a_dvp;
nch = ap->a_nch;
ncp = nch->ncp;
mp = dvp->v_mount;
dmp = VFS_TO_DIRFS(mp);
lwkt_gettoken(&mp->mnt_token);
cache_vget(nch, ap->a_cred, LK_SHARED, &vp);
vn_unlock(vp);
pdnp = VP_TO_NODE(dvp);
dnp = VP_TO_NODE(vp);
if (vp->v_type != VDIR) {
error = ENOTDIR;
} else {
tmp = dirfs_node_absolute_path(dmp, dnp, &pathfree);
dirfs_node_lock(pdnp);
error = rmdir(tmp);
if (error == 0) {
cache_unlink(nch);
dirfs_knote(dvp, NOTE_WRITE | NOTE_LINK);
dirfs_node_setpassive(dmp, dnp, 0);
if (dnp->dn_parent) {
dirfs_node_drop(dmp, dnp->dn_parent);
dnp->dn_parent = NULL;
}
dnp->dn_links = 0;
cache_inval_vp(vp, CINV_DESTROY);
} else {
error = errno;
}
dirfs_node_unlock(pdnp);
dirfs_dropfd(dmp, NULL, pathfree);
}
vrele(vp);
lwkt_reltoken(&mp->mnt_token);
KTR_LOG(dirfs_nrmdir, dnp, pdnp, error);
return error;
}
static int
dirfs_nsymlink(struct vop_nsymlink_args *ap)
{
dirfs_mount_t dmp;
dirfs_node_t dnp, pdnp;
struct mount *mp;
struct namecache *ncp;
struct vattr *vap;
struct vnode *dvp;
struct vnode **vpp;
char *tmp, *pathfree;
char *path;
int error;
dbg(3, "called\n");
error = 0;
dvp = ap->a_dvp;
vpp = ap->a_vpp;
mp = dvp->v_mount;
dmp = VFS_TO_DIRFS(dvp->v_mount);
pdnp = VP_TO_NODE(dvp);
ncp = ap->a_nch->ncp;
vap = ap->a_vap;
pathfree = tmp = path = NULL;
dnp = NULL;
lwkt_gettoken(&mp->mnt_token);
vap->va_type = VLNK;
tmp = dirfs_node_absolute_path(dmp, pdnp, &pathfree);
path = kmalloc(MAXPATHLEN, M_DIRFS_MISC, M_ZERO | M_WAITOK);
ksnprintf(path, MAXPATHLEN, "%s/%s", tmp, ncp->nc_name);
dirfs_dropfd(dmp, NULL, pathfree);
error = symlink(ap->a_target, path);
if (error) {
error = errno;
} else {
error = dirfs_alloc_file(dmp, &dnp, pdnp, ncp, vpp,
NULL, 0);
if (error)
error = errno;
cache_setunresolved(ap->a_nch);
cache_setvp(ap->a_nch, *vpp);
dirfs_knote(*vpp, NOTE_WRITE);
}
dbg(5, "path=%s a_target=%s\n", path, ap->a_target);
KTR_LOG(dirfs_nsymlink, dnp, ap->a_target, path, error);
kfree(path, M_DIRFS_MISC);
lwkt_reltoken(&mp->mnt_token);
return error;
}
static int
dirfs_readdir(struct vop_readdir_args *ap)
{
struct dirent *dp, *dpn;
off_t __unused **cookies = ap->a_cookies;
int *ncookies = ap->a_ncookies;
int bytes;
char *buf;
long base;
struct vnode *vp = ap->a_vp;
struct uio *uio;
dirfs_node_t dnp;
off_t startoff;
off_t cnt;
int error, r;
size_t bufsiz;
off_t curoff;
dbg(3, "called\n");
if (ncookies)
debug(1, "ncookies=%d\n", *ncookies);
dnp = VP_TO_NODE(vp);
uio = ap->a_uio;
startoff = uio->uio_offset;
cnt = 0;
error = 0;
base = 0;
bytes = 0;
if (vp->v_type != VDIR)
return ENOTDIR;
if (uio->uio_resid < 0)
return EINVAL;
if ((bufsiz = uio->uio_resid) > 4096)
bufsiz = 4096;
buf = kmalloc(bufsiz, M_DIRFS_MISC, M_WAITOK | M_ZERO);
dirfs_node_lock(dnp);
lseek(dnp->dn_fd, startoff, SEEK_SET);
bytes = getdirentries(dnp->dn_fd, buf, bufsiz, &base);
dbg(9, "seek %016jx %016jx %016jx\n",
(intmax_t)startoff, (intmax_t)base,
(intmax_t)lseek(dnp->dn_fd, 0, SEEK_CUR));
if (bytes < 0) {
if (errno == EINVAL)
panic("EINVAL on readdir\n");
error = errno;
curoff = startoff;
goto out;
} else if (bytes == 0) {
*ap->a_eofflag = 1;
curoff = startoff;
goto out;
}
for (dp = (struct dirent *)buf; bytes > 0 && uio->uio_resid > 0;
bytes -= _DIRENT_DIRSIZ(dp), dp = dpn) {
r = vop_write_dirent(&error, uio, dp->d_ino, dp->d_type,
dp->d_namlen, dp->d_name);
if (error || r)
break;
dpn = _DIRENT_NEXT(dp);
dp = dpn;
cnt++;
}
curoff = lseek(dnp->dn_fd, 0, SEEK_CUR);
out:
kfree(buf, M_DIRFS_MISC);
uio->uio_offset = curoff;
dirfs_node_unlock(dnp);
KTR_LOG(dirfs_readdir, dnp, dnp->dn_fd, startoff, uio->uio_offset);
return error;
}
static int
dirfs_readlink(struct vop_readlink_args *ap)
{
dirfs_node_t dnp, pathnp;
dirfs_mount_t dmp;
struct vnode *vp;
struct mount *mp;
struct uio *uio;
char *tmp, *pathfree, *buf;
ssize_t nlen;
int error;
dbg(3, "called\n");
vp = ap->a_vp;
KKASSERT(vp->v_type == VLNK);
error = 0;
tmp = pathfree = NULL;
uio = ap->a_uio;
mp = vp->v_mount;
dmp = VFS_TO_DIRFS(mp);
dnp = VP_TO_NODE(vp);
lwkt_gettoken(&mp->mnt_token);
pathnp = dirfs_findfd(dmp, dnp, &tmp, &pathfree);
buf = kmalloc(uio->uio_resid, M_DIRFS_MISC, M_WAITOK | M_ZERO);
nlen = readlinkat(pathnp->dn_fd, dnp->dn_name, buf, uio->uio_resid);
if (nlen == -1 ) {
error = errno;
} else {
error = uiomove(buf, nlen + 1, uio);
buf[nlen] = '\0';
if (error)
error = errno;
}
dirfs_dropfd(dmp, pathnp, pathfree);
kfree(buf, M_DIRFS_MISC);
lwkt_reltoken(&mp->mnt_token);
return error;
}
static int
dirfs_inactive(struct vop_inactive_args *ap)
{
struct vnode *vp;
dirfs_mount_t dmp;
dirfs_node_t dnp;
dbg(3, "called\n");
vp = ap->a_vp;
dmp = VFS_TO_DIRFS(vp->v_mount);
dnp = VP_TO_NODE(vp);
if (dnp == NULL) {
dbg(5, "dnp was NULL\n");
vrecycle(vp);
return 0;
}
if (dnp->dn_links == 0) {
vrecycle(vp);
dbg(5, "recycled a vnode of an unlinked dnp\n");
goto out;
}
dirfs_node_setpassive(dmp, dnp, 1);
out:
return 0;
}
int
dirfs_reclaim(struct vop_reclaim_args *ap)
{
struct vnode *vp;
dirfs_node_t dnp;
dirfs_mount_t dmp;
dbg(3, "called\n");
vp = ap->a_vp;
dnp = VP_TO_NODE(vp);
dmp = VFS_TO_DIRFS(vp->v_mount);
dirfs_free_vp(dmp, dnp);
return 0;
}
static int
dirfs_mountctl(struct vop_mountctl_args *ap)
{
dbg(3, "called\n");
KTR_LOG(dirfs_unsupported, __func__);
return EOPNOTSUPP;
}
static int
dirfs_print(struct vop_print_args *v)
{
dbg(3, "called\n");
KTR_LOG(dirfs_unsupported, __func__);
return EOPNOTSUPP;
}
static int __unused
dirfs_pathconf(struct vop_pathconf_args *v)
{
dbg(3, "called\n");
return EOPNOTSUPP;
}
static void filt_dirfsdetach(struct knote *kn);
static int filt_dirfsread(struct knote *kn, long hint);
static int filt_dirfswrite(struct knote *kn, long hint);
static int filt_dirfsvnode(struct knote *kn, long hint);
static struct filterops dirfsread_filtops =
{ FILTEROP_ISFD | FILTEROP_MPSAFE,
NULL, filt_dirfsdetach, filt_dirfsread };
static struct filterops dirfswrite_filtops =
{ FILTEROP_ISFD | FILTEROP_MPSAFE,
NULL, filt_dirfsdetach, filt_dirfswrite };
static struct filterops dirfsvnode_filtops =
{ FILTEROP_ISFD | FILTEROP_MPSAFE,
NULL, filt_dirfsdetach, filt_dirfsvnode };
static int
dirfs_kqfilter (struct vop_kqfilter_args *ap)
{
struct vnode *vp = ap->a_vp;
struct knote *kn = ap->a_kn;
switch (kn->kn_filter) {
case EVFILT_READ:
kn->kn_fop = &dirfsread_filtops;
break;
case EVFILT_WRITE:
kn->kn_fop = &dirfswrite_filtops;
break;
case EVFILT_VNODE:
kn->kn_fop = &dirfsvnode_filtops;
break;
default:
return (EOPNOTSUPP);
}
kn->kn_hook = (caddr_t)vp;
knote_insert(&vp->v_pollinfo.vpi_kqinfo.ki_note, kn);
return(0);
}
static void
filt_dirfsdetach(struct knote *kn)
{
struct vnode *vp = (void *)kn->kn_hook;
knote_remove(&vp->v_pollinfo.vpi_kqinfo.ki_note, kn);
}
static int
filt_dirfsread(struct knote *kn, long hint)
{
struct vnode *vp = (void *)kn->kn_hook;
dirfs_node_t dnp = VP_TO_NODE(vp);
off_t off;
if (hint == NOTE_REVOKE) {
kn->kn_flags |= (EV_EOF | EV_NODATA | EV_ONESHOT);
return(1);
}
dirfs_node_lock(dnp);
off = dnp->dn_size - kn->kn_fp->f_offset;
kn->kn_data = (off < INTPTR_MAX) ? off : INTPTR_MAX;
if (kn->kn_sfflags & NOTE_OLDAPI) {
dirfs_node_unlock(dnp);
return(1);
}
if (kn->kn_data == 0) {
kn->kn_data = (off < INTPTR_MAX) ? off : INTPTR_MAX;
}
dirfs_node_unlock(dnp);
return (kn->kn_data != 0);
}
static int
filt_dirfswrite(struct knote *kn, long hint)
{
if (hint == NOTE_REVOKE)
kn->kn_flags |= (EV_EOF | EV_NODATA | EV_ONESHOT);
kn->kn_data = 0;
return (1);
}
static int
filt_dirfsvnode(struct knote *kn, long hint)
{
if (kn->kn_sfflags & hint)
kn->kn_fflags |= hint;
if (hint == NOTE_REVOKE) {
kn->kn_flags |= (EV_EOF | EV_NODATA);
return (1);
}
return (kn->kn_fflags != 0);
}
struct vop_ops dirfs_vnode_vops = {
.vop_default = vop_defaultop,
.vop_nwhiteout = vop_compat_nwhiteout,
.vop_ncreate = dirfs_ncreate,
.vop_nresolve = dirfs_nresolve,
.vop_markatime = vop_stdmarkatime,
.vop_nlookupdotdot = dirfs_nlookupdotdot,
.vop_nmknod = dirfs_nmknod,
.vop_open = dirfs_open,
.vop_close = dirfs_close,
.vop_access = dirfs_access,
.vop_getattr = dirfs_getattr,
.vop_setattr = dirfs_setattr,
.vop_read = dirfs_read,
.vop_write = dirfs_write,
.vop_fsync = dirfs_fsync,
.vop_mountctl = dirfs_mountctl,
.vop_nremove = dirfs_nremove,
.vop_nlink = dirfs_nlink,
.vop_nrename = dirfs_nrename,
.vop_nmkdir = dirfs_nmkdir,
.vop_nrmdir = dirfs_nrmdir,
.vop_nsymlink = dirfs_nsymlink,
.vop_readdir = dirfs_readdir,
.vop_readlink = dirfs_readlink,
.vop_inactive = dirfs_inactive,
.vop_reclaim = dirfs_reclaim,
.vop_print = dirfs_print,
.vop_pathconf = vop_stdpathconf,
.vop_bmap = dirfs_bmap,
.vop_strategy = dirfs_strategy,
.vop_advlock = dirfs_advlock,
.vop_kqfilter = dirfs_kqfilter,
.vop_getpages = vop_stdgetpages,
.vop_putpages = vop_stdputpages
};