/*
 * Copyright (c) 1989 The Regents of the University of California.
 * All rights reserved.
 *
 * This code is derived from software contributed to Berkeley by
 * Rick Macklem at The University of Guelph.
 *
 * 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. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *	This product includes software developed by the University of
 *	California, Berkeley and its contributors.
 * 4. 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.
 *
 *	$Id: nfs_syscalls.c,v 1.1 94/10/20 10:57:35 root Exp $
 */

#include "sys/param.h"
#include "sys/stat.h"
#include "privilege.h"
#include "sys/mount.h"
#include "uio.h"
#include "sys/errno.h"
#include "proc.h"
#include "filedesc.h"
#include "malloc.h"
#include "buf.h"
#include "mbuf.h"
#include "socketvar.h"
#include "domain.h"
#include "protosw.h"

#include "in.h"
#include "tcp.h"

#include "vnode.h"
#include "namei.h"
#include "nfs_v2.h"
#include "nfs.h"
#include "nfs_srvcache.h"

#include "prototypes.h"

/* Global defs. */
extern u_long nfs_prog, nfs_vers;
extern int (*nfsrv_procs[NFS_NPROCS])();
extern struct buf nfs_bqueue;
extern int nfs_numasync;
extern struct proc *nfs_iodwant[NFS_MAXASYNCDAEMON];
extern int nfs_tcpnodelay;
struct mbuf *nfs_compress();

#define	TRUE	1
#define	FALSE	0

static int nfs_asyncdaemon[NFS_MAXASYNCDAEMON];
static int compressreply[NFS_NPROCS] = {
	FALSE,
	TRUE,
	TRUE,
	FALSE,
	TRUE,
	TRUE,
	FALSE,
	FALSE,
	TRUE,
	TRUE,
	TRUE,
	TRUE,
	TRUE,
	TRUE,
	TRUE,
	TRUE,
	TRUE,
	TRUE,
};
/*
 * NFS server system calls
 */

/*
 * Nfs server psuedo system call for the nfsd's
 * Never returns unless it fails or gets killed
 */
/* ARGSUSED */
nfssvc(p, uap, retval)
	struct proc *p;
	register struct args {
		int s;
		caddr_t mskval;
		int msklen;
		caddr_t mtchval;
		int mtchlen;
	} *uap;
	int *retval;
{
	register struct mbuf *m;
	register int siz;
	struct file *fp;
	struct mbuf *mreq, *mrep, *nam, *md;
	struct mbuf msk, mtch;
	struct socket *so;
	caddr_t dpos;
	int procid, repstat, error, cacherep, wascomp;
	u_long retxid;

	/*
	 * Must be super user
	 */
	if (error = use_priv(p->p_ucred, PRV_NFS_SVC, p))
		return (error);
	if (error = getsock(p->p_fd, uap->s, &fp))
		return (error);
	so = (struct socket *)fp->f_data;
	if (sosendallatonce(so))
		siz = NFS_MAXPACKET;
	else
		siz = NFS_MAXPACKET + sizeof(u_long);
	if (error = soreserve(so, siz, siz))
		goto bad;
	if (error = sockargs(&nam, uap->mskval, uap->msklen, MT_SONAME))
		goto bad;
	memcpy((caddr_t)&msk, (caddr_t)nam, sizeof (struct mbuf));
	msk.m_data = msk.m_dat;
	m_freem(nam);
	if (error = sockargs(&nam, uap->mtchval, uap->mtchlen, MT_SONAME))
		goto bad;
	memcpy((caddr_t)&mtch, (caddr_t)nam, sizeof (struct mbuf));
	mtch.m_data = mtch.m_dat;
	m_freem(nam);

	/* Copy the cred so others don't see changes */
	p->p_cred = modpcred(p);

	/*
	 * Set protocol specific options { for now TCP only } and
	 * reserve some space. For datagram sockets, this can get called
	 * repeatedly for the same socket, but that isn't harmful.
	 */
	if (so->so_proto->pr_flags & PR_CONNREQUIRED) {
		MGET(m, M_WAIT, MT_SOOPTS);
		*mtod(m, int *) = 1;
		m->m_len = sizeof(int);
		sosetopt(so, SOL_SOCKET, SO_KEEPALIVE, m);
	}
	if (so->so_proto->pr_domain->dom_family == AF_INET &&
	    so->so_proto->pr_protocol == IPPROTO_TCP &&
	    nfs_tcpnodelay) {
		MGET(m, M_WAIT, MT_SOOPTS);
		*mtod(m, int *) = 1;
		m->m_len = sizeof(int);
		sosetopt(so, IPPROTO_TCP, TCP_NODELAY, m);
	}
	so->so_rcv.sb_flags &= ~SB_NOINTR;
	so->so_rcv.sb_timeo = 0;
	so->so_snd.sb_flags &= ~SB_NOINTR;
	so->so_snd.sb_timeo = 0;

	/*
	 * Just loop around doin our stuff until SIGKILL
	 */
	for (;;) {
		if (error = nfs_getreq(so, nfs_prog, nfs_vers, NFS_NPROCS-1,
		   &nam, &mrep, &md, &dpos, &retxid, &procid, p->p_ucred,
		   &msk, &mtch, &wascomp)) {
			if (nam)
				m_freem(nam);
			if (error == EPIPE || error == EINTR ||
			    error == ERESTART) {
				error = 0;
				goto bad;
			}
			so->so_error = 0;
			continue;
		}

		if (nam)
			cacherep = nfsrv_getcache(nam, retxid, procid, &mreq);
		else
			cacherep = RC_DOIT;
		switch (cacherep) {
		case RC_DOIT:
			if (error = (*(nfsrv_procs[procid]))(mrep, md, dpos,
				p->p_ucred, retxid, &mreq, &repstat, p)) {
				nfsstats.srv_errs++;
				if (nam) {
					nfsrv_updatecache(nam, retxid, procid,
						FALSE, repstat, mreq);
					m_freem(nam);
				}
				break;
			}
			nfsstats.srvrpccnt[procid]++;
			if (nam)
				nfsrv_updatecache(nam, retxid, procid, TRUE,
					repstat, mreq);
			mrep = (struct mbuf *)0;
		case RC_REPLY:
			m = mreq;
			siz = 0;
			while (m) {
				siz += m->m_len;
				m = m->m_next;
			}
			if (siz <= 0 || siz > NFS_MAXPACKET) {
				printf("mbuf siz=%d\n",siz);
				panic("Bad nfs svc reply");
			}
			mreq->m_pkthdr.len = siz;
			mreq->m_pkthdr.rcvif = (struct ifnet *)0;
			if (wascomp && compressreply[procid]) {
				mreq = nfs_compress(mreq);
				siz = mreq->m_pkthdr.len;
			}
			/*
			 * For non-atomic protocols, prepend a Sun RPC
			 * Record Mark.
			 */
			if (!sosendallatonce(so)) {
				M_PREPEND(mreq, sizeof(u_long), M_WAIT);
				*mtod(mreq, u_long *) = htonl(0x80000000 | siz);
			}
			error = nfs_send(so, nam, mreq, (struct nfsreq *)0);
			if (nam)
				m_freem(nam);
			if (mrep)
				m_freem(mrep);
			if (error) {
				if (error == EPIPE || error == EINTR ||
				    error == ERESTART)
					goto bad;
				so->so_error = 0;
			}
			break;
		case RC_DROPIT:
			m_freem(mrep);
			m_freem(nam);
			break;
		};
	}
bad:
	return (error);
}

/*
 * Nfs pseudo system call for asynchronous i/o daemons.
 * These babies just pretend to be disk interrupt service routines
 * for client nfs. They are mainly here for read ahead/write behind.
 * Never returns unless it fails or gets killed
 */
/* ARGSUSED */
async_daemon(p, uap, retval)
	struct proc *p;
	struct args *uap;
	int *retval;
{
	register struct buf *bp, *dp;
	register int i, myiod;
	int error;

	/*
	 * Must be super user
	 */
	if (error = use_priv(p->p_ucred, PRV_NFS_ASYNC_DAEMON, p))
		return (error);
	/*
	 * Assign my position or return error if too many already running
	 */
	myiod = -1;
	for (i = 0; i < NFS_MAXASYNCDAEMON; i++)
		if (nfs_asyncdaemon[i] == 0) {
			nfs_asyncdaemon[i]++;
			myiod = i;
			break;
		}
	if (myiod == -1)
		return (EBUSY);
	nfs_numasync++;
	dp = &nfs_bqueue;
	/*
	 * Just loop around doin our stuff until SIGKILL
	 */
	for (;;) {
		while (dp->b_actf == NULL && error == 0) {
			nfs_iodwant[myiod] = p;
			error = tsleep((caddr_t)&nfs_iodwant[myiod],
				PWAIT | PCATCH, "nfsidl", 0);
			nfs_iodwant[myiod] = (struct proc *)0;
		}
		while (dp->b_actf != NULL) {
			/* Take one off the end of the list */
			bp = dp->b_actl;
			if (bp->b_actl == dp) {
				dp->b_actf = dp->b_actl = (struct buf *)0;
			} else {
				dp->b_actl = bp->b_actl;
				bp->b_actl->b_actf = dp;
			}
			(void) nfs_doio(bp);
		}
		if (error) {
			nfs_asyncdaemon[myiod] = 0;
			nfs_numasync--;
			return (error);
		}
	}
}
