#include <sys/param.h>
#include <sys/systm.h>
#include <sys/errno.h>
#include <sys/vnode.h>
#include <sys/vfs.h>
#include <sys/vfs_opreg.h>
#include <sys/uio.h>
#include <sys/cred.h>
#include <sys/pathname.h>
#include <sys/debug.h>
#include <sys/fs/lofs_node.h>
#include <sys/fs/lofs_info.h>
#include <fs/fs_subr.h>
#include <vm/as.h>
#include <vm/seg.h>
static int
lo_open(vnode_t **vpp, int flag, struct cred *cr, caller_context_t *ct)
{
vnode_t *vp = *vpp;
vnode_t *rvp;
vnode_t *oldvp;
int error;
#ifdef LODEBUG
lo_dprint(4, "lo_open vp %p cnt=%d realvp %p cnt=%d\n",
vp, vp->v_count, realvp(vp), realvp(vp)->v_count);
#endif
oldvp = vp;
vp = rvp = realvp(vp);
VN_HOLD(vp);
error = VOP_OPEN(&rvp, flag, cr, ct);
if (!error && rvp != vp) {
*vpp = makelonode(rvp, vtoli(oldvp->v_vfsp), 0);
if ((*vpp)->v_type == VDIR) {
(vtol(*vpp))->lo_looping |= (vtol(oldvp))->lo_looping;
}
if (IS_DEVVP(*vpp)) {
vnode_t *svp;
svp = specvp(*vpp, (*vpp)->v_rdev, (*vpp)->v_type, cr);
VN_RELE(*vpp);
if (svp == NULL)
error = ENOSYS;
else
*vpp = svp;
}
VN_RELE(oldvp);
} else {
ASSERT(rvp->v_count > 1);
VN_RELE(rvp);
}
return (error);
}
static int
lo_close(
vnode_t *vp,
int flag,
int count,
offset_t offset,
struct cred *cr,
caller_context_t *ct)
{
#ifdef LODEBUG
lo_dprint(4, "lo_close vp %p realvp %p\n", vp, realvp(vp));
#endif
vp = realvp(vp);
return (VOP_CLOSE(vp, flag, count, offset, cr, ct));
}
static int
lo_read(vnode_t *vp, struct uio *uiop, int ioflag, struct cred *cr,
caller_context_t *ct)
{
#ifdef LODEBUG
lo_dprint(4, "lo_read vp %p realvp %p\n", vp, realvp(vp));
#endif
vp = realvp(vp);
return (VOP_READ(vp, uiop, ioflag, cr, ct));
}
static int
lo_write(vnode_t *vp, struct uio *uiop, int ioflag, struct cred *cr,
caller_context_t *ct)
{
#ifdef LODEBUG
lo_dprint(4, "lo_write vp %p realvp %p\n", vp, realvp(vp));
#endif
vp = realvp(vp);
return (VOP_WRITE(vp, uiop, ioflag, cr, ct));
}
static int
lo_ioctl(
vnode_t *vp,
int cmd,
intptr_t arg,
int flag,
struct cred *cr,
int *rvalp,
caller_context_t *ct)
{
#ifdef LODEBUG
lo_dprint(4, "lo_ioctl vp %p realvp %p\n", vp, realvp(vp));
#endif
vp = realvp(vp);
return (VOP_IOCTL(vp, cmd, arg, flag, cr, rvalp, ct));
}
static int
lo_setfl(vnode_t *vp, int oflags, int nflags, cred_t *cr, caller_context_t *ct)
{
vp = realvp(vp);
return (VOP_SETFL(vp, oflags, nflags, cr, ct));
}
static int
lo_getattr(
vnode_t *vp,
struct vattr *vap,
int flags,
struct cred *cr,
caller_context_t *ct)
{
int error;
#ifdef LODEBUG
lo_dprint(4, "lo_getattr vp %p realvp %p\n", vp, realvp(vp));
#endif
if (error = VOP_GETATTR(realvp(vp), vap, flags, cr, ct))
return (error);
return (0);
}
static int
lo_setattr(
vnode_t *vp,
struct vattr *vap,
int flags,
struct cred *cr,
caller_context_t *ct)
{
#ifdef LODEBUG
lo_dprint(4, "lo_setattr vp %p realvp %p\n", vp, realvp(vp));
#endif
vp = realvp(vp);
return (VOP_SETATTR(vp, vap, flags, cr, ct));
}
static int
lo_access(
vnode_t *vp,
int mode,
int flags,
struct cred *cr,
caller_context_t *ct)
{
#ifdef LODEBUG
lo_dprint(4, "lo_access vp %p realvp %p\n", vp, realvp(vp));
#endif
if (mode & VWRITE) {
if (vp->v_type == VREG && vn_is_readonly(vp))
return (EROFS);
}
vp = realvp(vp);
return (VOP_ACCESS(vp, mode, flags, cr, ct));
}
static int
lo_fsync(vnode_t *vp, int syncflag, struct cred *cr, caller_context_t *ct)
{
#ifdef LODEBUG
lo_dprint(4, "lo_fsync vp %p realvp %p\n", vp, realvp(vp));
#endif
vp = realvp(vp);
return (VOP_FSYNC(vp, syncflag, cr, ct));
}
static void
lo_inactive(vnode_t *vp, struct cred *cr, caller_context_t *ct)
{
#ifdef LODEBUG
lo_dprint(4, "lo_inactive %p, realvp %p\n", vp, realvp(vp));
#endif
freelonode(vtol(vp));
}
static int
lo_fid(vnode_t *vp, struct fid *fidp, caller_context_t *ct)
{
#ifdef LODEBUG
lo_dprint(4, "lo_fid %p, realvp %p\n", vp, realvp(vp));
#endif
vp = realvp(vp);
return (VOP_FID(vp, fidp, ct));
}
static int
lo_lookup(
vnode_t *dvp,
char *nm,
vnode_t **vpp,
struct pathname *pnp,
int flags,
vnode_t *rdir,
struct cred *cr,
caller_context_t *ct,
int *direntflags,
pathname_t *realpnp)
{
vnode_t *vp = NULL, *tvp = NULL, *nonlovp;
int error, is_indirectloop;
vnode_t *realdvp = realvp(dvp);
struct loinfo *li = vtoli(dvp->v_vfsp);
int looping = 0;
int autoloop = 0;
int doingdotdot = 0;
int nosub = 0;
int mkflag = 0;
if (nm[0] == '\0' && ! (flags & (CREATE_XATTR_DIR|LOOKUP_XATTR))) {
VN_HOLD(dvp);
*vpp = dvp;
return (0);
}
if (nm[0] == '.' && nm[1] == '.' && nm[2] == '\0') {
doingdotdot++;
while ((realdvp->v_flag & VROOT) && realdvp != rootdir) {
realdvp = realdvp->v_vfsp->vfs_vnodecovered;
ASSERT(realdvp != NULL);
}
}
*vpp = NULL;
if (error = VOP_LOOKUP(realdvp, nm, &vp, pnp, flags, rdir, cr,
ct, direntflags, realpnp)) {
vp = NULL;
goto out;
}
if (nm[0] == '.' && nm[1] == '\0') {
ASSERT(vp == realdvp);
VN_HOLD(dvp);
VN_RELE(vp);
*vpp = dvp;
return (0);
}
if (doingdotdot) {
if ((vtol(dvp))->lo_looping & LO_LOOPING) {
vfs_t *vfsp;
error = vn_vfsrlock_wait(realdvp);
if (error)
goto out;
vfsp = vn_mountedvfs(realdvp);
if (vfsp == NULL) {
vn_vfsunlock(realdvp);
*vpp = makelonode(vp, li, 0);
(vtol(*vpp))->lo_looping |= LO_LOOPING;
return (0);
}
error = VFS_ROOT(vfsp, &tvp);
vn_vfsunlock(realdvp);
if (error)
goto out;
if ((tvp == li->li_rootvp) && (vp == realvp(tvp))) {
*vpp = tvp;
VN_RELE(vp);
return (0);
} else {
VN_RELE(tvp);
if ((vtol(dvp))->lo_looping & LO_AUTOLOOP) {
VN_RELE(vp);
vp = li->li_rootvp;
vp = vp->v_vfsp->vfs_vnodecovered;
VN_HOLD(vp);
*vpp = makelonode(vp, li, 0);
(vtol(*vpp))->lo_looping |= LO_LOOPING;
return (0);
}
}
} else {
*vpp = makelonode(vp, li, 0);
return (0);
}
}
nosub = (vtoli(dvp->v_vfsp)->li_flag & LO_NOSUB);
if (!nosub && (error = traverse(&vp)))
goto out;
if (vp->v_type != VDIR || nosub) {
*vpp = makelonode(vp, li, 0);
if (IS_DEVVP(*vpp)) {
vnode_t *svp;
svp = specvp(*vpp, (*vpp)->v_rdev, (*vpp)->v_type, cr);
VN_RELE(*vpp);
if (svp == NULL)
error = ENOSYS;
else
*vpp = svp;
}
return (error);
}
if (!doingdotdot && vfs_matchops(vp->v_vfsp, lo_vfsops)) {
if (!((vtol(dvp))->lo_looping & LO_LOOPING)) {
if (vp == li->li_rootvp) {
tvp = vp;
vp = vp->v_vfsp->vfs_vnodecovered;
VN_HOLD(vp);
VN_RELE(tvp);
looping++;
} else {
is_indirectloop = 0;
nonlovp = vp;
while (
vfs_matchops(nonlovp->v_vfsp, lo_vfsops) &&
!(is_indirectloop)) {
if (li->li_rootvp == nonlovp) {
is_indirectloop++;
break;
}
nonlovp = realvp(nonlovp);
}
if (is_indirectloop) {
VN_RELE(vp);
vp = nonlovp;
vp = vp->v_vfsp->vfs_vnodecovered;
VN_HOLD(vp);
looping++;
}
}
} else {
realdvp = realvp(dvp);
while (vfs_matchops(realdvp->v_vfsp, lo_vfsops)) {
realdvp = realvp(realdvp);
}
error = VFS_ROOT(realdvp->v_vfsp, &tvp);
if (error)
goto out;
VN_RELE(vp);
vp = tvp;
if (vp->v_vfsp->vfs_vnodecovered == NULL) {
vp = li->li_rootvp;
vp = vp->v_vfsp->vfs_vnodecovered;
VN_RELE(tvp);
error = VFS_ROOT(vp->v_vfsp, &tvp);
if (error)
goto out;
vp = tvp;
if (vp->v_vfsp->vfs_vnodecovered == NULL) {
error = ENOENT;
goto out;
}
}
vp = vp->v_vfsp->vfs_vnodecovered;
VN_HOLD(vp);
VN_RELE(tvp);
mkflag = LOF_FORCE;
autoloop++;
}
}
*vpp = makelonode(vp, li, mkflag);
if ((looping) ||
(((vtol(dvp))->lo_looping & LO_LOOPING) && !doingdotdot)) {
(vtol(*vpp))->lo_looping |= LO_LOOPING;
}
if (autoloop) {
(vtol(*vpp))->lo_looping |= LO_AUTOLOOP;
}
out:
if (error != 0 && vp != NULL)
VN_RELE(vp);
#ifdef LODEBUG
lo_dprint(4,
"lo_lookup dvp %x realdvp %x nm '%s' newvp %x real vp %x error %d\n",
dvp, realvp(dvp), nm, *vpp, vp, error);
#endif
return (error);
}
static int
lo_create(
vnode_t *dvp,
char *nm,
struct vattr *va,
enum vcexcl exclusive,
int mode,
vnode_t **vpp,
struct cred *cr,
int flag,
caller_context_t *ct,
vsecattr_t *vsecp)
{
int error;
vnode_t *vp = NULL;
#ifdef LODEBUG
lo_dprint(4, "lo_create vp %p realvp %p\n", dvp, realvp(dvp));
#endif
if (*nm == '\0') {
ASSERT(vpp && dvp == *vpp);
vp = realvp(*vpp);
}
error = VOP_CREATE(realvp(dvp), nm, va, exclusive, mode, &vp, cr, flag,
ct, vsecp);
if (!error) {
*vpp = makelonode(vp, vtoli(dvp->v_vfsp), 0);
if (IS_DEVVP(*vpp)) {
vnode_t *svp;
svp = specvp(*vpp, (*vpp)->v_rdev, (*vpp)->v_type, cr);
VN_RELE(*vpp);
if (svp == NULL)
error = ENOSYS;
else
*vpp = svp;
}
} else if (error == ENOSYS && exclusive == NONEXCL &&
dvp == vtoli(dvp->v_vfsp)->li_rootvp &&
realvp(dvp)->v_type == VREG) {
if ((error = VOP_ACCESS(dvp, mode, 0, cr, NULL)) == 0) {
struct vattr vattr;
vattr.va_size = 0;
vattr.va_mask = AT_SIZE;
if ((va->va_mask & AT_SIZE) != 0 && va->va_size == 0 &&
VOP_SETATTR(dvp, &vattr, 0, CRED(), NULL) != 0)
return (error);
VN_HOLD(dvp);
*vpp = dvp;
error = 0;
}
}
return (error);
}
static int
lo_remove(
vnode_t *dvp,
char *nm,
struct cred *cr,
caller_context_t *ct,
int flags)
{
#ifdef LODEBUG
lo_dprint(4, "lo_remove vp %p realvp %p\n", dvp, realvp(dvp));
#endif
dvp = realvp(dvp);
return (VOP_REMOVE(dvp, nm, cr, ct, flags));
}
static int
lo_link(
vnode_t *tdvp,
vnode_t *vp,
char *tnm,
struct cred *cr,
caller_context_t *ct,
int flags)
{
vnode_t *realvp;
#ifdef LODEBUG
lo_dprint(4, "lo_link vp %p realvp %p\n", vp, realvp(vp));
#endif
if (vn_is_readonly(vp)) {
return (EROFS);
}
while (vn_matchops(vp, lo_vnodeops)) {
vp = realvp(vp);
}
if (VOP_REALVP(vp, &realvp, ct) == 0)
vp = realvp;
while (vn_matchops(tdvp, lo_vnodeops)) {
tdvp = realvp(tdvp);
}
if (vp->v_vfsp != tdvp->v_vfsp)
return (EXDEV);
return (VOP_LINK(tdvp, vp, tnm, cr, ct, flags));
}
static int
lo_rename(
vnode_t *odvp,
char *onm,
vnode_t *ndvp,
char *nnm,
struct cred *cr,
caller_context_t *ct,
int flags)
{
vnode_t *tnvp;
#ifdef LODEBUG
lo_dprint(4, "lo_rename vp %p realvp %p\n", odvp, realvp(odvp));
#endif
if (odvp->v_vfsp->vfs_flag & VFS_RDONLY)
return (EROFS);
if (vn_matchops(ndvp, lo_vnodeops))
goto rename;
if (VOP_LOOKUP(ndvp, nnm, &tnvp, NULL, 0, NULL, cr,
ct, NULL, NULL) != 0)
goto rename;
if (tnvp->v_type != VDIR) {
VN_RELE(tnvp);
goto rename;
}
if (vn_mountedvfs(tnvp)) {
VN_RELE(tnvp);
return (EBUSY);
}
VN_RELE(tnvp);
rename:
if (vn_matchops(ndvp, lo_vnodeops)) {
ndvp = realvp(ndvp);
} else {
while (vn_matchops(odvp, lo_vnodeops)) {
odvp = realvp(odvp);
}
if (odvp->v_vfsp != ndvp->v_vfsp)
return (EXDEV);
}
return (VOP_RENAME(odvp, onm, ndvp, nnm, cr, ct, flags));
}
static int
lo_mkdir(
vnode_t *dvp,
char *nm,
struct vattr *va,
vnode_t **vpp,
struct cred *cr,
caller_context_t *ct,
int flags,
vsecattr_t *vsecp)
{
int error;
#ifdef LODEBUG
lo_dprint(4, "lo_mkdir vp %p realvp %p\n", dvp, realvp(dvp));
#endif
error = VOP_MKDIR(realvp(dvp), nm, va, vpp, cr, ct, flags, vsecp);
if (!error)
*vpp = makelonode(*vpp, vtoli(dvp->v_vfsp), 0);
return (error);
}
static int
lo_realvp(vnode_t *vp, vnode_t **vpp, caller_context_t *ct)
{
#ifdef LODEBUG
lo_dprint(4, "lo_realvp %p\n", vp);
#endif
while (vn_matchops(vp, lo_vnodeops))
vp = realvp(vp);
if (VOP_REALVP(vp, vpp, ct) != 0)
*vpp = vp;
return (0);
}
static int
lo_rmdir(
vnode_t *dvp,
char *nm,
vnode_t *cdir,
struct cred *cr,
caller_context_t *ct,
int flags)
{
vnode_t *rvp = cdir;
#ifdef LODEBUG
lo_dprint(4, "lo_rmdir vp %p realvp %p\n", dvp, realvp(dvp));
#endif
if (vn_matchops(dvp, vn_getops(rvp)))
(void) lo_realvp(cdir, &rvp, ct);
dvp = realvp(dvp);
return (VOP_RMDIR(dvp, nm, rvp, cr, ct, flags));
}
static int
lo_symlink(
vnode_t *dvp,
char *lnm,
struct vattr *tva,
char *tnm,
struct cred *cr,
caller_context_t *ct,
int flags)
{
#ifdef LODEBUG
lo_dprint(4, "lo_symlink vp %p realvp %p\n", dvp, realvp(dvp));
#endif
dvp = realvp(dvp);
return (VOP_SYMLINK(dvp, lnm, tva, tnm, cr, ct, flags));
}
static int
lo_readlink(
vnode_t *vp,
struct uio *uiop,
struct cred *cr,
caller_context_t *ct)
{
vp = realvp(vp);
return (VOP_READLINK(vp, uiop, cr, ct));
}
static int
lo_readdir(
vnode_t *vp,
struct uio *uiop,
struct cred *cr,
int *eofp,
caller_context_t *ct,
int flags)
{
#ifdef LODEBUG
lo_dprint(4, "lo_readdir vp %p realvp %p\n", vp, realvp(vp));
#endif
vp = realvp(vp);
return (VOP_READDIR(vp, uiop, cr, eofp, ct, flags));
}
static int
lo_rwlock(vnode_t *vp, int write_lock, caller_context_t *ct)
{
vp = realvp(vp);
return (VOP_RWLOCK(vp, write_lock, ct));
}
static void
lo_rwunlock(vnode_t *vp, int write_lock, caller_context_t *ct)
{
vp = realvp(vp);
VOP_RWUNLOCK(vp, write_lock, ct);
}
static int
lo_seek(vnode_t *vp, offset_t ooff, offset_t *noffp, caller_context_t *ct)
{
vp = realvp(vp);
return (VOP_SEEK(vp, ooff, noffp, ct));
}
static int
lo_cmp(vnode_t *vp1, vnode_t *vp2, caller_context_t *ct)
{
while (vn_matchops(vp1, lo_vnodeops))
vp1 = realvp(vp1);
while (vn_matchops(vp2, lo_vnodeops))
vp2 = realvp(vp2);
return (VOP_CMP(vp1, vp2, ct));
}
static int
lo_frlock(
vnode_t *vp,
int cmd,
struct flock64 *bfp,
int flag,
offset_t offset,
struct flk_callback *flk_cbp,
cred_t *cr,
caller_context_t *ct)
{
vp = realvp(vp);
return (VOP_FRLOCK(vp, cmd, bfp, flag, offset, flk_cbp, cr, ct));
}
static int
lo_space(
vnode_t *vp,
int cmd,
struct flock64 *bfp,
int flag,
offset_t offset,
struct cred *cr,
caller_context_t *ct)
{
vp = realvp(vp);
return (VOP_SPACE(vp, cmd, bfp, flag, offset, cr, ct));
}
static int
lo_getpage(
vnode_t *vp,
offset_t off,
size_t len,
uint_t *prot,
struct page *parr[],
size_t psz,
struct seg *seg,
caddr_t addr,
enum seg_rw rw,
struct cred *cr,
caller_context_t *ct)
{
vp = realvp(vp);
return (VOP_GETPAGE(vp, off, len, prot, parr, psz, seg, addr, rw, cr,
ct));
}
static int
lo_putpage(
vnode_t *vp,
offset_t off,
size_t len,
int flags,
struct cred *cr,
caller_context_t *ct)
{
vp = realvp(vp);
return (VOP_PUTPAGE(vp, off, len, flags, cr, ct));
}
static int
lo_map(
vnode_t *vp,
offset_t off,
struct as *as,
caddr_t *addrp,
size_t len,
uchar_t prot,
uchar_t maxprot,
uint_t flags,
struct cred *cr,
caller_context_t *ct)
{
vp = realvp(vp);
return (VOP_MAP(vp, off, as, addrp, len, prot, maxprot, flags, cr, ct));
}
static int
lo_addmap(
vnode_t *vp,
offset_t off,
struct as *as,
caddr_t addr,
size_t len,
uchar_t prot,
uchar_t maxprot,
uint_t flags,
struct cred *cr,
caller_context_t *ct)
{
vp = realvp(vp);
return (VOP_ADDMAP(vp, off, as, addr, len, prot, maxprot, flags, cr,
ct));
}
static int
lo_delmap(
vnode_t *vp,
offset_t off,
struct as *as,
caddr_t addr,
size_t len,
uint_t prot,
uint_t maxprot,
uint_t flags,
struct cred *cr,
caller_context_t *ct)
{
vp = realvp(vp);
return (VOP_DELMAP(vp, off, as, addr, len, prot, maxprot, flags, cr,
ct));
}
static int
lo_poll(
vnode_t *vp,
short events,
int anyyet,
short *reventsp,
struct pollhead **phpp,
caller_context_t *ct)
{
vp = realvp(vp);
return (VOP_POLL(vp, events, anyyet, reventsp, phpp, ct));
}
static int
lo_dump(vnode_t *vp, caddr_t addr, offset_t bn, offset_t count,
caller_context_t *ct)
{
vp = realvp(vp);
return (VOP_DUMP(vp, addr, bn, count, ct));
}
static int
lo_pathconf(
vnode_t *vp,
int cmd,
ulong_t *valp,
struct cred *cr,
caller_context_t *ct)
{
vp = realvp(vp);
return (VOP_PATHCONF(vp, cmd, valp, cr, ct));
}
static int
lo_pageio(
vnode_t *vp,
struct page *pp,
u_offset_t io_off,
size_t io_len,
int flags,
cred_t *cr,
caller_context_t *ct)
{
vp = realvp(vp);
return (VOP_PAGEIO(vp, pp, io_off, io_len, flags, cr, ct));
}
static void
lo_dispose(
vnode_t *vp,
page_t *pp,
int fl,
int dn,
cred_t *cr,
caller_context_t *ct)
{
vp = realvp(vp);
if (vp != NULL && !VN_ISKAS(vp))
VOP_DISPOSE(vp, pp, fl, dn, cr, ct);
}
static int
lo_setsecattr(
vnode_t *vp,
vsecattr_t *secattr,
int flags,
struct cred *cr,
caller_context_t *ct)
{
if (vn_is_readonly(vp))
return (EROFS);
vp = realvp(vp);
return (VOP_SETSECATTR(vp, secattr, flags, cr, ct));
}
static int
lo_getsecattr(
vnode_t *vp,
vsecattr_t *secattr,
int flags,
struct cred *cr,
caller_context_t *ct)
{
vp = realvp(vp);
return (VOP_GETSECATTR(vp, secattr, flags, cr, ct));
}
static int
lo_shrlock(
vnode_t *vp,
int cmd,
struct shrlock *shr,
int flag,
cred_t *cr,
caller_context_t *ct)
{
vp = realvp(vp);
return (VOP_SHRLOCK(vp, cmd, shr, flag, cr, ct));
}
struct vnodeops *lo_vnodeops;
const fs_operation_def_t lo_vnodeops_template[] = {
VOPNAME_OPEN, { .vop_open = lo_open },
VOPNAME_CLOSE, { .vop_close = lo_close },
VOPNAME_READ, { .vop_read = lo_read },
VOPNAME_WRITE, { .vop_write = lo_write },
VOPNAME_IOCTL, { .vop_ioctl = lo_ioctl },
VOPNAME_SETFL, { .vop_setfl = lo_setfl },
VOPNAME_GETATTR, { .vop_getattr = lo_getattr },
VOPNAME_SETATTR, { .vop_setattr = lo_setattr },
VOPNAME_ACCESS, { .vop_access = lo_access },
VOPNAME_LOOKUP, { .vop_lookup = lo_lookup },
VOPNAME_CREATE, { .vop_create = lo_create },
VOPNAME_REMOVE, { .vop_remove = lo_remove },
VOPNAME_LINK, { .vop_link = lo_link },
VOPNAME_RENAME, { .vop_rename = lo_rename },
VOPNAME_MKDIR, { .vop_mkdir = lo_mkdir },
VOPNAME_RMDIR, { .vop_rmdir = lo_rmdir },
VOPNAME_READDIR, { .vop_readdir = lo_readdir },
VOPNAME_SYMLINK, { .vop_symlink = lo_symlink },
VOPNAME_READLINK, { .vop_readlink = lo_readlink },
VOPNAME_FSYNC, { .vop_fsync = lo_fsync },
VOPNAME_INACTIVE, { .vop_inactive = lo_inactive },
VOPNAME_FID, { .vop_fid = lo_fid },
VOPNAME_RWLOCK, { .vop_rwlock = lo_rwlock },
VOPNAME_RWUNLOCK, { .vop_rwunlock = lo_rwunlock },
VOPNAME_SEEK, { .vop_seek = lo_seek },
VOPNAME_CMP, { .vop_cmp = lo_cmp },
VOPNAME_FRLOCK, { .vop_frlock = lo_frlock },
VOPNAME_SPACE, { .vop_space = lo_space },
VOPNAME_REALVP, { .vop_realvp = lo_realvp },
VOPNAME_GETPAGE, { .vop_getpage = lo_getpage },
VOPNAME_PUTPAGE, { .vop_putpage = lo_putpage },
VOPNAME_MAP, { .vop_map = lo_map },
VOPNAME_ADDMAP, { .vop_addmap = lo_addmap },
VOPNAME_DELMAP, { .vop_delmap = lo_delmap },
VOPNAME_POLL, { .vop_poll = lo_poll },
VOPNAME_DUMP, { .vop_dump = lo_dump },
VOPNAME_DUMPCTL, { .error = fs_error },
VOPNAME_PATHCONF, { .vop_pathconf = lo_pathconf },
VOPNAME_PAGEIO, { .vop_pageio = lo_pageio },
VOPNAME_DISPOSE, { .vop_dispose = lo_dispose },
VOPNAME_SETSECATTR, { .vop_setsecattr = lo_setsecattr },
VOPNAME_GETSECATTR, { .vop_getsecattr = lo_getsecattr },
VOPNAME_SHRLOCK, { .vop_shrlock = lo_shrlock },
NULL, NULL
};