/*
 *  linux/fs/nfs/sock.c
 *
 *  Copyright (C) 1992  Rick Sladkey
 *
 *  low-level nfs remote procedure call interface
 */

#include <linux/sched.h>
#include <linux/nfs_fs.h>
#include <linux/errno.h>
#include <linux/socket.h>
#include <linux/fcntl.h>

#include <netinet/in.h>

#include "../../net/kern_sock.h"

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

/*
 * 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)
{
	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;

	xid = start[0];
	len = ((char *) end) - ((char *) start);
	file = server->file;
	inode = file->f_inode;
	select = file->f_op->select;
	sock = socki_lookup(inode);
	init_timeout = server->timeo;
	max_timeout = NFS_MAX_RPC_TIMEOUT*HZ/10;
	retrans = server->retrans;
	major_timeout_seen = 0;
	server_name = server->hostname;
	if (!sock) {
		printk("nfs_rpc_call: socki_lookup failed\n");
		return -EBADF;
	}
	__asm__("mov %%fs,%0":"=r" (fs));
	__asm__("mov %0,%%fs"::"r" ((unsigned short) 0x10));
	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)
				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) {
#if 0
				/* doesn't work yet */
				if (!(server->flags & NFS_MOUNT_INTR))
					goto re_select;
#endif
				current->timeout = 0;
				result = -EINTR;
				break;
			}
			if (!current->timeout) {
				if (n < retrans)
					continue;
				if (server->flags & NFS_MOUNT_SOFT) {
					printk("NFS server %s not responding, "
						"timed out", 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;
		result = sock->ops->recv(sock, (void *) start, 4096, 1, 0);
		if (result < 0) {
			if (result == -EAGAIN) {
				printk("nfs_rpc_call: bad select ready\n");
				goto re_select;
			}
			if (result != -ERESTARTSYS) {
				printk("nfs_rpc_call: recv error = %d\n",
					-result);
			}
			break;
		}
		if (*start == xid) {
			if (major_timeout_seen)
				printk("NFS server %s OK\n", server_name);
			break;
		}
#if 0
		printk("nfs_rpc_call: XID mismatch\n");
#endif
	}
	__asm__("mov %0,%%fs"::"r" (fs));
	return result;
}

/*
 * For now we lock out other simulaneous nfs calls for the same filesytem
 * 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 result;

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

