/*
 *  linux/fs/nfs/sock.c
 *
 *  Copyright (C) 1992, 1993  Rick Sladkey
 *
 *  low-level nfs remote procedure call interface
 *
 * FIXES
 *
 * 2/7/94 James Bottomley and Jon Peatfield DAMTP, Cambridge University
 *
 * An xid mismatch no longer causes the request to be trashed.
 *
 * Peter Eriksson - incorrect XID used to confuse Linux
 * Florian La Roche - use the correct max size, if reading a packet and
 *                    also verify, if the whole packet has been read...
 *                    more checks should be done in proc.c...
 *
 */

#ifdef MODULE
#include <linux/module.h>
#endif

#include <linux/sched.h>
#include <linux/nfs_fs.h>
#include <linux/errno.h>
#include <linux/socket.h>
#include <linux/fcntl.h>
#include <asm/segment.h>
#include <linux/in.h>
#include <linux/net.h>
#include <linux/mm.h>

/* JEJB/JSP 2/7/94
 * this must match the value of NFS_SLACK_SPACE in linux/fs/nfs/proc.c 
 * ***FIXME*** should probably put this in nfs_fs.h */
#define NFS_SLACK_SPACE 1024


extern struct socket *socki_lookup(struct inode *inode);

#define _S(nr) (1<<((nr)-1))

/*
 * We violate some modularity principles here by poking around
 * in some socket internals.  Besides having to call socket
 * functions from kernel-space instead of user space, the socket
 * interface does not lend itself well to being cleanly called
 * without a file descriptor.  Since the nfs calls can run on
 * behalf of any process, the superblock maintains a file pointer
 * to the server socket.
 */

static int do_nfs_rpc_call(struct nfs_server *server, int *start, int *end, int size)
{
	struct file *file;
	struct inode *inode;
	struct socket *sock;
	unsigned short fs;
	int result;
	int xid;
	int len;
	select_table wait_table;
	struct select_table_entry entry;
	int (*select) (struct inode *, struct file *, int, select_table *);
	int init_timeout, max_timeout;
	int timeout;
	int retrans;
	int major_timeout_seen;
	char *server_name;
	int n;
	int addrlen;
	unsigned long old_mask;
	/* JEJB/JSP 2/7/94
	 * This is for a 4 byte recv of the xid only */
	int recv_xid;

	xid = start[0];
	len = ((char *) end) - ((char *) start);
	file = server->file;
	inode = file->f_inode;
	select = file->f_op->select;
	sock = socki_lookup(inode);
	if (!sock) {
		printk("nfs_rpc_call: socki_lookup failed\n");
		return -EBADF;
	}
	init_timeout = server->timeo;
	max_timeout = NFS_MAX_RPC_TIMEOUT*HZ/10;
	retrans = server->retrans;
	major_timeout_seen = 0;
	server_name = server->hostname;
	old_mask = current->blocked;
	current->blocked |= ~(_S(SIGKILL)
#if 0
		| _S(SIGSTOP)
#endif
		| ((server->flags & NFS_MOUNT_INTR)
		? ((current->sigaction[SIGINT - 1].sa_handler == SIG_DFL
			? _S(SIGINT) : 0)
		| (current->sigaction[SIGQUIT - 1].sa_handler == SIG_DFL
			? _S(SIGQUIT) : 0))
		: 0));
	fs = get_fs();
	set_fs(get_ds());
	for (n = 0, timeout = init_timeout; ; n++, timeout <<= 1) {
		result = sock->ops->send(sock, (void *) start, len, 0, 0);
		if (result < 0) {
			printk("nfs_rpc_call: send error = %d\n", result);
			break;
		}
	re_select:
		wait_table.nr = 0;
		wait_table.entry = &entry;
		current->state = TASK_INTERRUPTIBLE;
		if (!select(inode, file, SEL_IN, &wait_table)
		    && !select(inode, file, SEL_IN, NULL)) {
			if (timeout > max_timeout) {
			  /* JEJB/JSP 2/7/94
			   * This is useful to see if the system is
			   * hanging */
			  printk("NFS max timeout reached on %s\n",
				 server_name);
			  timeout = max_timeout;
			}
			current->timeout = jiffies + timeout;
			schedule();
			remove_wait_queue(entry.wait_address, &entry.wait);
			current->state = TASK_RUNNING;
			if (current->signal & ~current->blocked) {
				current->timeout = 0;
				result = -ERESTARTSYS;
				break;
			}
			if (!current->timeout) {
				if (n < retrans)
					continue;
				if (server->flags & NFS_MOUNT_SOFT) {
					printk("NFS server %s not responding, "
						"timed out\n", server_name);
					result = -EIO;
					break;
				}
				n = 0;
				timeout = init_timeout;
				init_timeout <<= 1;
				if (!major_timeout_seen) {
				  printk("NFS server %s not responding, "
					 "still trying\n", server_name);
				}
				major_timeout_seen = 1;
				continue;
			}
			else
				current->timeout = 0;
		}
		else if (wait_table.nr)
			remove_wait_queue(entry.wait_address, &entry.wait);
		current->state = TASK_RUNNING;
		addrlen = 0;
		/* JEJB/JSP 2/7/94
		 * Get the xid from the next packet using a peek, so keep it
		 * on the recv queue.  If it is wrong, it will be some reply
		 * we don't now need, so discard it */
		result = sock->ops->recvfrom(sock, (void *)&recv_xid,
					     sizeof(recv_xid), 1, MSG_PEEK,
					     NULL, &addrlen);
		if (result < 0) {
			if (result == -EAGAIN) {
#if 0
				printk("nfs_rpc_call: bad select ready\n");
#endif
				goto re_select;
			}
			if (result == -ECONNREFUSED) {
#if 0
				printk("nfs_rpc_call: server playing coy\n");
#endif
				goto re_select;
			}
			if (result != -ERESTARTSYS) {
				printk("nfs_rpc_call: recv error = %d\n",
					-result);
			}
			break;
		}
		if (recv_xid == xid) {
			if (major_timeout_seen)
				printk("NFS server %s OK\n", server_name);
			break;
		}
		/* JEJB/JSP 2/7/94
		 * we have xid mismatch, so discard the packet and start
		 * again.  What a hack! but I can't call recvfrom with
		 * a null buffer yet. */
		(void)sock->ops->recvfrom(sock, (void *)&recv_xid,
					  sizeof(recv_xid), 1, 0, NULL,
					  &addrlen);
#if 0
		printk("nfs_rpc_call: XID mismatch\n");
#endif
		goto re_select;
	}
	/* JEJB/JSP 2/7/94
	 *
	 * we have the correct xid, so read into the correct place and
	 * return it
	 *
	 */
	result=sock->ops->recvfrom(sock, (void *)start, 
				  size + 1024, 1, 0, NULL,
			/* Here is NFS_SLACK_SPACE..., hack */
				  &addrlen);
	if (result < 0) {
		printk("NFS: notice message: result=%d\n", result);
	} else if (result < addrlen) {
		printk("NFS: just caught a too small read memory size..., email to NET channel\n");
		printk("NFS: result=%d,addrlen=%d\n", result, addrlen);
		result = -EIO;
	}
	current->blocked = old_mask;
	set_fs(fs);
	return result;
}

/*
 * For now we lock out other simultaneous nfs calls for the same filesystem
 * because we are single-threaded and don't want to get mismatched
 * RPC replies.
 */

int nfs_rpc_call(struct nfs_server *server, int *start, int *end, int size)
{
	int result;

	while (server->lock)
		sleep_on(&server->wait);
	server->lock = 1;
	result = do_nfs_rpc_call(server, start, end, size);
	server->lock = 0;
	wake_up(&server->wait);
	return result;
}

