[BACK]Return to fdesc_vnops.c CVS log [TXT][DIR] Up to [cvs.NetBSD.org] / src / sys / miscfs / fdesc

File: [cvs.NetBSD.org] / src / sys / miscfs / fdesc / fdesc_vnops.c (download)

Revision 1.137, Tue Jun 29 22:34:08 2021 UTC (2 years, 9 months ago) by dholland
Branch: MAIN
Changes since 1.136: +3 -2 lines

- Add a new vnode op: VOP_PARSEPATH.
 - Move namei_getcomponent to genfs_vnops.c and call it genfs_parsepath.
 - Add a parsepath entry to every vnode ops table.

VOP_PARSEPATH takes a directory vnode to be searched and a complete
following path and chooses how much of that path to consume. To begin
with, all parsepath calls are genfs_parsepath, which locates the first
'/' as always.

Note that the call doesn't take the whole struct componentname, only
the string. The other bits of struct componentname should not be
needed and there's no reason to cause potential complications by
exposing them.

/*	$NetBSD: fdesc_vnops.c,v 1.137 2021/06/29 22:34:08 dholland Exp $	*/

/*
 * Copyright (c) 1992, 1993
 *	The Regents of the University of California.  All rights reserved.
 *
 * This code is derived from software donated to Berkeley by
 * Jan-Simon Pendry.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of the University nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
 *	@(#)fdesc_vnops.c	8.17 (Berkeley) 5/22/95
 *
 * #Id: fdesc_vnops.c,v 1.12 1993/04/06 16:17:17 jsp Exp #
 */

/*
 * /dev/fd Filesystem
 */

#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: fdesc_vnops.c,v 1.137 2021/06/29 22:34:08 dholland Exp $");

#include <sys/param.h>
#include <sys/systm.h>
#include <sys/time.h>
#include <sys/proc.h>
#include <sys/resourcevar.h>
#include <sys/socketvar.h>
#include <sys/filedesc.h>
#include <sys/vnode.h>
#include <sys/malloc.h>
#include <sys/conf.h>
#include <sys/file.h>
#include <sys/stat.h>
#include <sys/mount.h>
#include <sys/namei.h>
#include <sys/buf.h>
#include <sys/dirent.h>
#include <sys/tty.h>
#include <sys/kauth.h>
#include <sys/atomic.h>

#include <miscfs/fdesc/fdesc.h>
#include <miscfs/genfs/genfs.h>

#define cttyvp(p) ((p)->p_lflag & PL_CONTROLT ? (p)->p_session->s_ttyvp : NULL)

dev_t devctty;

#if (FD_STDIN != FD_STDOUT-1) || (FD_STDOUT != FD_STDERR-1)
FD_STDIN, FD_STDOUT, FD_STDERR must be a sequence n, n+1, n+2
#endif

int	fdesc_lookup(void *);
#define	fdesc_create	genfs_eopnotsupp
#define	fdesc_mknod	genfs_eopnotsupp
int	fdesc_open(void *);
#define	fdesc_close	genfs_nullop
#define	fdesc_access	genfs_nullop
int	fdesc_getattr(void *);
int	fdesc_setattr(void *);
int	fdesc_read(void *);
int	fdesc_write(void *);
int	fdesc_ioctl(void *);
int	fdesc_poll(void *);
int	fdesc_kqfilter(void *);
#define	fdesc_mmap	genfs_eopnotsupp
#define	fdesc_fcntl	genfs_fcntl
#define	fdesc_fsync	genfs_nullop
#define	fdesc_seek	genfs_seek
#define	fdesc_remove	genfs_eopnotsupp
int	fdesc_link(void *);
#define	fdesc_rename	genfs_eopnotsupp
#define	fdesc_mkdir	genfs_eopnotsupp
#define	fdesc_rmdir	genfs_eopnotsupp
int	fdesc_symlink(void *);
int	fdesc_readdir(void *);
int	fdesc_readlink(void *);
#define	fdesc_abortop	genfs_abortop
int	fdesc_inactive(void *);
int	fdesc_reclaim(void *);
#define	fdesc_lock	genfs_lock
#define	fdesc_unlock	genfs_unlock
#define	fdesc_bmap	genfs_eopnotsupp
#define	fdesc_strategy	genfs_badop
int	fdesc_print(void *);
int	fdesc_pathconf(void *);
#define	fdesc_islocked	genfs_islocked
#define	fdesc_advlock	genfs_einval
#define	fdesc_bwrite	genfs_eopnotsupp
#define fdesc_revoke	genfs_revoke
#define fdesc_putpages	genfs_null_putpages

static int fdesc_attr(int, struct vattr *, kauth_cred_t);

int (**fdesc_vnodeop_p)(void *);
const struct vnodeopv_entry_desc fdesc_vnodeop_entries[] = {
	{ &vop_default_desc, vn_default_error },
	{ &vop_parsepath_desc, genfs_parsepath },	/* parsepath */
	{ &vop_lookup_desc, fdesc_lookup },		/* lookup */
	{ &vop_create_desc, fdesc_create },		/* create */
	{ &vop_mknod_desc, fdesc_mknod },		/* mknod */
	{ &vop_open_desc, fdesc_open },			/* open */
	{ &vop_close_desc, fdesc_close },		/* close */
	{ &vop_access_desc, fdesc_access },		/* access */
	{ &vop_accessx_desc, genfs_accessx },		/* accessx */
	{ &vop_getattr_desc, fdesc_getattr },		/* getattr */
	{ &vop_setattr_desc, fdesc_setattr },		/* setattr */
	{ &vop_read_desc, fdesc_read },			/* read */
	{ &vop_write_desc, fdesc_write },		/* write */
	{ &vop_fallocate_desc, genfs_eopnotsupp },	/* fallocate */
	{ &vop_fdiscard_desc, genfs_eopnotsupp },	/* fdiscard */
	{ &vop_ioctl_desc, fdesc_ioctl },		/* ioctl */
	{ &vop_fcntl_desc, fdesc_fcntl },		/* fcntl */
	{ &vop_poll_desc, fdesc_poll },			/* poll */
	{ &vop_kqfilter_desc, fdesc_kqfilter },		/* kqfilter */
	{ &vop_revoke_desc, fdesc_revoke },		/* revoke */
	{ &vop_mmap_desc, fdesc_mmap },			/* mmap */
	{ &vop_fsync_desc, fdesc_fsync },		/* fsync */
	{ &vop_seek_desc, fdesc_seek },			/* seek */
	{ &vop_remove_desc, fdesc_remove },		/* remove */
	{ &vop_link_desc, fdesc_link },			/* link */
	{ &vop_rename_desc, fdesc_rename },		/* rename */
	{ &vop_mkdir_desc, fdesc_mkdir },		/* mkdir */
	{ &vop_rmdir_desc, fdesc_rmdir },		/* rmdir */
	{ &vop_symlink_desc, fdesc_symlink },		/* symlink */
	{ &vop_readdir_desc, fdesc_readdir },		/* readdir */
	{ &vop_readlink_desc, fdesc_readlink },		/* readlink */
	{ &vop_abortop_desc, fdesc_abortop },		/* abortop */
	{ &vop_inactive_desc, fdesc_inactive },		/* inactive */
	{ &vop_reclaim_desc, fdesc_reclaim },		/* reclaim */
	{ &vop_lock_desc, fdesc_lock },			/* lock */
	{ &vop_unlock_desc, fdesc_unlock },		/* unlock */
	{ &vop_bmap_desc, fdesc_bmap },			/* bmap */
	{ &vop_strategy_desc, fdesc_strategy },		/* strategy */
	{ &vop_print_desc, fdesc_print },		/* print */
	{ &vop_islocked_desc, fdesc_islocked },		/* islocked */
	{ &vop_pathconf_desc, fdesc_pathconf },		/* pathconf */
	{ &vop_advlock_desc, fdesc_advlock },		/* advlock */
	{ &vop_bwrite_desc, fdesc_bwrite },		/* bwrite */
	{ &vop_putpages_desc, fdesc_putpages },		/* putpages */
	{ NULL, NULL }
};

const struct vnodeopv_desc fdesc_vnodeop_opv_desc =
	{ &fdesc_vnodeop_p, fdesc_vnodeop_entries };

/*
 * Initialise cache headers
 */
void
fdesc_init(void)
{
	int cttymajor;

	/* locate the major number */
	cttymajor = devsw_name2chr("ctty", NULL, 0);
	devctty = makedev(cttymajor, 0);
}

void
fdesc_done(void)
{
}

/*
 * vp is the current namei directory
 * ndp is the name to locate in that directory...
 */
int
fdesc_lookup(void *v)
{
	struct vop_lookup_v2_args /* {
		struct vnode * a_dvp;
		struct vnode ** a_vpp;
		struct componentname * a_cnp;
	} */ *ap = v;
	struct vnode **vpp = ap->a_vpp;
	struct vnode *dvp = ap->a_dvp;
	struct componentname *cnp = ap->a_cnp;
	struct lwp *l = curlwp;
	const char *pname = cnp->cn_nameptr;
	struct proc *p = l->l_proc;
	unsigned fd = 0;
	int error, ix = -1;
	fdtab_t *dt;

	dt = atomic_load_consume(&curlwp->l_fd->fd_dt);

	if (cnp->cn_namelen == 1 && *pname == '.') {
		*vpp = dvp;
		vref(dvp);
		return (0);
	}

	switch (VTOFDESC(dvp)->fd_type) {
	default:
	case Flink:
	case Fdesc:
	case Fctty:
		error = ENOTDIR;
		goto bad;

	case Froot:
		if (cnp->cn_namelen == 2 && memcmp(pname, "fd", 2) == 0) {
			ix = FD_DEVFD;
			goto good;
		}

		if (cnp->cn_namelen == 3 && memcmp(pname, "tty", 3) == 0) {
			struct vnode *ttyvp = cttyvp(p);
			if (ttyvp == NULL) {
				error = ENXIO;
				goto bad;
			}
			ix = FD_CTTY;
			goto good;
		}

		switch (cnp->cn_namelen) {
		case 5:
			if (memcmp(pname, "stdin", 5) == 0) {
				ix = FD_STDIN;
				goto good;
			}
			break;
		case 6:
			if (memcmp(pname, "stdout", 6) == 0) {
				ix = FD_STDOUT;
				goto good;
			} else if (memcmp(pname, "stderr", 6) == 0) {
				ix = FD_STDERR;
				goto good;
			}
			break;
		}

		error = ENOENT;
		goto bad;

	case Fdevfd:
		if (cnp->cn_namelen == 2 && memcmp(pname, "..", 2) == 0) {
			ix = FD_ROOT;
			goto good;
		}

		fd = 0;
		while (*pname >= '0' && *pname <= '9') {
			fd = 10 * fd + *pname++ - '0';
			if (fd >= dt->dt_nfiles)
				break;
		}

		if (*pname != '\0') {
			error = ENOENT;
			goto bad;
		}

		if (fd >= dt->dt_nfiles || dt->dt_ff[fd] == NULL ||
		    dt->dt_ff[fd]->ff_file == NULL) {
			error = EBADF;
			goto bad;
		}

		ix = FD_DESC + fd;
		goto good;
	}

bad:
	*vpp = NULL;
	return error;

good:
	KASSERT(ix != -1);
	error = vcache_get(dvp->v_mount, &ix, sizeof(ix), vpp);
	if (error)
		return error;

	/*
	 * Prevent returning VNON nodes.
	 * Operation fdesc_inactive() will reset the type to VNON.
	 */
	if (ix == FD_CTTY)
		(*vpp)->v_type = VCHR;
	else if (ix >= FD_DESC)
		(*vpp)->v_type = VREG;
	KASSERT((*vpp)->v_type != VNON);

	return 0;
}

int
fdesc_open(void *v)
{
	struct vop_open_args /* {
		struct vnode *a_vp;
		int  a_mode;
		kauth_cred_t a_cred;
	} */ *ap = v;
	struct vnode *vp = ap->a_vp;

	switch (VTOFDESC(vp)->fd_type) {
	case Fdesc:
		/*
		 * XXX Kludge: set dupfd to contain the value of the
		 * the file descriptor being sought for duplication. The error
		 * return ensures that the vnode for this device will be
		 * released by vn_open. Open will detect this special error and
		 * take the actions in dupfdopen.  Other callers of vn_open or
		 * VOP_OPEN will simply report the error.
		 */
		curlwp->l_dupfd = VTOFDESC(vp)->fd_fd;	/* XXX */
		return EDUPFD;

	case Fctty:
		return cdev_open(devctty, ap->a_mode, 0, curlwp);
	case Froot:
	case Fdevfd:
	case Flink:
		break;
	}

	return (0);
}

static int
fdesc_attr(int fd, struct vattr *vap, kauth_cred_t cred)
{
	file_t *fp;
	struct stat stb;
	int error;

	if ((fp = fd_getfile(fd)) == NULL)
		return (EBADF);

	switch (fp->f_type) {
	case DTYPE_VNODE:
		vn_lock(fp->f_vnode, LK_SHARED | LK_RETRY);
		error = VOP_GETATTR(fp->f_vnode, vap, cred);
		VOP_UNLOCK(fp->f_vnode);
		if (error == 0 && vap->va_type == VDIR) {
			/*
			 * directories can cause loops in the namespace,
			 * so turn off the 'x' bits to avoid trouble.
			 */
			vap->va_mode &= ~(S_IXUSR|S_IXGRP|S_IXOTH);
		}
		break;

	default:
		memset(&stb, 0, sizeof(stb));
		error = (*fp->f_ops->fo_stat)(fp, &stb);
		if (error)
			break;

		vattr_null(vap);
		switch(fp->f_type) {
		case DTYPE_SOCKET:
			vap->va_type = VSOCK;
			break;
		case DTYPE_PIPE:
			vap->va_type = VFIFO;
			break;
		default:
			/* use VNON perhaps? */
			vap->va_type = VBAD;
			break;
		}
		vap->va_mode = stb.st_mode;
		vap->va_nlink = stb.st_nlink;
		vap->va_uid = stb.st_uid;
		vap->va_gid = stb.st_gid;
		vap->va_fsid = stb.st_dev;
		vap->va_fileid = stb.st_ino;
		vap->va_size = stb.st_size;
		vap->va_blocksize = stb.st_blksize;
		vap->va_atime = stb.st_atimespec;
		vap->va_mtime = stb.st_mtimespec;
		vap->va_ctime = stb.st_ctimespec;
		vap->va_gen = stb.st_gen;
		vap->va_flags = stb.st_flags;
		vap->va_rdev = stb.st_rdev;
		vap->va_bytes = stb.st_blocks * stb.st_blksize;
		break;
	}

	fd_putfile(fd);
	return (error);
}

int
fdesc_getattr(void *v)
{
	struct vop_getattr_args /* {
		struct vnode *a_vp;
		struct vattr *a_vap;
		kauth_cred_t a_cred;
		struct lwp *a_l;
	} */ *ap = v;
	struct vnode *vp = ap->a_vp;
	struct vattr *vap = ap->a_vap;
	unsigned fd;
	int error = 0;
	struct timeval tv;

	switch (VTOFDESC(vp)->fd_type) {
	case Froot:
	case Fdevfd:
	case Flink:
	case Fctty:
		vattr_null(vap);
		vap->va_fileid = VTOFDESC(vp)->fd_ix;

#define R_ALL (S_IRUSR|S_IRGRP|S_IROTH)
#define W_ALL (S_IWUSR|S_IWGRP|S_IWOTH)
#define X_ALL (S_IXUSR|S_IXGRP|S_IXOTH)

		switch (VTOFDESC(vp)->fd_type) {
		case Flink:
			vap->va_mode = R_ALL|X_ALL;
			vap->va_type = VLNK;
			vap->va_rdev = 0;
			vap->va_nlink = 1;
			vap->va_size = strlen(VTOFDESC(vp)->fd_link);
			break;

		case Fctty:
			vap->va_mode = R_ALL|W_ALL;
			vap->va_type = VCHR;
			vap->va_rdev = devctty;
			vap->va_nlink = 1;
			vap->va_size = 0;
			break;

		default:
			vap->va_mode = R_ALL|X_ALL;
			vap->va_type = VDIR;
			vap->va_rdev = 0;
			vap->va_nlink = 2;
			vap->va_size = DEV_BSIZE;
			break;
		}
		vap->va_uid = 0;
		vap->va_gid = 0;
		vap->va_fsid = vp->v_mount->mnt_stat.f_fsidx.__fsid_val[0];
		vap->va_blocksize = DEV_BSIZE;
		getmicroboottime(&tv);
		vap->va_atime.tv_sec = tv.tv_sec;
		vap->va_atime.tv_nsec = 0;
		vap->va_mtime = vap->va_atime;
		vap->va_ctime = vap->va_mtime;
		vap->va_gen = 0;
		vap->va_flags = 0;
		vap->va_bytes = 0;
		break;

	case Fdesc:
		fd = VTOFDESC(vp)->fd_fd;
		error = fdesc_attr(fd, vap, ap->a_cred);
		break;

	default:
		panic("fdesc_getattr");
		break;
	}

	if (error == 0)
		vp->v_type = vap->va_type;

	return (error);
}

int
fdesc_setattr(void *v)
{
	struct vop_setattr_args /* {
		struct vnode *a_vp;
		struct vattr *a_vap;
		kauth_cred_t a_cred;
	} */ *ap = v;
	file_t *fp;
	unsigned fd;

	/*
	 * Can't mess with the root vnode
	 */
	switch (VTOFDESC(ap->a_vp)->fd_type) {
	case Fdesc:
		break;

	case Fctty:
		return (0);

	default:
		return (EACCES);
	}

	fd = VTOFDESC(ap->a_vp)->fd_fd;
	if ((fp = fd_getfile(fd)) == NULL)
		return (EBADF);

	/*
	 * XXX: Can't reasonably set the attr's on any types currently.
	 *      On vnode's this will cause truncation and socket/pipes make
	 *      no sense.
	 */
	fd_putfile(fd);
	return (0);
}


struct fdesc_target {
	ino_t ft_fileno;
	u_char ft_type;
	u_char ft_namlen;
	const char *ft_name;
} fdesc_targets[] = {
#define N(s) sizeof(s)-1, s
	{ FD_DEVFD,  DT_DIR,     N("fd")     },
	{ FD_STDIN,  DT_LNK,     N("stdin")  },
	{ FD_STDOUT, DT_LNK,     N("stdout") },
	{ FD_STDERR, DT_LNK,     N("stderr") },
	{ FD_CTTY,   DT_UNKNOWN, N("tty")    },
#undef N
#define UIO_MX _DIRENT_RECLEN((struct dirent *)NULL, sizeof("stderr") - 1)
};
static int nfdesc_targets = sizeof(fdesc_targets) / sizeof(fdesc_targets[0]);

int
fdesc_readdir(void *v)
{
	struct vop_readdir_args /* {
		struct vnode *a_vp;
		struct uio *a_uio;
		kauth_cred_t a_cred;
		int *a_eofflag;
		off_t **a_cookies;
		int *a_ncookies;
	} */ *ap = v;
	struct uio *uio = ap->a_uio;
	struct dirent d;
	off_t i;
	int j;
	int error;
	off_t *cookies = NULL;
	int ncookies;
	fdtab_t *dt;

	switch (VTOFDESC(ap->a_vp)->fd_type) {
	case Fctty:
		return 0;

	case Fdesc:
		return ENOTDIR;

	default:
		break;
	}

	dt = atomic_load_consume(&curlwp->l_fd->fd_dt);

	if (uio->uio_resid < UIO_MX)
		return EINVAL;
	if (uio->uio_offset < 0)
		return EINVAL;

	error = 0;
	i = uio->uio_offset;
	(void)memset(&d, 0, UIO_MX);
	d.d_reclen = UIO_MX;
	if (ap->a_ncookies)
		ncookies = uio->uio_resid / UIO_MX;
	else
		ncookies = 0;

	if (VTOFDESC(ap->a_vp)->fd_type == Froot) {
		struct fdesc_target *ft;

		if (i >= nfdesc_targets)
			return 0;

		if (ap->a_ncookies) {
			ncookies = uimin(ncookies, (nfdesc_targets - i));
			cookies = malloc(ncookies * sizeof(off_t),
			    M_TEMP, M_WAITOK);
			*ap->a_cookies = cookies;
			*ap->a_ncookies = ncookies;
		}

		for (ft = &fdesc_targets[i]; uio->uio_resid >= UIO_MX &&
		    i < nfdesc_targets; ft++, i++) {
			switch (ft->ft_fileno) {
			case FD_CTTY:
				if (cttyvp(curproc) == NULL)
					continue;
				break;

			case FD_STDIN:
			case FD_STDOUT:
			case FD_STDERR:
				if ((ft->ft_fileno - FD_STDIN) >=
				    dt->dt_nfiles)
					continue;
				if (dt->dt_ff[ft->ft_fileno - FD_STDIN]
				    == NULL || dt->dt_ff[ft->ft_fileno -
				    FD_STDIN]->ff_file == NULL)
					continue;
				break;
			}

			d.d_fileno = ft->ft_fileno;
			d.d_namlen = ft->ft_namlen;
			(void)memcpy(d.d_name, ft->ft_name, ft->ft_namlen + 1);
			d.d_type = ft->ft_type;

			if ((error = uiomove(&d, UIO_MX, uio)) != 0)
				break;
			if (cookies)
				*cookies++ = i + 1;
		}
	} else {
		if (ap->a_ncookies) {
			ncookies = uimin(ncookies, dt->dt_nfiles + 2);
			cookies = malloc(ncookies * sizeof(off_t),
			    M_TEMP, M_WAITOK);
			*ap->a_cookies = cookies;
			*ap->a_ncookies = ncookies;
		}
		for (; i - 2 < dt->dt_nfiles && uio->uio_resid >= UIO_MX; i++) {
			switch (i) {
			case 0:
			case 1:
				d.d_fileno = FD_ROOT;		/* XXX */
				d.d_namlen = i + 1;
				(void)memcpy(d.d_name, "..", d.d_namlen);
				d.d_name[i + 1] = '\0';
				d.d_type = DT_DIR;
				break;

			default:
				j = (int)i - 2;
				if (dt->dt_ff[j] == NULL ||
				    dt->dt_ff[j]->ff_file == NULL)
					continue;
				d.d_fileno = j + FD_STDIN;
				d.d_namlen = snprintf(d.d_name,
				    sizeof(d.d_name), "%d", j);
				d.d_type = DT_UNKNOWN;
				break;
			}

			if ((error = uiomove(&d, UIO_MX, uio)) != 0)
				break;
			if (cookies)
				*cookies++ = i + 1;
		}
	}

	if (ap->a_ncookies && error) {
		free(*ap->a_cookies, M_TEMP);
		*ap->a_ncookies = 0;
		*ap->a_cookies = NULL;
	}

	uio->uio_offset = i;
	return error;
}

int
fdesc_readlink(void *v)
{
	struct vop_readlink_args /* {
		struct vnode *a_vp;
		struct uio *a_uio;
		kauth_cred_t a_cred;
	} */ *ap = v;
	struct vnode *vp = ap->a_vp;
	int error;

	if (vp->v_type != VLNK)
		return (EPERM);

	if (VTOFDESC(vp)->fd_type == Flink) {
		const char *ln = VTOFDESC(vp)->fd_link;
		error = uiomove(__UNCONST(ln), strlen(ln), ap->a_uio);
	} else {
		error = EOPNOTSUPP;
	}

	return (error);
}

int
fdesc_read(void *v)
{
	struct vop_read_args /* {
		struct vnode *a_vp;
		struct uio *a_uio;
		int  a_ioflag;
		kauth_cred_t a_cred;
	} */ *ap = v;
	int error = EOPNOTSUPP;
	struct vnode *vp = ap->a_vp;

	switch (VTOFDESC(vp)->fd_type) {
	case Fctty:
		VOP_UNLOCK(vp);
		error = cdev_read(devctty, ap->a_uio, ap->a_ioflag);
		vn_lock(vp, LK_EXCLUSIVE | LK_RETRY);
		break;

	default:
		error = EOPNOTSUPP;
		break;
	}

	return (error);
}

int
fdesc_write(void *v)
{
	struct vop_write_args /* {
		struct vnode *a_vp;
		struct uio *a_uio;
		int  a_ioflag;
		kauth_cred_t a_cred;
	} */ *ap = v;
	int error = EOPNOTSUPP;
	struct vnode *vp = ap->a_vp;

	switch (VTOFDESC(vp)->fd_type) {
	case Fctty:
		VOP_UNLOCK(vp);
		error = cdev_write(devctty, ap->a_uio, ap->a_ioflag);
		vn_lock(vp, LK_EXCLUSIVE | LK_RETRY);
		break;

	default:
		error = EOPNOTSUPP;
		break;
	}

	return (error);
}

int
fdesc_ioctl(void *v)
{
	struct vop_ioctl_args /* {
		struct vnode *a_vp;
		u_long a_command;
		void *a_data;
		int  a_fflag;
		kauth_cred_t a_cred;
	} */ *ap = v;
	int error = EOPNOTSUPP;

	switch (VTOFDESC(ap->a_vp)->fd_type) {
	case Fctty:
		error = cdev_ioctl(devctty, ap->a_command, ap->a_data,
		    ap->a_fflag, curlwp);
		break;

	default:
		error = EOPNOTSUPP;
		break;
	}

	return (error);
}

int
fdesc_poll(void *v)
{
	struct vop_poll_args /* {
		struct vnode *a_vp;
		int a_events;
	} */ *ap = v;
	int revents;

	switch (VTOFDESC(ap->a_vp)->fd_type) {
	case Fctty:
		revents = cdev_poll(devctty, ap->a_events, curlwp);
		break;

	default:
		revents = genfs_poll(v);
		break;
	}

	return (revents);
}

int
fdesc_kqfilter(void *v)
{
	struct vop_kqfilter_args /* {
		struct vnode *a_vp;
		struct knote *a_kn;
	} */ *ap = v;
	int error, fd;
	file_t *fp;

	switch (VTOFDESC(ap->a_vp)->fd_type) {
	case Fctty:
		error = cdev_kqfilter(devctty, ap->a_kn);
		break;

	case Fdesc:
		/* just invoke kqfilter for the underlying descriptor */
		fd = VTOFDESC(ap->a_vp)->fd_fd;
		if ((fp = fd_getfile(fd)) == NULL)
			return (1);
		error = (*fp->f_ops->fo_kqfilter)(fp, ap->a_kn);
		fd_putfile(fd);
		break;

	default:
		return (genfs_kqfilter(v));
	}

	return (error);
}

int
fdesc_inactive(void *v)
{
	struct vop_inactive_v2_args /* {
		struct vnode *a_vp;
	} */ *ap = v;
	struct vnode *vp = ap->a_vp;
	struct fdescnode *fd = VTOFDESC(vp);

	/*
	 * Clear out the v_type field to avoid
	 * nasty things happening on reclaim.
	 */
	if (fd->fd_type == Fctty || fd->fd_type == Fdesc)
		vp->v_type = VNON;

	return (0);
}

int
fdesc_reclaim(void *v)
{
	struct vop_reclaim_v2_args /* {
		struct vnode *a_vp;
	} */ *ap = v;
	struct vnode *vp = ap->a_vp;
	struct fdescnode *fd = VTOFDESC(vp);

	VOP_UNLOCK(vp);

	vp->v_data = NULL;
	kmem_free(fd, sizeof(struct fdescnode));

	return (0);
}

/*
 * Return POSIX pathconf information applicable to special devices.
 */
int
fdesc_pathconf(void *v)
{
	struct vop_pathconf_args /* {
		struct vnode *a_vp;
		int a_name;
		register_t *a_retval;
	} */ *ap = v;

	switch (ap->a_name) {
	case _PC_LINK_MAX:
		*ap->a_retval = LINK_MAX;
		return (0);
	case _PC_MAX_CANON:
		*ap->a_retval = MAX_CANON;
		return (0);
	case _PC_MAX_INPUT:
		*ap->a_retval = MAX_INPUT;
		return (0);
	case _PC_PIPE_BUF:
		*ap->a_retval = PIPE_BUF;
		return (0);
	case _PC_CHOWN_RESTRICTED:
		*ap->a_retval = 1;
		return (0);
	case _PC_VDISABLE:
		*ap->a_retval = _POSIX_VDISABLE;
		return (0);
	case _PC_SYNC_IO:
		*ap->a_retval = 1;
		return (0);
	default:
		return genfs_pathconf(ap);
	}
	/* NOTREACHED */
}

/*
 * Print out the contents of a /dev/fd vnode.
 */
/* ARGSUSED */
int
fdesc_print(void *v)
{
	printf("tag VT_NON, fdesc vnode\n");
	return (0);
}

int
fdesc_link(void *v)
{
	struct vop_link_v2_args /* {
		struct vnode *a_dvp;
		struct vnode *a_vp;
		struct componentname *a_cnp;
	} */ *ap = v;

	VOP_ABORTOP(ap->a_dvp, ap->a_cnp);
	return (EROFS);
}

int
fdesc_symlink(void *v)
{
	struct vop_symlink_v3_args /* {
		struct vnode *a_dvp;
		struct vnode **a_vpp;
		struct componentname *a_cnp;
		struct vattr *a_vap;
		char *a_target;
	} */ *ap = v;

	VOP_ABORTOP(ap->a_dvp, ap->a_cnp);
	return (EROFS);
}