/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */
/*
 * Copyright 2004 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

/*	Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T	*/
/*	  All Rights Reserved  	*/


#pragma ident	"%Z%%M%	%I%	%E% SMI"

#include <sys/types.h>
#include <sys/sysmacros.h>
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/errno.h>
#include <sys/vnode.h>
#include <sys/file.h>
#include <sys/proc.h>
#include <sys/stropts.h>
#include <sys/stream.h>
#include <sys/strsubr.h>
#include <sys/fs/fifonode.h>
#include <sys/socket.h>
#include <sys/socketvar.h>
#include <sys/debug.h>

/*
 * STREAMS system calls.
 */

int getmsg(int fdes, struct strbuf *ctl, struct strbuf *data, int *flagsp);
int putmsg(int fdes, struct strbuf *ctl, struct strbuf *data, int flags);
int getpmsg(int fdes, struct strbuf *ctl, struct strbuf *data, int *prip,
    int *flagsp);
int putpmsg(int fdes, struct strbuf *ctl, struct strbuf *data, int pri,
    int flags);

static int msgio(int fdes, struct strbuf *ctl, struct strbuf *data, int *rval,
    int mode, unsigned char *prip, int *flagsp);

int
getmsg(int fdes, struct strbuf *ctl, struct strbuf *data, int *flagsp)
{
	int error;
	int localflags;
	int realflags = 0;
	unsigned char pri = 0;
	int rv = 0;

	/*
	 * Convert between old flags (localflags) and new flags (realflags).
	 */
	if (copyin(flagsp, &localflags, sizeof (*flagsp)))
		return (set_errno(EFAULT));
	switch (localflags) {
	case 0:
		realflags = MSG_ANY;
		break;

	case RS_HIPRI:
		realflags = MSG_HIPRI;
		break;

	default:
		return (set_errno(EINVAL));
	}

	if ((error = msgio(fdes, ctl, data, &rv, FREAD, &pri,
	    &realflags)) == 0) {
		/*
		 * massage realflags based on localflags.
		 */
		if (realflags == MSG_HIPRI)
			localflags = RS_HIPRI;
		else
			localflags = 0;
		if (copyout(&localflags, flagsp, sizeof (*flagsp)))
			error = EFAULT;
	}
	if (error != 0)
		return (set_errno(error));
	return (rv);
}

int
putmsg(int fdes, struct strbuf *ctl, struct strbuf *data, int flags)
{
	unsigned char pri = 0;
	int realflags;
	int error;
	int rv = 0;

	switch (flags) {
	case RS_HIPRI:
		realflags = MSG_HIPRI;
		break;
	case (RS_HIPRI|MSG_XPG4):
		realflags = MSG_HIPRI|MSG_XPG4;
		break;
	case MSG_XPG4:
		realflags = MSG_BAND|MSG_XPG4;
		break;
	case 0:
		realflags = MSG_BAND;
		break;

	default:
		return (set_errno(EINVAL));
	}
	error = msgio(fdes, ctl, data, &rv, FWRITE, &pri, &realflags);
	if (error != 0)
		return (set_errno(error));
	return (rv);
}


int
getpmsg(int fdes, struct strbuf *ctl, struct strbuf *data, int *prip,
    int *flagsp)
{
	int error;
	int flags;
	int intpri;
	unsigned char pri;
	int rv = 0;

	if (copyin(flagsp, &flags, sizeof (flags)))
		return (set_errno(EFAULT));
	if (copyin(prip, &intpri, sizeof (intpri)))
		return (set_errno(EFAULT));
	if ((intpri > 255) || (intpri < 0))
		return (set_errno(EINVAL));
	pri = (unsigned char)intpri;
	error = msgio(fdes, ctl, data, &rv, FREAD, &pri, &flags);
	if (error != 0)
		return (set_errno(error));
	if (copyout(&flags, flagsp, sizeof (flags)))
		return (set_errno(EFAULT));
	intpri = (int)pri;
	if (copyout(&intpri, prip, sizeof (intpri)))
		return (set_errno(EFAULT));
	return (rv);
}

int
putpmsg(int fdes, struct strbuf *ctl, struct strbuf *data, int intpri,
    int flags)
{
	unsigned char pri;
	int rv = 0;
	int error;

	if ((intpri > 255) || (intpri < 0))
		return (set_errno(EINVAL));
	pri = (unsigned char)intpri;
	error = msgio(fdes, ctl, data, &rv, FWRITE, &pri, &flags);
	if (error != 0)
		return (set_errno(error));
	return (rv);
}

/*
 * Common code for getmsg and putmsg calls: check permissions,
 * copy in args, do preliminary setup, and switch to
 * appropriate stream routine.
 */
static int
msgio(int fdes, struct strbuf *ctl, struct strbuf *data, int *rval,
    int mode, unsigned char *prip, int *flagsp)
{
	file_t *fp;
	vnode_t *vp;
	struct strbuf msgctl, msgdata;
	int error;
	int flag;
	klwp_t *lwp = ttolwp(curthread);
	rval_t rv;

	if ((fp = getf(fdes)) == NULL)
		return (EBADF);
	if ((fp->f_flag & mode) == 0) {
		releasef(fdes);
		return (EBADF);
	}
	vp = fp->f_vnode;
	if (vp->v_type == VFIFO) {
		if (vp->v_stream) {
			/*
			 * must use sd_vnode, could be named pipe
			 */
			(void) fifo_vfastoff(vp->v_stream->sd_vnode);
		} else {
			releasef(fdes);
			return (ENOSTR);
		}
	} else if ((vp->v_type != VCHR && vp->v_type != VSOCK) ||
		    vp->v_stream == NULL) {
		releasef(fdes);
		return (ENOSTR);
	}
	if ((ctl != NULL) &&
	    copyin(ctl, &msgctl, sizeof (struct strbuf))) {
		releasef(fdes);
		return (EFAULT);
	}
	if ((data != NULL) &&
	    copyin(data, &msgdata, sizeof (struct strbuf))) {
		releasef(fdes);
		return (EFAULT);
	}

	if (mode == FREAD) {
		if (ctl == NULL)
			msgctl.maxlen = -1;
		if (data == NULL)
			msgdata.maxlen = -1;
		flag = fp->f_flag;
		rv.r_val1 = 0;
		if (vp->v_type == VSOCK) {
			error = sock_getmsg(vp, &msgctl, &msgdata, prip,
			    flagsp, flag, &rv);
		} else {
			error = strgetmsg(vp, &msgctl, &msgdata, prip,
			    flagsp, flag, &rv);
		}
		*rval = rv.r_val1;
		if (error != 0) {
			releasef(fdes);
			return (error);
		}
		if (lwp != NULL)
			lwp->lwp_ru.msgrcv++;
		if (((ctl != NULL) &&
		    copyout(&msgctl, ctl, sizeof (struct strbuf))) ||
		    ((data != NULL) &&
		    copyout(&msgdata, data, sizeof (struct strbuf)))) {
			releasef(fdes);
			return (EFAULT);
		}
		releasef(fdes);
		return (0);
	}

	/*
	 * FWRITE case
	 */
	if (ctl == NULL)
		msgctl.len = -1;
	if (data == NULL)
		msgdata.len = -1;
	flag = fp->f_flag;
	if (vp->v_type == VSOCK) {
		error = sock_putmsg(vp, &msgctl, &msgdata, *prip, *flagsp,
		    flag);
	} else {
		error = strputmsg(vp, &msgctl, &msgdata, *prip, *flagsp, flag);
	}
	releasef(fdes);
	if (error == 0 && lwp != NULL)
		lwp->lwp_ru.msgsnd++;
	return (error);
}


#if defined(_LP64) && defined(_SYSCALL32)

static int msgio32(int fdes, struct strbuf32 *ctl, struct strbuf32 *data,
    int *rval, int mode, unsigned char *prip, int *flagsp);

int
getmsg32(int fdes, struct strbuf32 *ctl, struct strbuf32 *data, int32_t *flagsp)
{
	int error;
	int32_t localflags;
	int realflags = 0;
	unsigned char pri = 0;
	int rv = 0;

	/*
	 * Convert between old flags (localflags) and new flags (realflags).
	 */
	if (copyin(flagsp, &localflags, sizeof (*flagsp)))
		return (set_errno(EFAULT));
	switch (localflags) {
	case 0:
		realflags = MSG_ANY;
		break;

	case RS_HIPRI:
		realflags = MSG_HIPRI;
		break;

	default:
		return (set_errno(EINVAL));
	}

	if ((error = msgio32(fdes, ctl, data, &rv, FREAD, &pri,
	    &realflags)) == 0) {
		/*
		 * massage realflags based on localflags.
		 */
		if (realflags == MSG_HIPRI)
			localflags = RS_HIPRI;
		else
			localflags = 0;
		if (copyout(&localflags, flagsp, sizeof (*flagsp)))
			error = EFAULT;
	}
	if (error != 0)
		return (set_errno(error));
	return (rv);
}

int
putmsg32(int fdes, struct strbuf32 *ctl, struct strbuf32 *data, int32_t flags)
{
	unsigned char pri = 0;
	int realflags;
	int error;
	int rv = 0;

	switch (flags) {
	case RS_HIPRI:
		realflags = MSG_HIPRI;
		break;
	case (RS_HIPRI|MSG_XPG4):
		realflags = MSG_HIPRI|MSG_XPG4;
		break;
	case MSG_XPG4:
		realflags = MSG_BAND|MSG_XPG4;
		break;
	case 0:
		realflags = MSG_BAND;
		break;

	default:
		return (set_errno(EINVAL));
	}
	error = msgio32(fdes, ctl, data, &rv, FWRITE, &pri, &realflags);
	if (error != 0)
		return (set_errno(error));
	return (rv);
}


int
getpmsg32(int fdes, struct strbuf32 *ctl, struct strbuf32 *data, int32_t *prip,
    int32_t *flagsp)
{
	int error;
	int32_t flags;
	int32_t intpri;
	unsigned char pri;
	int rv = 0;

	if (copyin(flagsp, &flags, sizeof (*flagsp)))
		return (set_errno(EFAULT));
	if (copyin(prip, &intpri, sizeof (intpri)))
		return (set_errno(EFAULT));
	if ((intpri > 255) || (intpri < 0))
		return (set_errno(EINVAL));
	pri = (unsigned char)intpri;
	error = msgio32(fdes, ctl, data, &rv, FREAD, &pri, &flags);
	if (error != 0)
		return (set_errno(error));
	if (copyout(&flags, flagsp, sizeof (flags)))
		return (set_errno(EFAULT));
	intpri = (int)pri;
	if (copyout(&intpri, prip, sizeof (intpri)))
		return (set_errno(EFAULT));
	return (rv);
}

int
putpmsg32(int fdes, struct strbuf32 *ctl, struct strbuf32 *data, int32_t intpri,
    int32_t flags)
{
	unsigned char pri;
	int rv = 0;
	int error;

	if ((intpri > 255) || (intpri < 0))
		return (set_errno(EINVAL));
	pri = (unsigned char)intpri;
	error = msgio32(fdes, ctl, data, &rv, FWRITE, &pri, &flags);
	if (error != 0)
		return (set_errno(error));
	return (rv);
}

/*
 * Common code for getmsg and putmsg calls: check permissions,
 * copy in args, do preliminary setup, and switch to
 * appropriate stream routine.
 */
static int
msgio32(int fdes, struct strbuf32 *ctl, struct strbuf32 *data, int *rval,
    int mode, unsigned char *prip, int *flagsp)
{
	file_t *fp;
	vnode_t *vp;
	struct strbuf32 msgctl32, msgdata32;
	struct strbuf msgctl, msgdata;
	int error;
	int flag;
	klwp_t *lwp = ttolwp(curthread);
	rval_t rv;

	if ((fp = getf(fdes)) == NULL)
		return (EBADF);
	if ((fp->f_flag & mode) == 0) {
		releasef(fdes);
		return (EBADF);
	}
	vp = fp->f_vnode;
	if (vp->v_type == VFIFO) {
		if (vp->v_stream) {
			/*
			 * must use sd_vnode, could be named pipe
			 */
			(void) fifo_vfastoff(vp->v_stream->sd_vnode);
		} else {
			releasef(fdes);
			return (ENOSTR);
		}
	} else if ((vp->v_type != VCHR && vp->v_type != VSOCK) ||
		    vp->v_stream == NULL) {
		releasef(fdes);
		return (ENOSTR);
	}
	if (ctl != NULL) {
		if (copyin(ctl, &msgctl32, sizeof (msgctl32))) {
			releasef(fdes);
			return (EFAULT);
		}
		msgctl.len = msgctl32.len;
		msgctl.maxlen = msgctl32.maxlen;
		msgctl.buf = (caddr_t)(uintptr_t)msgctl32.buf;
	}
	if (data != NULL) {
		if (copyin(data, &msgdata32, sizeof (msgdata32))) {
			releasef(fdes);
			return (EFAULT);
		}
		msgdata.len = msgdata32.len;
		msgdata.maxlen = msgdata32.maxlen;
		msgdata.buf = (caddr_t)(uintptr_t)msgdata32.buf;
	}

	if (mode == FREAD) {
		if (ctl == NULL)
			msgctl.maxlen = -1;
		if (data == NULL)
			msgdata.maxlen = -1;
		flag = fp->f_flag;
		rv.r_val1 = 0;
		if (vp->v_type == VSOCK) {
			error = sock_getmsg(vp, &msgctl, &msgdata, prip,
			    flagsp, flag, &rv);
		} else {
			error = strgetmsg(vp, &msgctl, &msgdata, prip,
			    flagsp, flag, &rv);
		}
		*rval = rv.r_val1;
		if (error != 0) {
			releasef(fdes);
			return (error);
		}
		if (lwp != NULL)
			lwp->lwp_ru.msgrcv++;
		if (ctl != NULL) {
			/* XX64 - range check */
			msgctl32.len = msgctl.len;
			msgctl32.maxlen = msgctl.maxlen;
			msgctl32.buf = (caddr32_t)(uintptr_t)msgctl.buf;
			if (copyout(&msgctl32, ctl, sizeof (msgctl32))) {
				releasef(fdes);
				return (EFAULT);
			}
		}
		if (data != NULL) {
			/* XX64 - range check */
			msgdata32.len = msgdata.len;
			msgdata32.maxlen = msgdata.maxlen;
			msgdata32.buf = (caddr32_t)(uintptr_t)msgdata.buf;
			if (copyout(&msgdata32, data, sizeof (msgdata32))) {
				releasef(fdes);
				return (EFAULT);
			}
		}
		releasef(fdes);
		return (0);
	}

	/*
	 * FWRITE case
	 */
	if (ctl == NULL)
		msgctl.len = -1;
	if (data == NULL)
		msgdata.len = -1;
	flag = fp->f_flag;
	if (vp->v_type == VSOCK) {
		error = sock_putmsg(vp, &msgctl, &msgdata, *prip, *flagsp,
		    flag);
	} else {
		error = strputmsg(vp, &msgctl, &msgdata, *prip, *flagsp, flag);
	}
	releasef(fdes);
	if (error == 0 && lwp != NULL)
		lwp->lwp_ru.msgsnd++;
	return (error);
}

#endif /* _LP64 && _SYSCALL32 */