/* $FreeBSD: src/sys/nfs4client/nfs4_socket.c,v 1.4 2005/01/07 01:45:50 imp Exp $ */
/* $Id: nfs_socket.c,v 1.12 2003/11/05 14:59:01 rees Exp $ */

/*-
 * copyright (c) 2003
 * the regents of the university of michigan
 * all rights reserved
 * 
 * permission is granted to use, copy, create derivative works and redistribute
 * this software and such derivative works for any purpose, so long as the name
 * of the university of michigan is not used in any advertising or publicity
 * pertaining to the use or distribution of this software without specific,
 * written prior authorization.  if the above copyright notice or any other
 * identification of the university of michigan is included in any copy of any
 * portion of this software, then the disclaimer below must also be included.
 * 
 * this software is provided as is, without representation from the university
 * of michigan as to its fitness for any purpose, and without warranty by the
 * university of michigan of any kind, either express or implied, including
 * without limitation the implied warranties of merchantability and fitness for
 * a particular purpose. the regents of the university of michigan shall not be
 * liable for any damages, including special, indirect, incidental, or
 * consequential damages, with respect to any claim arising out of or in
 * connection with the use of the software, even if it has been or is hereafter
 * advised of the possibility of such damages.
 */

/*-
 * Copyright (c) 1989, 1991, 1993, 1995
 *	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.
 * 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.
 *
 *	@(#)nfs_socket.c	8.5 (Berkeley) 3/30/95
 */

#include <sys/cdefs.h>
__FBSDID("$FreeBSD: src/sys/nfs4client/nfs4_socket.c,v 1.4 2005/01/07 01:45:50 imp Exp $");

/*
 * Socket operations for use by nfs
 */

#include "opt_inet6.h"

#include <sys/param.h>
#include <sys/systm.h>
#include <sys/kernel.h>
#include <sys/lock.h>
#include <sys/malloc.h>
#include <sys/mbuf.h>
#include <sys/mount.h>
#include <sys/mutex.h>
#include <sys/proc.h>
#include <sys/protosw.h>
#include <sys/signalvar.h>
#include <sys/socket.h>
#include <sys/socketvar.h>
#include <sys/syslog.h>
#include <sys/vnode.h>

#include <netinet/in.h>
#include <netinet/tcp.h>

#include <rpc/rpcclnt.h>

#include <nfs/rpcv2.h>
#include <nfs/nfsproto.h>
#include <nfsclient/nfs.h>
#include <nfs4client/nfs4.h>
#include <nfs/xdr_subs.h>
#include <nfsclient/nfsm_subs.h>
#include <nfsclient/nfsmount.h>

#ifdef NFS4_USE_RPCCLNT
#include <rpc/rpcclnt.h>
#include <rpc/rpcm_subs.h>
#endif

#ifdef NFS4_USE_RPCCLNT
static struct rpc_program nfs_program = {
  NFS_PROG, NFS_VER4, "NFSv4"
};
#endif


static struct {
	short	nfserr;
	short	syserr;
} nfs_errtbl[] = {
	{ NFS_OK,		0 },
	{ NFSERR_PERM,		EPERM },
	{ NFSERR_NOENT,		ENOENT },
	{ NFSERR_IO,		EIO },
	{ NFSERR_NXIO,		ENXIO },
	{ NFSERR_ACCES,		EACCES },
	{ NFSERR_EXIST,		EEXIST },
	{ NFSERR_XDEV,		EXDEV },
	{ NFSERR_MLINK,		EMLINK },
	{ NFSERR_NODEV,		ENODEV },
	{ NFSERR_NOTDIR,	ENOTDIR },
	{ NFSERR_ISDIR,		EISDIR },
	{ NFSERR_INVAL,		EINVAL },
	{ NFSERR_FBIG,		EFBIG },
	{ NFSERR_NOSPC,		ENOSPC },
	{ NFSERR_ROFS,		EROFS },
	{ NFSERR_MLINK,		EMLINK },
	{ NFSERR_NAMETOL,	ENAMETOOLONG },
	{ NFSERR_NOTEMPTY,	ENOTEMPTY },
	{ NFSERR_NOTSUPP,	EOPNOTSUPP },
#ifdef EDQUOT
	{ NFSERR_DQUOT,		EDQUOT },
#endif
	{ NFSERR_STALE,		ESTALE },
	{ NFSERR_DENIED,	EAGAIN },
	{ NFSERR_SYMLINK,	ELOOP },
	{ NFSERR_BADXDR,	EBADRPC },
	{ NFSERR_WRONGSEC,	EPERM },
	{ -1,			EIO }
};

static int
nfs4_nfserr_to_syserr(int nfserr)
{
	int i, syserr;
	
	/* XXX : not the optimal algorithm, but will do for now! */
	for (i = 0; nfs_errtbl[i].nfserr != -1; i++) {
		if (nfs_errtbl[i].nfserr == nfserr)
			break;
	}
#ifdef NFS4_MAP_UNKNOWN_ERR
	syserr = nfs_errtbl[i].syserr;
#else
	if (nfs_errtbl[i].nfserr != -1)
		syserr = nfs_errtbl[i].syserr;
	else
		syserr = nfserr;
#endif
	return syserr;
}

int
nfs4_connect(struct nfsmount *nmp)
{
	struct rpcclnt * rpc = &nmp->nm_rpcclnt;
	struct rpc_auth * auth;
	int flag = 0;
	int error;

	/* XXX hack! */
#ifdef __OpenBSD__
	struct proc * td = curproc;
#else
	struct thread * td = curthread;
#endif

	MALLOC(auth, struct rpc_auth *, sizeof(struct rpc_auth), M_TEMP, M_WAITOK);
	auth->auth_type  = RPCAUTH_UNIX;

	/* translate nfs flags -> rpcclnt flags */
	if (nmp->nm_flag & NFSMNT_SOFT)
		flag |= RPCCLNT_SOFT;

	if (nmp->nm_flag & NFSMNT_INT)
		flag |= RPCCLNT_INT;

	if (nmp->nm_flag & NFSMNT_NOCONN)
		flag |= RPCCLNT_NOCONN;

	if (nmp->nm_flag & NFSMNT_DUMBTIMR)
		flag |= RPCCLNT_DUMBTIMR;

	/* rpc->rc_servername = nmp->nm_mountp->mnt_stat.f_mntfromname; */
				  
	error = rpcclnt_setup(rpc, &nfs_program, nmp->nm_nam, nmp->nm_sotype,
			      nmp->nm_soproto, auth, 
			      /* XXX: check nmp->nm_flag to make sure these are set */
			      (nmp->nm_rsize > nmp->nm_readdirsize) ? nmp->nm_rsize : nmp->nm_readdirsize,
			      nmp->nm_wsize, flag);

	/* set deadthresh, timeo, retry */
	rpc->rc_deadthresh = nmp->nm_deadthresh;
	rpc->rc_timeo = nmp->nm_timeo;
	rpc->rc_retry = nmp->nm_retry;


	if (error)
		return error;

	return rpcclnt_connect(rpc, td);
}

/*
 * NFS disconnect. Clean up and unlink.
 */
void
nfs4_disconnect(struct nfsmount *nmp)
{
	rpcclnt_disconnect(&nmp->nm_rpcclnt);
}

void
nfs4_safedisconnect(struct nfsmount *nmp)
{
	rpcclnt_safedisconnect(&nmp->nm_rpcclnt);
}

/*
 * nfs_request - goes something like this
 *	- fill in request struct
 *	- links it into list
 *	- calls nfs_send() for first transmit
 *	- calls nfs_receive() to get reply
 *	- break down rpc header and return with nfs reply pointed to
 *	  by mrep or error
 * nb: always frees up mreq mbuf list
 */
/* XXX overloaded before */
#define	NQ_TRYLATERDEL	15	/* Initial try later delay (sec) */

int
nfs4_request(struct vnode *vp, struct mbuf *mrest, int procnum,
    struct thread *td, struct ucred *cred, struct mbuf **mrp,
    struct mbuf **mdp, caddr_t *dposp)
{
	int error;

	error = nfs4_request_mnt(VFSTONFS(vp->v_mount), mrest, procnum,
				 td, cred, mrp, mdp, dposp);

	/*
	 ** If the File Handle was stale, invalidate the
	 ** lookup cache, just in case.
	 **/
	if (error == ESTALE)
		cache_purge(vp);

	return (error);
}


int
nfs4_request_mnt(struct nfsmount *nmp, struct mbuf *mrest, int procnum,
    struct thread *td, struct ucred *cred, struct mbuf **mrp,
    struct mbuf **mdp, caddr_t *dposp)
{
	int error;
	u_int32_t *tl;
	struct rpcclnt * clnt = &nmp->nm_rpcclnt;
	struct mbuf *md, *mrep;
	caddr_t dpos;
	struct rpc_reply reply;

	if ((error = rpcclnt_request(clnt, mrest, procnum, td, cred,
	    &reply)) != 0) {
		goto out;
	}

	/* XXX: don't free mrest if an error occured, to allow caller to retry*/
	m_freem(mrest);
	mrep = reply.mrep;
	md = reply.result_md;
	dpos = reply.result_dpos;

	tl = nfsm_dissect(u_int32_t *, NFSX_UNSIGNED);
	if (*tl != 0) {
		error = fxdr_unsigned(int, *tl);
#if 0
		if ((nmp->nm_flag & NFSMNT_NFSV3) &&
		    error == NFSERR_TRYLATER) {
			m_freem(mrep);
			error = 0;
			waituntil = time_second + trylater_delay;
			while (time_second < waituntil)
				(void) tsleep(&lbolt, PSOCK, "nqnfstry", 0);
			trylater_delay *= nfs_backoff[trylater_cnt];
			if (trylater_cnt < NFS_NBACKOFF - 1)
				trylater_cnt++;
			goto tryagain;
		}
#endif
		goto out;
	}

	*mrp = mrep;
	*mdp = md;
	*dposp = dpos;
	return (0);
nfsmout:
out:
	m_freem(reply.mrep);
	*mrp = NULL;
	*mdp = NULL;
	return (nfs4_nfserr_to_syserr(error));
}


/*
 * Mark all of an nfs mount's outstanding requests with R_SOFTTERM and
 * wait for all requests to complete. This is used by forced unmounts
 * to terminate any outstanding RPCs.
 */
int
nfs4_nmcancelreqs(nmp)
	struct nfsmount *nmp;
{
	return rpcclnt_cancelreqs(&nmp->nm_rpcclnt);
}

/*
 * Test for a termination condition pending on the process.
 * This is used for NFSMNT_INT mounts.
 */
int
nfs4_sigintr(struct nfsmount *nmp, struct nfsreq *rep, struct thread *td)
{
	if (rep != NULL) {
		printf("nfs_sigintr: attempting to use nfsreq != NULL\n");
		return EINTR;
	}
	return rpcclnt_sigintr(&nmp->nm_rpcclnt, NULL, td);
}
