/*
 *  linux/fs/ncpfs/sock.c
 *
 *  Copyright (C) 1992, 1993  Rick Sladkey
 *
 *  Modified 1995, 1996 by Volker Lendecke to be usable for ncp
 *
 */

#include <linux/sched.h>
#include <linux/ncp_fs.h>
#include <linux/errno.h>
#include <linux/socket.h>
#include <linux/fcntl.h>
#include <linux/stat.h>
#include <asm/segment.h>
#include <linux/in.h>
#include <linux/net.h>
#include <linux/mm.h>
#include <linux/netdevice.h>
#include <linux/ipx.h>

#include <linux/ncp.h>
#include <linux/ncp_fs.h>
#include <linux/ncp_fs_sb.h>
#include <net/sock.h>


#define _S(nr) (1<<((nr)-1))
static int _recvfrom(struct socket *sock, unsigned char *ubuf,
		     int size, int noblock, unsigned flags,
		     struct sockaddr_ipx *sa, int *addr_len)
{
        struct iovec iov;
        struct msghdr msg;

        iov.iov_base = ubuf;
        iov.iov_len  = size;

        msg.msg_name      = (void *)sa;
        msg.msg_namelen   = 0;
        if (addr_len)
                msg.msg_namelen = *addr_len;
        msg.msg_control = NULL;
        msg.msg_iov       = &iov;
        msg.msg_iovlen    = 1;

        return sock->ops->recvmsg(sock, &msg, size, noblock, flags, addr_len);
}

static int _sendto(struct socket *sock, const void *buff,
		   int len, int nonblock, unsigned flags,
		   struct sockaddr_ipx *sa, int addr_len)

{
        struct iovec iov;
        struct msghdr msg;

        iov.iov_base = (void *)buff;
        iov.iov_len  = len;

        msg.msg_name      = (void *)sa;
        msg.msg_namelen   = addr_len;
        msg.msg_control = NULL;
        msg.msg_iov       = &iov;
        msg.msg_iovlen    = 1;

        return sock->ops->sendmsg(sock, &msg, len, nonblock, flags);
}


static void
ncp_wdog_data_ready(struct sock *sk, int len)
{
	struct socket *sock = sk->socket;

	if (!sk->dead)
	{
		unsigned char packet_buf[2];
		struct sockaddr_ipx sender;
		int addr_len = sizeof(struct sockaddr_ipx);
		int result;
		unsigned short fs;

		fs = get_fs();
		set_fs(get_ds());

		result = _recvfrom(sock, (void *)packet_buf, 2, 1, 0,
				   &sender, &addr_len);

		if (   (result != 2)
		    || (packet_buf[1] != '?')
		    /* How to check connection number here? */
		    )
		{
			printk("ncpfs: got strange packet on watchdog "
			       "socket\n");
		}
		else
		{
			int result;
			DDPRINTK("ncpfs: got watchdog from:\n");
			DDPRINTK("ncpfs: %08lX:%02X%02X%02X%02X%02X%02X:%04X,"
				" conn:%02X,type:%c\n",
				htonl(sender.sipx_network),
				sender.sipx_node[0], sender.sipx_node[1],
				sender.sipx_node[2], sender.sipx_node[3],
				sender.sipx_node[4], sender.sipx_node[5],
				ntohs(sender.sipx_port),
				packet_buf[0], packet_buf[1]);

			packet_buf[1] = 'Y';
			result = _sendto(sock, (void *)packet_buf, 2, 1, 0,
					 &sender, sizeof(sender));
			DDPRINTK("send result: %d\n", result);
		}
		set_fs(fs);
	}
}

int
ncp_catch_watchdog(struct ncp_server *server)
{
        struct file   *file;
        struct inode  *inode;
        struct socket *sock;
        struct sock   *sk;

        if (   (server == NULL)
            || ((file  = server->wdog_filp) == NULL)
            || ((inode = file->f_inode) == NULL)
            || (!S_ISSOCK(inode->i_mode)))
	{
                printk("ncp_catch_watchdog: did not get valid server!\n");
                server->data_ready = NULL;
                return -EINVAL;
        }

        sock = &(inode->u.socket_i);

        if (sock->type != SOCK_DGRAM)
	{
                printk("ncp_catch_watchdog: did not get SOCK_DGRAM\n");
                server->data_ready = NULL;
                return -EINVAL;
        }

        sk   = (struct sock *)(sock->data);

        if (sk == NULL)
	{
                printk("ncp_catch_watchdog: sk == NULL");
                server->data_ready = NULL;
                return -EINVAL;
        }

        DDPRINTK("ncp_catch_watchdog: sk->d_r = %x, server->d_r = %x\n",
                 (unsigned int)(sk->data_ready),
                 (unsigned int)(server->data_ready));

        if (sk->data_ready == ncp_wdog_data_ready)
	{
                printk("ncp_catch_watchdog: already done\n");
                return -EINVAL;
        }

        server->data_ready = sk->data_ready;
        sk->data_ready = ncp_wdog_data_ready;
	sk->allocation = GFP_ATOMIC;
        return 0;
}
                
int
ncp_dont_catch_watchdog(struct ncp_server *server)
{
        struct file   *file;
        struct inode  *inode;
        struct socket *sock;
        struct sock   *sk;

        if (   (server == NULL)
            || ((file  = server->wdog_filp) == NULL)
            || ((inode = file->f_inode) == NULL)
            || (!S_ISSOCK(inode->i_mode)))
	{
                printk("ncp_dont_catch_watchdog: "
                       "did not get valid server!\n");
                return -EINVAL;
        }

        sock = &(inode->u.socket_i);

        if (sock->type != SOCK_DGRAM)
	{
                printk("ncp_dont_catch_watchdog: did not get SOCK_DGRAM\n");
                return -EINVAL;
        }

        sk = (struct sock *)(sock->data);

        if (sk == NULL)
	{
                printk("ncp_dont_catch_watchdog: sk == NULL");
                return -EINVAL;
        }

        if (server->data_ready == NULL)
	{
                printk("ncp_dont_catch_watchdog: "
                       "server->data_ready == NULL\n");
                return -EINVAL;
        }

        if (sk->data_ready != ncp_wdog_data_ready)
	{
                printk("ncp_dont_catch_watchdog: "
                       "sk->data_callback != ncp_data_callback\n");
                return -EINVAL;
        }

        DDPRINTK("ncp_dont_catch_watchdog: sk->d_r = %x, server->d_r = %x\n",
                 (unsigned int)(sk->data_ready),
                 (unsigned int)(server->data_ready));

        sk->data_ready = server->data_ready;
	sk->allocation = GFP_KERNEL;
        server->data_ready = NULL;
        return 0;
}

static void
ncp_msg_data_ready(struct sock *sk, int len)
{
	struct socket *sock = sk->socket;

	if (!sk->dead)
	{
		unsigned char packet_buf[2];
		struct sockaddr_ipx sender;
		int addr_len = sizeof(struct sockaddr_ipx);
		int result;
		unsigned short fs;

		fs = get_fs();
		set_fs(get_ds());

		result = _recvfrom(sock, (void *)packet_buf, 2, 1, 0,
				   &sender, &addr_len);

		DPRINTK("ncpfs: got message of size %d from:\n", result);
		DPRINTK("ncpfs: %08lX:%02X%02X%02X%02X%02X%02X:%04X,"
			" conn:%02X,type:%c\n",
			htonl(sender.sipx_network),
			sender.sipx_node[0], sender.sipx_node[1],
			sender.sipx_node[2], sender.sipx_node[3],
			sender.sipx_node[4], sender.sipx_node[5],
			ntohs(sender.sipx_port),
			packet_buf[0], packet_buf[1]);

		ncp_trigger_message(sk->protinfo.af_ipx.ncp_server);

		set_fs(fs);
	}
}

int
ncp_catch_message(struct ncp_server *server)
{
        struct file   *file;
        struct inode  *inode;
        struct socket *sock;
        struct sock   *sk;

        if (   (server == NULL)
            || ((file  = server->msg_filp) == NULL)
            || ((inode = file->f_inode) == NULL)
            || (!S_ISSOCK(inode->i_mode)))
	{
                printk("ncp_catch_message: did not get valid server!\n");
                return -EINVAL;
        }

        sock = &(inode->u.socket_i);

        if (sock->type != SOCK_DGRAM)
	{
                printk("ncp_catch_message: did not get SOCK_DGRAM\n");
                return -EINVAL;
        }

        sk = (struct sock *)(sock->data);

        if (sk == NULL)
	{
                printk("ncp_catch_message: sk == NULL");
                return -EINVAL;
        }

        DDPRINTK("ncp_catch_message: sk->d_r = %x\n",
                 (unsigned int)(sk->data_ready));

        if (sk->data_ready == ncp_msg_data_ready)
	{
                printk("ncp_catch_message: already done\n");
                return -EINVAL;
        }

        sk->data_ready = ncp_msg_data_ready;
	sk->protinfo.af_ipx.ncp_server = server;
        return 0;
}
                
#define NCP_SLACK_SPACE 1024

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

static int
do_ncp_rpc_call(struct ncp_server *server, int size)
{
	struct file *file;
	struct inode *inode;
	struct socket *sock;
	unsigned short fs;
	int result;
	char *start = server->packet;
	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;
	int acknowledge_seen;
	char *server_name;
	int n;
	int addrlen;
	unsigned long old_mask;

	/* We have to check the result, so store the complete header */
	struct ncp_request_header request =
		*((struct ncp_request_header *)(server->packet));

	struct ncp_reply_header reply;	


	file = server->ncp_filp;
	inode = file->f_inode;
	select = file->f_op->select;
	sock = &inode->u.socket_i;
	if (!sock)
	{
		printk("ncp_rpc_call: socki_lookup failed\n");
		return -EBADF;
	}
	init_timeout = server->m.time_out;
	max_timeout = NCP_MAX_RPC_TIMEOUT;
	retrans = server->m.retry_count;
	major_timeout_seen = 0;
	acknowledge_seen = 0;
	server_name = server->m.server_name;
	old_mask = current->blocked;
	current->blocked |= ~(_S(SIGKILL)
#if 0
		| _S(SIGSTOP)
#endif
		| ((server->m.flags & NCP_MOUNT_INTR)
		? ((current->sig->action[SIGINT - 1].sa_handler == SIG_DFL
			? _S(SIGINT) : 0)
		| (current->sig->action[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)
	{
		DDPRINTK("ncpfs: %08lX:%02X%02X%02X%02X%02X%02X:%04X\n",
			htonl(server->m.serv_addr.sipx_network),
			server->m.serv_addr.sipx_node[0],
			server->m.serv_addr.sipx_node[1],
			server->m.serv_addr.sipx_node[2],
			server->m.serv_addr.sipx_node[3],
			server->m.serv_addr.sipx_node[4],
			server->m.serv_addr.sipx_node[5],
			ntohs(server->m.serv_addr.sipx_port));
		DDPRINTK("ncpfs: req.typ: %04X, con: %d, "
			"seq: %d",
			request.type,
			(request.conn_high << 8) + request.conn_low,
			request.sequence);
		DDPRINTK(" func: %d\n",
			 request.function);

		result = _sendto(sock, (void *) start, size, 0, 0,
				 &(server->m.serv_addr),
				 sizeof(server->m.serv_addr));
		if (result < 0)
		{
			printk("ncp_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 */
				if (acknowledge_seen == 0)
				{
					printk("NCP 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->m.flags & NCP_MOUNT_SOFT)
				{
					printk("NCP 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("NCP 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;

		/* Get the header 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 = _recvfrom(sock, (void *)&reply,
				   sizeof(reply), 1, MSG_PEEK,
				   NULL, &addrlen);
		if (result < 0)
		{
			if (result == -EAGAIN)
			{
				DPRINTK("ncp_rpc_call: bad select ready\n");
				goto re_select;
			}
			if (result == -ECONNREFUSED)
			{
				DPRINTK("ncp_rpc_call: server playing coy\n");
				goto re_select;
			}
			if (result != -ERESTARTSYS)
			{
				printk("ncp_rpc_call: recv error = %d\n",
					-result);
			}
			break;
		}
		if (   (result     == sizeof(reply))
		    && (reply.type == NCP_POSITIVE_ACK))
		{
			/* Throw away the packet */
			DPRINTK("ncp_rpc_call: got positive acknowledge\n");
			_recvfrom(sock, (void *)&reply, sizeof(reply), 1, 0,
				  NULL, &addrlen);
			n = 0;
			timeout = max_timeout;
			acknowledge_seen = 1;
			goto re_select;
		}

		DDPRINTK("ncpfs: rep.typ: %04X, con: %d, tsk: %d,"
			"seq: %d\n",
			reply.type,
			(reply.conn_high << 8) + reply.conn_low,
			reply.task,
			reply.sequence);
		
		if (   (result          >= sizeof(reply))
		    && (reply.type      == NCP_REPLY)
		    && (   (request.type    == NCP_ALLOC_SLOT_REQUEST)
			|| (   (reply.sequence  == request.sequence)
			    && (reply.conn_low  == request.conn_low)
/* seem to get wrong task from NW311 && (reply.task      == request.task)*/
			    && (reply.conn_high == request.conn_high))))
		{
			if (major_timeout_seen)
				printk("NCP 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. */
		_recvfrom(sock, (void *)&reply, sizeof(reply), 1, 0, NULL,
			  &addrlen);

		DPRINTK("ncp_rpc_call: reply mismatch\n");
		goto re_select;
	}
	/* 
	 * we have the correct reply, so read into the correct place and
	 * return it
	 */
	result = _recvfrom(sock, (void *)start, server->packet_size,
			   1, 0, NULL, &addrlen);
	if (result < 0)
	{
		printk("NCP: notice message: result=%d\n", result);
	}
	else if (result < sizeof(struct ncp_reply_header))
	{
		printk("NCP: just caught a too small read memory size..., "
		       "email to NET channel\n");
		printk("NCP: result=%d,addrlen=%d\n", result, addrlen);
		result = -EIO;
	}

	current->blocked = old_mask;
	set_fs(fs);
	return result;
}


/*
 * We need the server to be locked here, so check!
 */

static int
ncp_do_request(struct ncp_server *server, int size)
{
	int result;

	if (server->lock == 0)
	{
		printk("ncpfs: Server not locked!\n");
		return -EIO;
	}

	if (!ncp_conn_valid(server))
	{
		return -EIO;
	}

	result = do_ncp_rpc_call(server, size);

	DDPRINTK("do_ncp_rpc_call returned %d\n", result);

	if (result < 0)
	{
		/* There was a problem with I/O, so the connections is
                 * no longer usable. */
		ncp_invalidate_conn(server);
	}
	return result;
}

/* ncp_do_request assures that at least a complete reply header is
 * received. It assumes that server->current_size contains the ncp
 * request size */
int
ncp_request(struct ncp_server *server, int function)
{
	struct ncp_request_header *h
		= (struct ncp_request_header *)(server->packet);
	struct ncp_reply_header *reply
		= (struct ncp_reply_header *)(server->packet);

	int request_size = server->current_size
		- sizeof(struct ncp_request_header);

	int result;

	if (server->has_subfunction != 0)
	{
		*(__u16 *)&(h->data[0]) = htons(request_size - 2);
	}

	h->type = NCP_REQUEST;
	
	server->sequence += 1;
	h->sequence  = server->sequence;
	h->conn_low  = (server->connection) & 0xff;
	h->conn_high = ((server->connection) & 0xff00) >> 8;
	h->task      = 2;
	h->function  = function;

	if ((result = ncp_do_request(server, request_size + sizeof(*h))) < 0)
	{
		DPRINTK("ncp_request_error: %d\n", result);
		return result;
	}

	server->completion  = reply->completion_code;
	server->conn_status = reply->connection_state;
	server->reply_size  = result;
	server->ncp_reply_size = result - sizeof(struct ncp_reply_header);

	result = reply->completion_code;

	if (result != 0)
	{
		DPRINTK("ncp_completion_code: %x\n", result);
	}
	return result;	
}

int
ncp_connect(struct ncp_server *server)
{
	struct ncp_request_header *h
		= (struct ncp_request_header *)(server->packet);
	int result;

	h->type = NCP_ALLOC_SLOT_REQUEST;
	
	server->sequence = 0;
	h->sequence  = server->sequence;
	h->conn_low  = 0xff;
	h->conn_high = 0xff;
	h->task      = 2;
	h->function  = 0;

	if ((result = ncp_do_request(server, sizeof(*h))) < 0)
	{
		return result;
	}

	server->sequence = 0;
	server->connection = h->conn_low + (h->conn_high * 256);
	return 0;
}
	
int
ncp_disconnect(struct ncp_server *server)
{
	struct ncp_request_header *h
		= (struct ncp_request_header *)(server->packet);

	h->type = NCP_DEALLOC_SLOT_REQUEST;
	
	server->sequence += 1;
	h->sequence  = server->sequence;
	h->conn_low  = (server->connection) & 0xff;
	h->conn_high = ((server->connection) & 0xff00) >> 8;
	h->task      = 2;
	h->function  = 0;

	return ncp_do_request(server, sizeof(*h));
}

void
ncp_lock_server(struct ncp_server *server)
{
#if 0
	/* For testing, only 1 process */
	if (server->lock != 0)
	{
		DPRINTK("ncpfs: server locked!!!\n");
	}
#endif
        while (server->lock)
		sleep_on(&server->wait);
	server->lock = 1;
}

void
ncp_unlock_server(struct ncp_server *server)
{
        if (server->lock != 1)
	{
                printk("ncp_unlock_server: was not locked!\n");
        }

        server->lock = 0;
        wake_up(&server->wait);
}
        
