#include <sys/param.h>
#include <sys/systm.h>
#include <sys/types.h>
#include <sys/time.h>
#include <sys/proc_internal.h>
#include <sys/kernel.h>
#include <sys/resourcevar.h>
#include <sys/filedesc.h>
#include <sys/kauth.h>
#include <sys/vnode_internal.h>
#include <sys/malloc.h>
#include <sys/file_internal.h>
#include <sys/stat.h>
#include <sys/mount_internal.h>
#include <sys/namei.h>
#include <sys/dirent.h>
#include <sys/ubc.h>
#include <sys/socketvar.h>
#include <sys/pipe.h>
#include <sys/uio_internal.h>
#include <miscfs/fdesc/fdesc.h>
#include <vfs/vfs_support.h>
#include <pexpert/pexpert.h>
extern int soo_stat(struct socket *so, void *ub, int isstat64);
#define FDL_WANT 0x01
#define FDL_LOCKED 0x02
static int fdcache_lock;
#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
#define NFDCACHE 3
#define FD_NHASH(ix) \
(&fdhashtbl[(ix) & fdhash])
LIST_HEAD(fdhashhead, fdescnode) *fdhashtbl;
u_long fdhash;
static int fdesc_attr(int fd, struct vnode_attr *vap, vfs_context_t a_context);
int
fdesc_init(__unused struct vfsconf *vfsp)
{
fdhashtbl = hashinit(NFDCACHE, M_CACHE, &fdhash);
return( 0 );
}
int
fdesc_allocvp(fdntype ftype, int ix, struct mount *mp, struct vnode **vpp,
enum vtype vtype)
{
struct fdhashhead *fc;
struct fdescnode *fd;
int error = 0;
int vid = 0;
struct vnode_fsparam vfsp;
fc = FD_NHASH(ix);
loop:
for (fd = fc->lh_first; fd != 0; fd = fd->fd_hash.le_next) {
if (fd->fd_ix == ix && vnode_mount(fd->fd_vnode) == mp) {
vid = vnode_vid(fd->fd_vnode);
if (vnode_getwithvid(fd->fd_vnode, vid))
goto loop;
*vpp = fd->fd_vnode;
(*vpp)->v_type = vtype;
return (error);
}
}
if (fdcache_lock & FDL_LOCKED) {
fdcache_lock |= FDL_WANT;
sleep((caddr_t) &fdcache_lock, PINOD);
goto loop;
}
fdcache_lock |= FDL_LOCKED;
MALLOC(fd, void *, sizeof(struct fdescnode), M_TEMP, M_WAITOK);
vfsp.vnfs_mp = mp;
vfsp.vnfs_vtype = vtype;
vfsp.vnfs_str = "fdesc";
vfsp.vnfs_dvp = NULL;
vfsp.vnfs_fsnode = fd;
vfsp.vnfs_cnp = NULL;
vfsp.vnfs_vops = fdesc_vnodeop_p;
vfsp.vnfs_rdev = 0;
vfsp.vnfs_filesize = 0;
vfsp.vnfs_flags = VNFS_NOCACHE | VNFS_CANTCACHE;
vfsp.vnfs_marksystem = 0;
if (ftype == Froot)
vfsp.vnfs_markroot = 1;
else
vfsp.vnfs_markroot = 0;
error = vnode_create(VNCREATE_FLAVOR, VCREATESIZE, &vfsp, vpp);
if (error) {
FREE(fd, M_TEMP);
goto out;
}
(*vpp)->v_tag = VT_FDESC;
fd->fd_vnode = *vpp;
fd->fd_type = ftype;
fd->fd_fd = -1;
fd->fd_link = NULL;
fd->fd_ix = ix;
LIST_INSERT_HEAD(fc, fd, fd_hash);
out:
fdcache_lock &= ~FDL_LOCKED;
if (fdcache_lock & FDL_WANT) {
fdcache_lock &= ~FDL_WANT;
wakeup((caddr_t) &fdcache_lock);
}
return (error);
}
int
fdesc_lookup(struct vnop_lookup_args *ap)
{
struct vnode **vpp = ap->a_vpp;
struct vnode *dvp = ap->a_dvp;
struct componentname *cnp = ap->a_cnp;
char *pname = cnp->cn_nameptr;
struct proc *p = vfs_context_proc(ap->a_context);
int numfiles = p->p_fd->fd_nfiles;
int fd;
int error;
struct vnode *fvp;
const char *ln;
if (cnp->cn_namelen == 1 && *pname == '.') {
*vpp = dvp;
if ( (error = vnode_get(dvp)) ) {
return(error);
}
return (0);
}
switch (VTOFDESC(dvp)->fd_type) {
default:
case Flink:
case Fdesc:
error = ENOTDIR;
goto bad;
case Froot:
if (cnp->cn_namelen == 2 && bcmp(pname, "fd", 2) == 0) {
error = fdesc_allocvp(Fdevfd, FD_DEVFD, dvp->v_mount, &fvp, VDIR);
if (error)
goto bad;
*vpp = fvp;
return (0);
}
ln = NULL;
switch (cnp->cn_namelen) {
case 5:
if (bcmp(pname, "stdin", 5) == 0) {
ln = "fd/0";
fd = FD_STDIN;
}
break;
case 6:
if (bcmp(pname, "stdout", 6) == 0) {
ln = "fd/1";
fd = FD_STDOUT;
} else
if (bcmp(pname, "stderr", 6) == 0) {
ln = "fd/2";
fd = FD_STDERR;
}
break;
}
if (ln) {
error = fdesc_allocvp(Flink, fd, dvp->v_mount, &fvp, VLNK);
if (error)
goto bad;
VTOFDESC(fvp)->fd_link = ln;
*vpp = fvp;
return (0);
} else {
error = ENOENT;
goto bad;
}
case Fdevfd:
if (cnp->cn_namelen == 2 && bcmp(pname, "..", 2) == 0) {
if ((error = fdesc_root(dvp->v_mount, vpp, ap->a_context)))
goto bad;
return (0);
}
fd = 0;
while (*pname >= '0' && *pname <= '9') {
fd = 10 * fd + *pname++ - '0';
if (fd >= numfiles)
break;
}
if (*pname != '\0') {
error = ENOENT;
goto bad;
}
if (fd < 0 || fd >= numfiles ||
*fdfile(p, fd) == NULL ||
(*fdflags(p, fd) & UF_RESERVED)) {
error = EBADF;
goto bad;
}
error = fdesc_allocvp(Fdesc, FD_DESC+fd, dvp->v_mount, &fvp, VNON);
if (error)
goto bad;
VTOFDESC(fvp)->fd_fd = fd;
*vpp = fvp;
return (0);
}
bad:;
*vpp = NULL;
return (error);
}
int
fdesc_open(struct vnop_open_args *ap)
{
struct vnode *vp = ap->a_vp;
thread_t thr = vfs_context_thread(ap->a_context);
uthread_t uu;
int error = 0;
if (thr == NULL)
return (EINVAL);
uu = get_bsdthread_info(thr);
switch (VTOFDESC(vp)->fd_type) {
case Fdesc:
uu->uu_dupfd = VTOFDESC(vp)->fd_fd;
error = ENODEV;
break;
default:
break;
}
return (error);
}
static int
fdesc_attr(int fd, struct vnode_attr *vap, vfs_context_t a_context)
{
struct fileproc *fp;
struct proc *p = vfs_context_proc(a_context);
struct stat stb;
int error;
if ((error = fp_lookup(p, fd, &fp, 0)))
return (error);
switch (fp->f_fglob->fg_type) {
case DTYPE_VNODE:
if((error = vnode_getwithref((struct vnode *) fp->f_fglob->fg_data)) != 0) {
break;
}
if ((error = vnode_authorize((struct vnode *)fp->f_fglob->fg_data,
NULL,
KAUTH_VNODE_READ_ATTRIBUTES | KAUTH_VNODE_READ_SECURITY,
a_context)) == 0)
error = vnode_getattr((struct vnode *)fp->f_fglob->fg_data, vap, a_context);
if (error == 0 && vap->va_type == VDIR) {
vap->va_mode &= ~((VEXEC)|(VEXEC>>3)|(VEXEC>>6));
}
(void)vnode_put((struct vnode *) fp->f_fglob->fg_data);
break;
case DTYPE_SOCKET:
case DTYPE_PIPE:
#if SOCKETS
if (fp->f_fglob->fg_type == DTYPE_SOCKET)
error = soo_stat((struct socket *)fp->f_fglob->fg_data, (void *)&stb, 0);
else
#endif
error = pipe_stat((struct pipe *)fp->f_fglob->fg_data, (void *)&stb, 0);
if (error == 0) {
if (fp->f_fglob->fg_type == DTYPE_SOCKET)
VATTR_RETURN(vap, va_type, VSOCK);
else
VATTR_RETURN(vap, va_type, VFIFO);
VATTR_RETURN(vap, va_mode, stb.st_mode);
VATTR_RETURN(vap, va_nlink, stb.st_nlink);
VATTR_RETURN(vap, va_uid, stb.st_uid);
VATTR_RETURN(vap, va_gid, stb.st_gid);
VATTR_RETURN(vap, va_fsid, stb.st_dev);
VATTR_RETURN(vap, va_fileid, stb.st_ino);
VATTR_RETURN(vap, va_data_size, stb.st_size);
VATTR_RETURN(vap, va_access_time, stb.st_atimespec);
VATTR_RETURN(vap, va_modify_time, stb.st_mtimespec);
VATTR_RETURN(vap, va_change_time, stb.st_ctimespec);
VATTR_RETURN(vap, va_gen, stb.st_gen);
VATTR_RETURN(vap, va_flags, stb.st_flags);
VATTR_RETURN(vap, va_rdev, stb.st_rdev);
VATTR_RETURN(vap, va_total_alloc, stb.st_blocks * stb.st_blksize);
VATTR_RETURN(vap, va_acl, NULL);
}
break;
default:
error = EBADF;
}
fp_drop(p, fd, fp, 0);
return (error);
}
int
fdesc_getattr(struct vnop_getattr_args *ap)
{
struct vnode *vp = ap->a_vp;
struct vnode_attr *vap = ap->a_vap;
unsigned fd;
int error = 0;
struct timespec ts;
switch (VTOFDESC(vp)->fd_type) {
case Froot:
case Fdevfd:
case Flink:
VATTR_RETURN(vap, va_fileid, VTOFDESC(vp)->fd_ix);
VATTR_RETURN(vap, va_uid, 0);
VATTR_RETURN(vap, va_gid, 0);
VATTR_RETURN(vap, va_fsid, vp->v_mount->mnt_vfsstat.f_fsid.val[0]);
VATTR_RETURN(vap, va_iosize, DEV_BSIZE);
ts.tv_sec = boottime_sec();
ts.tv_nsec = 0;
VATTR_RETURN(vap, va_access_time, ts);
VATTR_RETURN(vap, va_modify_time, ts);
VATTR_RETURN(vap, va_change_time, ts);
VATTR_RETURN(vap, va_gen, 0);
VATTR_RETURN(vap, va_flags, 0);
VATTR_RETURN(vap, va_rdev, 0);
VATTR_RETURN(vap, va_acl, NULL);
switch (VTOFDESC(vp)->fd_type) {
case Flink:
VATTR_RETURN(vap, va_mode, S_IRUSR|S_IRGRP|S_IROTH);
VATTR_RETURN(vap, va_type, VLNK);
VATTR_RETURN(vap, va_nlink, 1);
VATTR_RETURN(vap, va_data_size, strlen(VTOFDESC(vp)->fd_link));
break;
default:
VATTR_RETURN(vap, va_mode, S_IRUSR|S_IXUSR|S_IRGRP|S_IXGRP|S_IROTH|S_IXOTH);
VATTR_RETURN(vap, va_type, VDIR);
VATTR_RETURN(vap, va_nlink, 2);
VATTR_RETURN(vap, va_data_size, DEV_BSIZE);
break;
}
break;
case Fdesc:
fd = VTOFDESC(vp)->fd_fd;
error = fdesc_attr(fd, vap, ap->a_context);
break;
default:
return (EBADF);
break;
}
if (error == 0) {
vp->v_type = vap->va_type;
}
return (error);
}
int
fdesc_setattr(struct vnop_setattr_args *ap)
{
struct fileproc *fp;
unsigned fd;
int error;
struct proc * p = vfs_context_proc(ap->a_context);
switch (VTOFDESC(ap->a_vp)->fd_type) {
case Fdesc:
break;
default:
return (EACCES);
}
fd = VTOFDESC(ap->a_vp)->fd_fd;
if ((error = fp_lookup(vfs_context_proc(ap->a_context), fd, &fp, 0)))
return (error);
switch (fp->f_fglob->fg_type) {
case DTYPE_VNODE:
{
if ((error = vnode_getwithref((struct vnode *) fp->f_fglob->fg_data)) != 0)
break;
error = vnode_setattr((struct vnode *) fp->f_fglob->fg_data, ap->a_vap, ap->a_context);
(void)vnode_put((struct vnode *) fp->f_fglob->fg_data);
break;
}
case DTYPE_SOCKET:
case DTYPE_PIPE:
error = 0;
break;
default:
kprintf("fp->f_fglob->fg_type = %d\n", fp->f_fglob->fg_type);
error = EBADF;
break;
}
fp_drop(p, fd, fp, 0);
return (error);
}
#define UIO_MX 16
static struct dirtmp {
u_long d_fileno;
u_short d_reclen;
u_short d_namlen;
char d_name[8];
} rootent[] = {
{ FD_DEVFD, UIO_MX, 2, "fd" },
{ FD_STDIN, UIO_MX, 5, "stdin" },
{ FD_STDOUT, UIO_MX, 6, "stdout" },
{ FD_STDERR, UIO_MX, 6, "stderr" },
{ 0, 0, 0, "" }
};
int
fdesc_readdir(struct vnop_readdir_args *ap)
{
struct uio *uio = ap->a_uio;
struct proc *p = current_proc();
int i, error;
if (ap->a_flags & (VNODE_READDIR_EXTENDED | VNODE_READDIR_REQSEEKOFF))
return (EINVAL);
switch (VTOFDESC(ap->a_vp)->fd_type) {
case Fdesc:
return (ENOTDIR);
default:
break;
}
if (VTOFDESC(ap->a_vp)->fd_type == Froot) {
struct dirent d;
struct dirent *dp = &d;
struct dirtmp *dt;
int fd;
i = uio->uio_offset / UIO_MX;
error = 0;
while (uio_resid(uio) > 0) {
dt = &rootent[i];
if (dt->d_fileno == 0) {
break;
}
i++;
switch (dt->d_fileno) {
case FD_STDIN:
case FD_STDOUT:
case FD_STDERR:
fd = dt->d_fileno - FD_STDIN;
if (fd >= p->p_fd->fd_nfiles)
continue;
if (*fdfile(p, fd) == NULL &&
!(*fdflags(p, fd) &
UF_RESERVED))
continue;
break;
}
bzero((caddr_t) dp, UIO_MX);
dp->d_fileno = dt->d_fileno;
dp->d_namlen = dt->d_namlen;
dp->d_type = DT_UNKNOWN;
dp->d_reclen = dt->d_reclen;
bcopy(dt->d_name, dp->d_name, dp->d_namlen+1);
error = uiomove((caddr_t) dp, UIO_MX, uio);
if (error)
break;
}
uio->uio_offset = i * UIO_MX;
return (error);
}
i = uio->uio_offset / UIO_MX;
error = 0;
while (uio_resid(uio) > 0) {
if (i >= p->p_fd->fd_nfiles)
break;
if (*fdfile(p, i) != NULL && !(*fdflags(p, i) & UF_RESERVED)) {
struct dirent d;
struct dirent *dp = &d;
bzero((caddr_t) dp, UIO_MX);
dp->d_namlen = snprintf(dp->d_name, sizeof(dp->d_name),
"%d", i);
dp->d_reclen = UIO_MX;
dp->d_type = DT_UNKNOWN;
dp->d_fileno = i + FD_STDIN;
error = uiomove((caddr_t) dp, UIO_MX, uio);
if (error)
break;
}
i++;
}
uio->uio_offset = i * UIO_MX;
return (error);
}
int
fdesc_readlink(struct vnop_readlink_args *ap)
{
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(ln, strlen(ln), ap->a_uio);
} else {
error = ENOTSUP;
}
return (error);
}
int
fdesc_read(__unused struct vnop_read_args *ap)
{
return (ENOTSUP);
}
int
fdesc_write(__unused struct vnop_write_args *ap)
{
return (ENOTSUP);
}
int
fdesc_ioctl(__unused struct vnop_ioctl_args *ap)
{
return (ENOTSUP);
}
int
fdesc_select(__unused struct vnop_select_args *ap)
{
return (ENOTSUP);
}
int
fdesc_inactive(struct vnop_inactive_args *ap)
{
struct vnode *vp = ap->a_vp;
vp->v_type = VNON;
return (0);
}
int
fdesc_reclaim(struct vnop_reclaim_args *ap)
{
struct vnode *vp = ap->a_vp;
struct fdescnode *fd = VTOFDESC(vp);
LIST_REMOVE(fd, fd_hash);
FREE(vp->v_data, M_TEMP);
vp->v_data = NULL;
return (0);
}
int
fdesc_pathconf(struct vnop_pathconf_args *ap)
{
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 = 200112;
return (0);
case _PC_VDISABLE:
*ap->a_retval = _POSIX_VDISABLE;
return (0);
default:
return (EINVAL);
}
}
int
fdesc_badop(void)
{
return (ENOTSUP);
}
#define VOPFUNC int (*)(void *)
#define fdesc_create (int (*) (struct vnop_create_args *))eopnotsupp
#define fdesc_mknod (int (*) (struct vnop_mknod_args *))eopnotsupp
#define fdesc_close (int (*) (struct vnop_close_args *))nullop
#define fdesc_access (int (*) (struct vnop_access_args *))nullop
#define fdesc_mmap (int (*) (struct vnop_mmap_args *))eopnotsupp
#define fdesc_revoke nop_revoke
#define fdesc_fsync (int (*) (struct vnop_fsync_args *))nullop
#define fdesc_remove (int (*) (struct vnop_remove_args *))eopnotsupp
#define fdesc_link (int (*) (struct vnop_link_args *))eopnotsupp
#define fdesc_rename (int (*) (struct vnop_rename_args *))eopnotsupp
#define fdesc_mkdir (int (*) (struct vnop_mkdir_args *))eopnotsupp
#define fdesc_rmdir (int (*) (struct vnop_rmdir_args *))eopnotsupp
#define fdesc_symlink (int (*) (struct vnop_symlink_args *))eopnotsupp
#define fdesc_strategy (int (*) (struct vnop_strategy_args *))fdesc_badop
#define fdesc_advlock (int (*) (struct vnop_advlock_args *))eopnotsupp
#define fdesc_bwrite (int (*) (struct vnop_bwrite_args *))eopnotsupp
#define fdesc_blktooff (int (*) (struct vnop_blktooff_args *))eopnotsupp
#define fdesc_offtoblk (int (*) (struct vnop_offtoblk_args *))eopnotsupp
#define fdesc_blockmap (int (*) (struct vnop_blockmap_args *))eopnotsupp
int (**fdesc_vnodeop_p)(void *);
struct vnodeopv_entry_desc fdesc_vnodeop_entries[] = {
{ &vnop_default_desc, (VOPFUNC)vn_default_error },
{ &vnop_lookup_desc, (VOPFUNC)fdesc_lookup },
{ &vnop_create_desc, (VOPFUNC)fdesc_create },
{ &vnop_mknod_desc, (VOPFUNC)fdesc_mknod },
{ &vnop_open_desc, (VOPFUNC)fdesc_open },
{ &vnop_close_desc, (VOPFUNC)fdesc_close },
{ &vnop_access_desc, (VOPFUNC)fdesc_access },
{ &vnop_getattr_desc, (VOPFUNC)fdesc_getattr },
{ &vnop_setattr_desc, (VOPFUNC)fdesc_setattr },
{ &vnop_read_desc, (VOPFUNC)fdesc_read },
{ &vnop_write_desc, (VOPFUNC)fdesc_write },
{ &vnop_ioctl_desc, (VOPFUNC)fdesc_ioctl },
{ &vnop_select_desc, (VOPFUNC)fdesc_select },
{ &vnop_revoke_desc, (VOPFUNC)fdesc_revoke },
{ &vnop_mmap_desc, (VOPFUNC)fdesc_mmap },
{ &vnop_fsync_desc, (VOPFUNC)fdesc_fsync },
{ &vnop_remove_desc, (VOPFUNC)fdesc_remove },
{ &vnop_link_desc, (VOPFUNC)fdesc_link },
{ &vnop_rename_desc, (VOPFUNC)fdesc_rename },
{ &vnop_mkdir_desc, (VOPFUNC)fdesc_mkdir },
{ &vnop_rmdir_desc, (VOPFUNC)fdesc_rmdir },
{ &vnop_symlink_desc, (VOPFUNC)fdesc_symlink },
{ &vnop_readdir_desc, (VOPFUNC)fdesc_readdir },
{ &vnop_readlink_desc, (VOPFUNC)fdesc_readlink },
{ &vnop_inactive_desc, (VOPFUNC)fdesc_inactive },
{ &vnop_reclaim_desc, (VOPFUNC)fdesc_reclaim },
{ &vnop_strategy_desc, (VOPFUNC)fdesc_strategy },
{ &vnop_pathconf_desc, (VOPFUNC)fdesc_pathconf },
{ &vnop_advlock_desc, (VOPFUNC)fdesc_advlock },
{ &vnop_bwrite_desc, (VOPFUNC)fdesc_bwrite },
{ &vnop_pagein_desc, (VOPFUNC)err_pagein },
{ &vnop_pageout_desc, (VOPFUNC)err_pageout },
{ &vnop_copyfile_desc, (VOPFUNC)err_copyfile },
{ &vnop_blktooff_desc, (VOPFUNC)fdesc_blktooff },
{ &vnop_blktooff_desc, (VOPFUNC)fdesc_offtoblk },
{ &vnop_blockmap_desc, (VOPFUNC)fdesc_blockmap },
{ (struct vnodeop_desc*)NULL, (VOPFUNC)NULL }
};
struct vnodeopv_desc fdesc_vnodeop_opv_desc =
{ &fdesc_vnodeop_p, fdesc_vnodeop_entries };