#include <sys/param.h>
#include <sys/proc.h>
#include <sys/time.h>
#include <sys/namei.h>
#include <sys/vnode_internal.h>
#include <sys/socket.h>
#include <sys/socketvar.h>
#include <sys/stat.h>
#include <sys/systm.h>
#include <sys/ioctl.h>
#include <sys/file_internal.h>
#include <sys/errno.h>
#include <sys/malloc.h>
#include <miscfs/fifofs/fifo.h>
#include <vfs/vfs_support.h>
#define VOPFUNC int (*)(void *)
int(**fifo_vnodeop_p)(void *);
const struct vnodeopv_entry_desc fifo_vnodeop_entries[] = {
{ .opve_op = &vnop_default_desc, .opve_impl = (VOPFUNC)vn_default_error },
{ .opve_op = &vnop_lookup_desc, .opve_impl = (VOPFUNC)fifo_lookup },
{ .opve_op = &vnop_create_desc, .opve_impl = (VOPFUNC)err_create },
{ .opve_op = &vnop_mknod_desc, .opve_impl = (VOPFUNC)err_mknod },
{ .opve_op = &vnop_open_desc, .opve_impl = (VOPFUNC)fifo_open },
{ .opve_op = &vnop_close_desc, .opve_impl = (VOPFUNC)fifo_close },
{ .opve_op = &vnop_access_desc, .opve_impl = (VOPFUNC)fifo_access },
{ .opve_op = &vnop_getattr_desc, .opve_impl = (VOPFUNC)fifo_getattr },
{ .opve_op = &vnop_setattr_desc, .opve_impl = (VOPFUNC)fifo_setattr },
{ .opve_op = &vnop_read_desc, .opve_impl = (VOPFUNC)fifo_read },
{ .opve_op = &vnop_write_desc, .opve_impl = (VOPFUNC)fifo_write },
{ .opve_op = &vnop_ioctl_desc, .opve_impl = (VOPFUNC)fifo_ioctl },
{ .opve_op = &vnop_select_desc, .opve_impl = (VOPFUNC)fifo_select },
{ .opve_op = &vnop_revoke_desc, .opve_impl = (VOPFUNC)fifo_revoke },
{ .opve_op = &vnop_mmap_desc, .opve_impl = (VOPFUNC)err_mmap },
{ .opve_op = &vnop_fsync_desc, .opve_impl = (VOPFUNC)fifo_fsync },
{ .opve_op = &vnop_remove_desc, .opve_impl = (VOPFUNC)err_remove },
{ .opve_op = &vnop_link_desc, .opve_impl = (VOPFUNC)err_link },
{ .opve_op = &vnop_rename_desc, .opve_impl = (VOPFUNC)err_rename },
{ .opve_op = &vnop_mkdir_desc, .opve_impl = (VOPFUNC)err_mkdir },
{ .opve_op = &vnop_rmdir_desc, .opve_impl = (VOPFUNC)err_rmdir },
{ .opve_op = &vnop_symlink_desc, .opve_impl = (VOPFUNC)err_symlink },
{ .opve_op = &vnop_readdir_desc, .opve_impl = (VOPFUNC)err_readdir },
{ .opve_op = &vnop_readlink_desc, .opve_impl = (VOPFUNC)err_readlink },
{ .opve_op = &vnop_inactive_desc, .opve_impl = (VOPFUNC)fifo_inactive },
{ .opve_op = &vnop_reclaim_desc, .opve_impl = (VOPFUNC)fifo_reclaim },
{ .opve_op = &vnop_strategy_desc, .opve_impl = (VOPFUNC)err_strategy },
{ .opve_op = &vnop_pathconf_desc, .opve_impl = (VOPFUNC)fifo_pathconf },
{ .opve_op = &vnop_advlock_desc, .opve_impl = (VOPFUNC)fifo_advlock },
{ .opve_op = &vnop_bwrite_desc, .opve_impl = (VOPFUNC)fifo_bwrite },
{ .opve_op = &vnop_pagein_desc, .opve_impl = (VOPFUNC)err_pagein },
{ .opve_op = &vnop_pageout_desc, .opve_impl = (VOPFUNC)err_pageout },
{ .opve_op = &vnop_copyfile_desc, .opve_impl = (VOPFUNC)err_copyfile },
{ .opve_op = &vnop_blktooff_desc, .opve_impl = (VOPFUNC)err_blktooff },
{ .opve_op = &vnop_offtoblk_desc, .opve_impl = (VOPFUNC)err_offtoblk },
{ .opve_op = &vnop_blockmap_desc, .opve_impl = (VOPFUNC)err_blockmap },
{ .opve_op = (struct vnodeop_desc*)NULL, .opve_impl = (int (*)(void *))NULL }
};
const struct vnodeopv_desc fifo_vnodeop_opv_desc =
{ .opv_desc_vector_p = &fifo_vnodeop_p, .opv_desc_ops = fifo_vnodeop_entries };
int
fifo_lookup(struct vnop_lookup_args *ap)
{
*ap->a_vpp = NULL;
return ENOTDIR;
}
int
fifo_open(struct vnop_open_args *ap)
{
struct vnode *vp = ap->a_vp;
struct fifoinfo *fip;
struct socket *rso, *wso;
int error;
vnode_lock(vp);
retry:
fip = vp->v_fifoinfo;
if (fip == (struct fifoinfo *)0) {
panic("fifo_open with no fifoinfo");
}
if ((fip->fi_flags & FIFO_CREATED) == 0) {
if (fip->fi_flags & FIFO_INCREATE) {
fip->fi_flags |= FIFO_CREATEWAIT;
error = msleep(&fip->fi_flags, &vp->v_lock, PRIBIO | PCATCH, "fifocreatewait", NULL);
if (error) {
vnode_unlock(vp);
return error;
}
goto retry;
} else {
fip->fi_flags |= FIFO_INCREATE;
vnode_unlock(vp);
if ((error = socreate(AF_LOCAL, &rso, SOCK_STREAM, 0))) {
goto bad1;
}
if ((error = socreate(AF_LOCAL, &wso, SOCK_STREAM, 0))) {
(void)soclose(rso);
goto bad1;
}
if ((error = soconnect2(wso, rso))) {
(void)soclose(wso);
(void)soclose(rso);
goto bad1;
}
fip->fi_readers = fip->fi_writers = 0;
socket_lock(wso, 1);
wso->so_state |= SS_CANTRCVMORE;
wso->so_snd.sb_lowat = PIPE_BUF;
socket_unlock(wso, 1);
socket_lock(rso, 1);
rso->so_state |= SS_CANTSENDMORE;
socket_unlock(rso, 1);
vnode_lock(vp);
fip->fi_readsock = rso;
fip->fi_writesock = wso;
fip->fi_flags |= FIFO_CREATED;
fip->fi_flags &= ~FIFO_INCREATE;
if ((fip->fi_flags & FIFO_CREATEWAIT)) {
fip->fi_flags &= ~FIFO_CREATEWAIT;
wakeup(&fip->fi_flags);
}
}
}
if (ap->a_mode & FREAD) {
fip->fi_readers++;
if (fip->fi_readers == 1) {
socket_lock(fip->fi_writesock, 1);
fip->fi_writesock->so_state &= ~SS_CANTSENDMORE;
socket_unlock(fip->fi_writesock, 1);
if (fip->fi_writers > 0) {
wakeup((caddr_t)&fip->fi_writers);
}
}
}
if (ap->a_mode & FWRITE) {
fip->fi_writers++;
if (fip->fi_writers == 1) {
socket_lock(fip->fi_readsock, 1);
fip->fi_readsock->so_state &= ~SS_CANTRCVMORE;
socket_unlock(fip->fi_readsock, 1);
if (fip->fi_readers > 0) {
wakeup((caddr_t)&fip->fi_readers);
}
}
}
if ((ap->a_mode & FREAD) && (ap->a_mode & O_NONBLOCK) == 0) {
if (fip->fi_writers == 0) {
error = msleep((caddr_t)&fip->fi_readers, &vp->v_lock,
PCATCH | PSOCK, "fifoor", NULL);
if (error) {
goto bad;
}
if (fip->fi_readers == 1) {
if (fip->fi_writers > 0) {
wakeup((caddr_t)&fip->fi_writers);
}
}
}
}
if (ap->a_mode & FWRITE) {
if (ap->a_mode & O_NONBLOCK) {
if (fip->fi_readers == 0) {
error = ENXIO;
goto bad;
}
} else {
if (fip->fi_readers == 0) {
error = msleep((caddr_t)&fip->fi_writers, &vp->v_lock,
PCATCH | PSOCK, "fifoow", NULL);
if (error) {
goto bad;
}
if (fip->fi_writers == 1) {
if (fip->fi_readers > 0) {
wakeup((caddr_t)&fip->fi_readers);
}
}
}
}
}
vnode_unlock(vp);
return 0;
bad:
fifo_close_internal(vp, ap->a_mode, ap->a_context, 1);
vnode_unlock(vp);
return error;
bad1:
vnode_lock(vp);
fip->fi_flags &= ~FIFO_INCREATE;
if ((fip->fi_flags & FIFO_CREATEWAIT)) {
fip->fi_flags &= ~FIFO_CREATEWAIT;
wakeup(&fip->fi_flags);
}
vnode_unlock(vp);
return error;
}
int
fifo_read(struct vnop_read_args *ap)
{
struct uio *uio = ap->a_uio;
struct socket *rso = ap->a_vp->v_fifoinfo->fi_readsock;
user_ssize_t startresid;
int error;
int rflags;
#if DIAGNOSTIC
if (uio->uio_rw != UIO_READ) {
panic("fifo_read mode");
}
#endif
if (uio_resid(uio) == 0) {
return 0;
}
rflags = (ap->a_ioflag & IO_NDELAY) ? MSG_NBIO : 0;
startresid = uio_resid(uio);
error = 0;
if (ap->a_vp->v_fifoinfo->fi_writers < 1) {
socket_lock(rso, 1);
error = (rso->so_rcv.sb_cc == 0) ? EWOULDBLOCK : 0;
socket_unlock(rso, 1);
}
if (error != EWOULDBLOCK) {
error = soreceive(rso, (struct sockaddr **)0, uio, (struct mbuf **)0,
(struct mbuf **)0, &rflags);
if (error == 0) {
lock_vnode_and_post(ap->a_vp, 0);
}
} else {
error = 0;
}
if (uio_resid(uio) == startresid) {
socket_lock(rso, 1);
rso->so_state &= ~SS_CANTRCVMORE;
socket_unlock(rso, 1);
}
return error;
}
int
fifo_write(struct vnop_write_args *ap)
{
struct socket *wso = ap->a_vp->v_fifoinfo->fi_writesock;
int error;
#if DIAGNOSTIC
if (ap->a_uio->uio_rw != UIO_WRITE) {
panic("fifo_write mode");
}
#endif
error = sosend(wso, (struct sockaddr *)0, ap->a_uio, NULL,
(struct mbuf *)0, (ap->a_ioflag & IO_NDELAY) ? MSG_NBIO : 0);
if (error == 0) {
lock_vnode_and_post(ap->a_vp, 0);
}
return error;
}
int
fifo_ioctl(struct vnop_ioctl_args *ap)
{
struct fileproc filetmp;
struct fileglob filefg;
int error;
if (ap->a_command == FIONBIO) {
return 0;
}
bzero(&filetmp, sizeof(struct fileproc));
filetmp.f_fglob = &filefg;
if (ap->a_fflag & FREAD) {
filetmp.f_fglob->fg_data = (caddr_t)ap->a_vp->v_fifoinfo->fi_readsock;
error = soo_ioctl(&filetmp, ap->a_command, ap->a_data, ap->a_context);
if (error) {
return error;
}
}
if (ap->a_fflag & FWRITE) {
filetmp.f_fglob->fg_data = (caddr_t)ap->a_vp->v_fifoinfo->fi_writesock;
error = soo_ioctl(&filetmp, ap->a_command, ap->a_data, ap->a_context);
if (error) {
return error;
}
}
return 0;
}
int
fifo_select(struct vnop_select_args *ap)
{
struct fileproc filetmp;
struct fileglob filefg;
int ready;
bzero(&filetmp, sizeof(struct fileproc));
filetmp.f_fglob = &filefg;
if (ap->a_which & FREAD) {
filetmp.f_fglob->fg_data = (caddr_t)ap->a_vp->v_fifoinfo->fi_readsock;
ready = soo_select(&filetmp, ap->a_which, ap->a_wql, ap->a_context);
if (ready) {
return ready;
}
}
if (ap->a_which & FWRITE) {
filetmp.f_fglob->fg_data = (caddr_t)ap->a_vp->v_fifoinfo->fi_writesock;
ready = soo_select(&filetmp, ap->a_which, ap->a_wql, ap->a_context);
if (ready) {
return ready;
}
}
return 0;
}
int
fifo_inactive(__unused struct vnop_inactive_args *ap)
{
return 0;
}
int
fifo_close(struct vnop_close_args *ap)
{
return fifo_close_internal(ap->a_vp, ap->a_fflag, ap->a_context, 0);
}
int
fifo_close_internal(vnode_t vp, int fflag, __unused vfs_context_t context, int locked)
{
struct fifoinfo *fip = vp->v_fifoinfo;
int error1, error2;
struct socket *rso;
struct socket *wso;
if (!locked) {
vnode_lock(vp);
}
if ((fip->fi_flags & FIFO_CREATED) == 0) {
if (!locked) {
vnode_unlock(vp);
}
return 0;
}
if (fflag & FREAD) {
fip->fi_readers--;
if (fip->fi_readers == 0) {
socket_lock(fip->fi_writesock, 1);
socantsendmore(fip->fi_writesock);
socket_unlock(fip->fi_writesock, 1);
}
}
if (fflag & FWRITE) {
fip->fi_writers--;
if (fip->fi_writers == 0) {
socket_lock(fip->fi_readsock, 1);
socantrcvmore(fip->fi_readsock);
socket_unlock(fip->fi_readsock, 1);
}
}
#if 0
if (vnode_isinuse_locked(vp, 0, 1)) {
if (!locked) {
vnode_unlock(vp);
}
return 0;
}
#endif
if (fip->fi_writers || fip->fi_readers) {
if (!locked) {
vnode_unlock(vp);
}
return 0;
}
wso = fip->fi_writesock;
rso = fip->fi_readsock;
fip->fi_readsock = NULL;
fip->fi_writesock = NULL;
fip->fi_flags &= ~FIFO_CREATED;
if (!locked) {
vnode_unlock(vp);
}
error1 = soclose(rso);
error2 = soclose(wso);
if (error1) {
return error1;
}
return error2;
}
void
fifo_printinfo(struct vnode *vp)
{
struct fifoinfo *fip = vp->v_fifoinfo;
printf(", fifo with %ld readers and %ld writers",
fip->fi_readers, fip->fi_writers);
}
int
fifo_pathconf(struct vnop_pathconf_args *ap)
{
switch (ap->a_name) {
case _PC_LINK_MAX:
*ap->a_retval = LINK_MAX;
return 0;
case _PC_PIPE_BUF:
*ap->a_retval = PIPE_BUF;
return 0;
case _PC_CHOWN_RESTRICTED:
*ap->a_retval = 200112;
return 0;
default:
return EINVAL;
}
}
int
fifo_ebadf(__unused void *dummy)
{
return EBADF;
}
int
fifo_advlock(__unused struct vnop_advlock_args *ap)
{
return ENOTSUP;
}
int
fifo_freespace(struct vnode *vp, long *count)
{
struct socket *rsock;
rsock = vp->v_fifoinfo->fi_readsock;
socket_lock(rsock, 1);
*count = sbspace(&rsock->so_rcv);
socket_unlock(rsock, 1);
return 0;
}
int
fifo_charcount(struct vnode *vp, int *count)
{
int mcount;
int err = sock_ioctl(vp->v_fifoinfo->fi_readsock, FIONREAD, (void*)&mcount);
if (err == 0) {
*count = mcount;
}
return err;
}