/*
 * sock_session.c - Contains functions that supply SOCK_STREAM type sockets for
 *                  NetBEUI protocol stack which their names has a
 *                  'nbso_session_' prefix, and also some utility functions that
 *                  their names only has a 'nbso_' prefix.
 * 
 * Notes:
 *	- VRP in comments is the acronym of "Value Result Parameter"
 *
 * Copyright (c) 1997 by Procom Technology,Inc.
 *
 * This program can be redistributed or modified under the terms of the 
 * GNU General Public License as published by the Free Software Foundation.
 * This program is distributed without any warranty or implied warranty
 * of merchantability or fitness for a particular purpose.
 *
 * See the GNU General Public License for more details.
 *
 */
 


#include <linux/net.h>
#include <linux/sched.h>
#include <linux/wait.h>
#include <linux/fs.h>
#include <linux/malloc.h>
#include <linux/string.h>
#include <linux/kernel.h>
#include <linux/types.h>
#include <linux/timer.h>
#include <linux/uio.h>
#include <linux/skbuff.h>
#include <linux/netbeui.h>


#define NBSO_SHUTDOWN_MASK	3
#define NBSO_RCV_SHUTDOWN	1
#define NBSO_SND_SHUTDOWN	2



/*
 * SOCK_STREAM internal functions
 */

/*
 * Function: nbso_end_acked_backlog
 *	Closes sessions that their connection was established but not
 *	accepted yet.
 *
 * Parameters:
 *	sk : pointer to NetBEUI socket that must close its waited connections.
 *
 * Returns: none
 */

static void
nbso_end_acked_backlog (struct netbeui_sock *sk)
{
	session_t   *sn;
	
	while (sk->acked_backlog_list) {
		sn = sk->acked_backlog_list;
		sk->acked_backlog_list = sk->acked_backlog_list->next;
		nbss_hangup(sn);
	}

	return;
}


/*
 * Function: nbso_autobind
 *	Automatically binds a NetBEUI socket to NAME_NUMBER_1 .
 *
 * Parameters:
 *	sk : pointer to NetBEUI socket that must bind it to NAME_NUMBER_1 .
 *
 * Returns: int
 *	0  : if NetBEUI socket binds to NAME_NUMBER_1 successfully.
 *	-1 : if can not bind NetBEUI socket to NAME_NUMBER_1 .
 */

static int
nbso_autobind (struct netbeui_sock *sk)
{
	sk->name = nbns_name_number_1();
	if (sk->name)
		return 0;

	return -1;   
}


/*
 * Function: nbso_session_ready (Call back function)
 *	Gets an established session from transport layer, and puts it
 *	in its owner's queue for sessions which are waited for accept.
 *
 * Parameters:
 *	owner   : a void pointer to NetBEUI socket that is owner of established
 *	          session.
 *	session : a void pointer to established session.
 *
 * Returns: none
 */

static void
nbso_session_ready (void *owner, void *session)
{
	session_t   *sn = (session_t *) session;
	struct netbeui_sock   *sk = (struct netbeui_sock *) owner;

	if (sk->acked_backlog_list)
		sk->acked_backlog_list->prev = sn;

	sn->next = sk->acked_backlog_list;
	sn->prev = NULL;
	sk->acked_backlog_list = sn;

	wake_up_interruptible(sk->waitq);

	return;
}


/*
 * Function: nbso_abort_interface_session (Call back function)
 *	Transport layer announces that a session is not valid from now.
 *	we must perform different actions depend on NetBEUI socket state:
 *	i)   if socket state is NBSO_INIT , we are in a special case that causes
 *	     when a 'nbss_call()' was interruptted. we must only announce process
 *	     which wants to establish connection that its request was aborted.
 *	ii)  if socket state is NBSO_RUNNING , we must announce all processes
 *	     that wait for something on the socket.
 *	iii) if socket state is NBSO_LISTENNING , we only must remove the
 *	     session from backlog list.
 *
 * Parameters:
 *	owner   : a void pointer to NetBEUI socket that is owner of aborted
 *	          session.
 *	session : a void pointer to aborted session.
 *
 * Returns: none
 */

static void
nbso_abort_interface_session (void *owner, void *session)
{
	struct netbeui_sock   *sk = (struct netbeui_sock *) owner;
	session_t   *sn = (session_t *) session;

	if (sk->state == NBSO_INIT) {
		sk->socket->state = SS_UNCONNECTED;
		return;
	}

	if (sk->state == NBSO_RUNNING) {
		sk->state = NBSO_INIT;
		sk->socket->state = SS_UNCONNECTED;
		wake_up_interruptible(sk->waitq);
		return;
	}

	/* NOW, Certainly state is NBSO_LISTENING */

	if (sn->next)
		sn->next->prev = sn->prev;

	if (sn->prev)
		sn->prev->next = sn->next;
	else 
		sk->acked_backlog_list = sn->next;    

	/* To countervail deletion of this session from backlog list */
	nbss_listen(sk->name, sk->max_acked_backlog, (void *) owner,
			nbso_abort_interface_session,
			nbso_session_ready				);

	return;
}


/*
 * Function: nbso_start_timer
 *	Starts a timer.
 *
 * Parameters:
 *	tmr      : pointer to timer that must be started.
 *	tmr_func : pointer to FIRE function of timer.
 *	param    : parameter that will be passed to FIRE function. this often
 *	           is a pointer to dependent data structure.
 *
 * Returns: none
 */

static inline void
nbso_start_timer (struct timer_list *tmr, void *tmr_func, unsigned long param)
{
	tmr->data = param;
	tmr->function = tmr_func;
	add_timer(tmr);

	return;
}


/*
 * Function: nbso_stop_timer
 *	Stops a timer.
 *
 * Parameters:
 *	tmr      : pointer to timer that must be stoped.
 *
 * Returns: int
 *	0  : timer was stopped succesfully.
 *	-1 : a fault occurs during timer stopping (this is rare case).
 */

static inline int
nbso_stop_timer (struct timer_list *tmr)
{
	if (del_timer(tmr))
		return 0;

	return -1;
}


/******************************************************************************/
/****************************** SOCK_STREAM Calls *****************************/
/******************************************************************************/

/*
 * Function: nbso_session_create
 *	Performs additional actions at creation of SOCK_STREAM sockets (if
 *	required).
 *
 * Parameters:
 *	sock     : pointer to 'struct socket' that created by system before
 *	           call this function.
 *	protocol : an integer that have protocol specific meaning and we
 *	           do not use it.
 *
 * Returns: int
 *	0 : No Additional Action Required.
 */

static int
nbso_session_create (struct socket *sock, int protocol)
{
	return 0;
}


/*
 * Function: nbso_session_dup
 *	Performs additional actions at duplication of SOCK_STREAM sockets (if
 *	required).
 *
 * Parameters:
 *	newsock : pointer to 'struct socket' that created by system before
 *	          call this function & is destination of copy operation.
 *	oldsock : pointer to original socket.
 *
 * Returns: int
 *	0 : No Additional Action Required.
 */

static int 
nbso_session_dup (struct socket *newsock, struct socket *oldsock)
{
	return 0;
}


/*
 * Function: nbso_session_release
 *	Performs additional actions at release of SOCK_STREAM sockets.
 *
 * Parameters:
 *	sock : pointer to socket that must be released.
 *	peer : pointer to peer socket. This parameter defined only for
 *	       UNIX domain sockets (AF_UNIX) and we do not use it.
 *
 * Returns: int
 *	0 : in all cases. (this function always succeed)
 */

static int 
nbso_session_release (struct socket *sock, struct socket *peer)
{
	unsigned long   flags;
	struct netbeui_sock   *sk = (struct netbeui_sock *) sock->data;

	sk->shutdown = NBSO_SHUTDOWN_MASK;

	save_flags(flags);
	cli();
	if (sk->state == NBSO_LISTENING) {
			nbss_end_listen(sk->name);
			nbso_end_acked_backlog(sk);
	}
	restore_flags(flags);
	
	if (sk->session)
		nbss_hangup(sk->session);
	if (sk->name)
		nbns_del_name(sk->name);

	return 0;
}


/*
 * Function: nbso_session_bind
 *	Performs additional actions at bind of SOCK_STREAM sockets to names.
 *
 * Parameters:
 *	sock     : pointer to socket that must bind a name to it.
 *	uaddr    : pointer to 'struct sockaddr_netbeui' that contains
 *	           information about sightly name.
 *	addr_len : length of 'struct sockaddr_netbeui'.
 *
 * Returns: int
 *	0        : if name is binded to socket successfully.
 *	negative : if a fault occurs.
 *	           (-EINVAL) : if socket binds already.
 */

static int 
nbso_session_bind (struct socket *sock, struct sockaddr *uaddr, int addr_len)
{
	int   rc;
	name_t   *nb_name;
	struct netbeui_sock   *sk = (struct netbeui_sock *) sock->data;
	struct sockaddr_netbeui   *addr = (struct sockaddr_netbeui *) uaddr;

	if (sk->name)
		return (-EINVAL);  /* return (-EALREADY); ?! */

	rc = nbns_add_name(addr->snb_addr.name, addr->snb_addr.name_type,
								&nb_name);
	if (rc)
		return rc; /* -ENOTUNIQ ?! */

	sk->name = nb_name;
	return 0;
}


/*
 * Function: nbso_session_connect
 *	Performs additional actions at connect of SOCK_STREAM sockets to a
 *	specified peer.
 *
 * Parameters:
 *	sock     : pointer to socket that must connect to peer.
 *	uaddr    : pointer to 'struct sockaddr_netbeui' that contains
 *	           information about peer.
 *	addr_len : length of 'struct sockaddr_netbeui'.
 *	sflags   : bitwise integer that contains socket flags.
 *
 * Returns: int
 *	0        : if socket connects to the specified peer successfully.
 *	negative : if a fault occurs.
 *	           (-EISCONN)    : socket connected already or listens for
 *	                           incomming connection requests.
 *	           (-EAGAIN)     : no name available for the socket now, try
 *	                           again.
 *	           (-ECONNRESET) : connection reset by peer.
 */

static int 
nbso_session_connect (struct socket *sock, struct sockaddr *uaddr, int addr_len,
								 int sflags)
{
	int   rc;
	session_t   *sn;
	unsigned long   flags;
	struct netbeui_sock   *sk = (struct netbeui_sock *) sock->data;
	struct sockaddr_netbeui   *addr = (struct sockaddr_netbeui *) uaddr;

	if (sk->state != NBSO_INIT)
		return (-EISCONN);

	if (!sk->name)
		/* To force short circuiting by compiler */
		if (nbso_autobind(sk) != 0)
			return (-EAGAIN);   /* return (-EADDRNOTAVAIL); ?! */

	sock->state = SS_CONNECTING;

	rc = nbss_call(sk->name, addr->snb_addr.name, (void *) sk,
			nbso_abort_interface_session, &sn);
	if (rc)
		return rc;

	save_flags(flags);
	cli();

	if (sock->state != SS_CONNECTING) {
		restore_flags(flags);
		return (-ECONNRESET);
	}

	sock->state = SS_CONNECTED;

	sk->state = NBSO_RUNNING;
	sk->waitq = &sn->waitq;
	sk->session = sn;

	restore_flags(flags);

	return 0;
}


/*
 * Function: nbso_session_socketpair
 *	This function was defined only for AF_UNIX sockets.
 *
 * Parameters:
 *
 * Returns: int
 *	   (-EOPNOTSUPP) : This operation NOT supported by us.
 */

static int 
nbso_session_socketpair (struct socket *sock1, struct socket *sock2)
{
	return (-EOPNOTSUPP);
}


/*
 * Function: nbso_session_accept
 *	Performs additional actions at accept of incomming connection requests
 *	for a SOCK_STREAM socket.
 *
 * Parameters:
 *	sock    : pointer to socket that wants to accept the incomming
 *	          connection requests.
 *	newsock : (Semi VRP!) pointer to a new socket with attributes like
 *	          original that connection between it & peer will be established.
 *	          this 'struct socket' created by system before call us, and
 *	          we only must complete its fields.
 *	sflags  : bitwise integer that contains socket flags.
 *
 * Returns: int
 *	0        : if connection is successfully established between newsock
 *	           and peer.
 *	negative : if a fault occurs.
 *	           (-EPERM)       : this operation permitted only after a
 *	                            successful call of listen().
 *	           (-EWOULDBLOCK) : user requests non-blocking operation, but
 *	                            operation would block.
 *	           (-ERESTARTSYS) : interrupted system call.
 */

static int 
nbso_session_accept (struct socket *sock, struct socket *newsock, int sflags)
{
	int   rc;
	unsigned long   flags;
	struct netbeui_sock   *sk = (struct netbeui_sock *) sock->data;

	if (sk->state != NBSO_LISTENING)
		return (-EPERM);

	newsock->state = SS_CONNECTING;
	
	save_flags(flags);
	cli();
	if (sk->acked_backlog_list) {
		struct netbeui_sock   *newsk = (struct netbeui_sock *) newsock->data;

		newsk->session = sk->acked_backlog_list;

		newsk->session->owner = (void *) newsk;

		newsock->state = SS_CONNECTED;
		newsk->name = nbns_use_name(sk->name);
		newsk->state = NBSO_RUNNING;
		newsk->waitq = &newsk->session->waitq;

		sk->acked_backlog_list = sk->acked_backlog_list->next;
		if (sk->acked_backlog_list)
			sk->acked_backlog_list->prev = NULL;

		restore_flags(flags);
		
		nbss_listen(sk->name, sk->max_acked_backlog, (void *) sk,
				nbso_abort_interface_session, nbso_session_ready);
		return 0;
	}
	restore_flags(flags);

	if (sflags & O_NONBLOCK) {
		newsock->state = SS_UNCONNECTED;
		return (-EWOULDBLOCK);
	}

	rc = nbss_listen(sk->name, sk->max_acked_backlog, (void *) sk,
				nbso_abort_interface_session, nbso_session_ready);
	if (rc < 0) {
		newsock->state = SS_UNCONNECTED;
		return rc;
	}

	save_flags(flags);
	cli();

	if (sk->acked_backlog_list) {
	    	restore_flags(flags);

		/* Try again */
	    	return (nbso_session_accept(sock, newsock, sflags));
	}

	interruptible_sleep_on(sk->waitq);
	restore_flags(flags);

	if (current->signal & ~current->blocked)
		return (-ERESTARTSYS);
		
	/* Try again */
	return (nbso_session_accept(sock, newsock, sflags));
}


/*
 * Function: nbso_session_getname
 *	Gets SOCK_STREAM socket name or peer name that connected to it.
 *
 * Parameters:
 *	sock      : pointer to socket that we need to name of it or its peer.
 *	uaddr     : (VRP) pointer to 'struct sockaddr_netbeui' that be filled
 *	            with requested information.
 *	uaddr_len : (VRP) pointer to an integer that returns length of
 *	            'struct sockaddr_netbeui'.
 *	peer      : an integer that indicates type of request.
 *
 * Returns: int
 *	0        : if requested name is retrieved successfully.
 *	negative : if a fault occurs.
 *	           (-ENOTCONN) : name of peer was requested but socket has not
 *	                         any connection.
 *	           (-EBADF)    : socket not bounded to a name but name of it
 *	                         was requested.
 */

static int 
nbso_session_getname (struct socket *sock, struct sockaddr *uaddr, int *uaddr_len,
								 int peer)
{
	unsigned long   flags;
	struct netbeui_sock   *sk = (struct netbeui_sock *) sock->data;
	struct sockaddr_netbeui   *addr = (struct sockaddr_netbeui *) uaddr;

	if (peer) {
		save_flags(flags);
		cli();

		if (sk->state != NBSO_RUNNING) {
			restore_flags(flags);
			return (-ENOTCONN);
		}

		memcpy(addr->snb_addr.name, sk->session->remote_name, NB_NAME_LEN);
		addr->snb_addr.name_type = sk->session->remote_name_type;

		restore_flags(flags);
	}
	else {
		if (!sk->name)
			return (-EBADF);

		memcpy(addr->snb_addr.name, sk->name->name, NB_NAME_LEN);
		addr->snb_addr.name_type = sk->name->type;
	}

	return 0;
}


/*
 * Function: nbso_session_select
 *	Determines operational (particularly I/O) condition of SOCK_STREAM
 *	socket.
 *
 * Parameters:
 *	sock     : pointer to socket that check it.
 *	sel_type : an integer that determines type of checking.
 *	wait     : pointer to a particular structure that contains some
 *	           wait queues. The system itself checks members of these 
 *	           wait queues for their time outs. we only sleep on this
 *	           structure if there is not exist a categoric answer, so far.
 *
 * Returns: int
 *	0 : means that however must wait.
 *	1 : means that answer is positive or an error occured.
 */

static int 
nbso_session_select (struct socket *sock , int sel_type, select_table *wait)
{
	struct netbeui_sock   *sk = (struct netbeui_sock *) sock->data;

	if (sk->state != NBSO_RUNNING)
		return 1;

	switch (sel_type) {
		case SEL_IN:
			if ((sk->shutdown & NBSO_RCV_SHUTDOWN) ||
			    (nbss_receive_ready(sk->session) == 0))
				return 1; 

			break;

		case SEL_OUT:
			if ((sk->shutdown & NBSO_SND_SHUTDOWN) ||
			    (nbss_send_ready(sk->session) == 0))
				return 1; 

			break;

		case SEL_EX:
	     	/* Currently, we haven't any exception definition in NetBEUI */
			return 1;
	}

	select_wait(sk->waitq, wait);

	return 0;
}


/*
 * Function: nbso_session_ioctl
 *	Performs some particular operations on SOCK_STREAM socket, that can not
 *	do with regular system calls.
 *
 * Parameters:
 *	sock : pointer to socket that action must perform on it.
 *	cmd  : an integer that indicates type of operation.
 *	arg  : this parameter often is a pointer to 'cmd' relative data structure
 *	       that be used by it as an argument.
 *
 * Returns: int
 *	0        : if cmd is performed successfully.
 *	negative : if a fault occurs. error codes that bubble to user are
 *	           dependent to cmd.
 */

static int 
nbso_session_ioctl (struct socket *sock, unsigned int cmd, unsigned long arg)
{
	struct netbeui_sock   *sk = (struct netbeui_sock *) sock->data;

	switch (cmd) {
                case SIOCTRIMDATA:
			if (sk->state == NBSO_RUNNING)
				return (nbss_trim_data(sk->session));

                        return (-EOPNOTSUPP);

                case SIOCSENDZERO:
			if (sk->state == NBSO_RUNNING)
				return (nbss_send_zero(sk->session, (char *) arg));

			return (-EOPNOTSUPP);

		default: 
			return (-EINVAL);
	}
}


/*
 * Function: nbso_session_listen
 *	Listens for incomming connection requests, and places them in a queue
 *	that MAXimum of its length is 'backlog'. this operation defined only
 *	for SOCK_STREAM functions.
 *
 * Parameters:
 *	sock    : pointer to socket that must listens for incomming requests.
 *	backlog : an integer that indicates length of queue which holds
 *	          incomming requests that not accepted yet.
 *
 * Returns: int
 *	0        : if operation is performed successfully.
 *	negative : if a fault occurs.
 *	           (-EPERM) : this operation not permitted on a connected socket.
 */

static int 
nbso_session_listen (struct socket *sock, int backlog)
{
	struct netbeui_sock   *sk = (struct netbeui_sock *) sock->data;

	if (sk->state == NBSO_RUNNING)
		return (-EPERM);
	
	if ((unsigned) backlog == 0)	/* BSDism */
		backlog = 1;
	if ((unsigned) backlog > SOMAXCONN)
		backlog = SOMAXCONN;
	
	if (backlog > sk->max_acked_backlog)
		nbss_listen(sk->name, backlog, (void *) sk,
		            nbso_abort_interface_session, nbso_session_ready);

	sk->max_acked_backlog = backlog;
	sk->state = NBSO_LISTENING;

	return 0;
}


/*
 * Function: nbso_session_shutdown
 *	Shuts down part of a full-duplex connection.
 *
 * Parameters:
 *	sock : pointer to socket that part of its connection must be closed.
 *	how  : an integer that indicates part of full_duplex connection that
 *	       must be closed.
 *
 * Returns: int
 *	0        : if operation is performed successfully.
 *	negative : if a fault occurs.
 *	           (-ENOTCONN) : the specified socket is not connected.
 *	           (-EINVAL)   : the 'how' parameter has not a valid value.
 */

static int 
nbso_session_shutdown (struct socket *sock, int how)
{
	struct netbeui_sock   *sk = (struct netbeui_sock *) sock->data;

	if (!sk)
		return (-ENOTCONN);
	/* To avoid short circuiting by compiler */
	if (sk->state != NBSO_RUNNING)
		return (-ENOTCONN);

	how++; /*
		* maps 0->1 has the advantage of making bit 1 rcvs and
		*      1->2 bit 2 snds.
		*      2->3
		*/

	if ((how & ~NBSO_SHUTDOWN_MASK) || how == 0) /* Is "1 <= how <= 3" ?! */
		return (-EINVAL);

	sk->shutdown |= how;

	return 0;
}


/*
 * Function: nbso_session_setsockopt
 *	Sets some operational options of SOCK_STREAM sockets.
 *
 * Parameters:
 *	sock    : a pointer to socket that must tune its options.
 *	level   : this parameter is not used in this function and always is zero.
 *	optname : an integer that indicates option that must tune.
 *	optval  : a pointer to related data structure which used for assign
 *	          value(s) to option.
 *	optlen  : length of data structure that 'optval' points to it.
 *
 * Returns: int
 *	0        : if tuning is performed successfully.
 *	negative : if a fault occurs.
 *	           (-EOPNOTSUPP)  : Operation not supported by us.
 *	           (-ENOPROTOOPT) : Option name is not defined for us.
 */

static int 
nbso_session_setsockopt (struct socket *sock, int level, int optname, char *optval,
								int optlen)
{
	struct netbeui_sock   *sk = (struct netbeui_sock *) sock->data;

  	switch (optname) {

		case SO_SNDBUF:
			return (-EOPNOTSUPP);

		case SO_RCVBUF:
			return (-EOPNOTSUPP);

		case SO_SNDLOWAT:
			return (-EOPNOTSUPP);

		case SO_RCVLOWAT:
			return (-EOPNOTSUPP);

		case SO_SNDTIMEO:
			memcpy_fromfs((void *)&sk->sto, optval,
			              MIN(sizeof(sk->sto), optlen));
			return 0;

		case SO_RCVTIMEO:
			memcpy_fromfs((void *)&sk->rto, optval,
			              MIN(sizeof(sk->rto), optlen));
			return 0;

	}

	return(-ENOPROTOOPT);
}


/*
 * Function: nbso_session_getsockopt
 *	Gets some operational options of SOCK_STREAM sockets.
 *
 * Parameters:
 *	sock    : a pointer to socket that action performs on it.
 *	level   : this parameter is not used in this function and always is zero.
 *	optname : an integer that indicates option that must be gotten.
 *	optval  : (VRP) a pointer to related data structure which used for
 *	          getting value(s) of option.
 *	optlen  : (VRP) length of data structure that 'optval' points to it.
 *
 * Returns: int
 *	0        : if operation is performed successfully.
 *	negative : if a fault occurs.
 *	           (-EOPNOTSUPP)  : Operation not supported by us.
 *	           (-ENOPROTOOPT) : Option name is not defined for us.
 */

static int 
nbso_session_getsockopt (struct socket *sock, int level, int optname, char *optval,
								 int *optlen)
{
	int len;
	struct netbeui_sock   *sk = (struct netbeui_sock *) sock->data;
  	
  	switch (optname) {
		case SO_SNDBUF:
			return (-EOPNOTSUPP);

		case SO_RCVBUF:
			return (-EOPNOTSUPP);

		case SO_SNDLOWAT:
			return (-EOPNOTSUPP);

		case SO_RCVLOWAT:
			return (-EOPNOTSUPP);

		case SO_SNDTIMEO:
			len = MIN(get_user((int *)optlen), sizeof(sk->sto));
			memcpy_tofs(optval, (void *)&sk->sto, len);
			put_user(len, (int *)optlen);
			return 0;			

		case SO_RCVTIMEO:
			len = MIN(get_user((int *)optlen), sizeof(sk->rto));
			memcpy_tofs(optval, (void *)&sk->rto, len);
			put_user(len, (int *)optlen);
			return 0;			
  	}

	return(-ENOPROTOOPT);
}


/*
 * Function: nbso_session_fcntl
 *	Performs some miscellaneous operations on SOCK_STREAM sockets.
 *
 * Parameters:
 *	sock : pointer to socket that function must perform on it.
 *	cmd  : an integer that indicates what action must perform.
 *	arg  : this parameter often is a pointer to 'cmd' relative data structure
 *	       that be used by it as an argument.
 *
 * Returns: int
 *	0        : if operation is performed successfully. for GET commands
 *	           return code may be non-zero in successful return! (see
 *	           'fcntl()' man page for more).
 *	negative : if a fault occurs.
 *	           (-EOPNOTSUPP) : No additional fcntl() command supported for
 *	                           SOCK_STREAM sockets.
 */

static int 
nbso_session_fcntl (struct socket *sock, unsigned int cmd, unsigned long arg)
{
	return (-EOPNOTSUPP);
}


/*
 * Function: nbso_session_sendmsg
 *	Sends a message through a SOCK_STREAM socket to desired target.
 *
 * Parameters:
 *	sock     : a pointer to socket that data sends through it.
 *	msg      : a pointer to 'struct msghdr' that contains message body,
 *	           target name and etc.
 *	len      : length of message all around.
 *	nonblock : an integer that if be set to non-zero value means that
 *	           no waiting (sleeping, blocking & ...) acceptable during
 *	           operation.
 *	sflags   : bitwise integer that contains socket flags.
 *
 * Returns: int
 *	positive : indicates how many bytes of data was sent.
 *	negative : if a fault occurs.
 *	           (-EINVAL)      : we do not support any flags.
 *	           (-EPIPE)       : 'send part' of full-duplex connection was
 *	                            closed already.
 *	           (-ENOTCONN)    : socket is not connected yet.
 *	           (-EMSGSIZE)    : length of one of iovec buffers is greater
 *	                            than NB_MAX_DATALEN.
 *	           (-EWOULDBLOCK) : user requests non-blocking operation, but
 *	                            operation would block.
 */

static int 
nbso_session_sendmsg (struct socket *sock, struct msghdr *msg, int len, int nonblock,
								int sflags)
{
	int   rc = 0,
	      iov_no,
	      bytes_sent;
	struct iovec   *iov;
	struct timer_list   snd_tmr;
	struct netbeui_sock   *sk = (struct netbeui_sock *) sock->data;

	if (sflags)
		return (-EINVAL);

	if (sk->shutdown & NBSO_SND_SHUTDOWN) {
		send_sig(SIGPIPE, current, 1);
		return (-EPIPE);
	}

	if ((sock->state != SS_CONNECTED) || (sk->state != NBSO_RUNNING))
		return (-ENOTCONN);

	init_timer(&snd_tmr);
	if (sk->sto.tv_sec || sk->sto.tv_usec) {
		snd_tmr.expires = (sk->sto.tv_sec * HZ) + 
		                  (sk->sto.tv_usec/(1000000/HZ)) +
		                  jiffies;
		nbso_start_timer(&snd_tmr, nbss_abort_send,
		                 (unsigned long) sk->session);
	}

	bytes_sent = 0;
	iov = msg->msg_iov;
	iov_no = msg->msg_iovlen;
	
	/* All things are good, so start to send data ... */
	while (iov_no--) {

		/* Currently, limit on size of data
		   which can be sent is 64K-bytes   */
		if (iov->iov_len > NB_MAX_DATALEN) {
			rc = (bytes_sent ? bytes_sent : -EMSGSIZE);
			break;
		}

		rc = nbss_send(sk->session, iov->iov_base, iov->iov_len,
								nonblock, 0);

		if (rc < 0) {
			rc = (bytes_sent ? bytes_sent : rc);
			break;
		}

		bytes_sent += rc;

		if (rc < iov->iov_len) {
			rc = (bytes_sent ? bytes_sent : -EWOULDBLOCK);
			break;
		}

		++iov;
		rc = bytes_sent;
	}

	nbso_stop_timer(&snd_tmr);

	return rc;
}


/*
 * Function: nbso_session_recvmsg
 *	Receives a message through a SOCK_STREAM socket from desired source.
 *
 * Parameters:
 *	sock     : a pointer to socket that data receives through it.
 *	msg      : (VRP) a pointer to 'struct msghdr' that at return contains
 *	           message body, source name and etc.
 *	size     : MAXimum length of message all around.
 *	nonblock : an integer that if be set to non-zero value means that
 *	           no waiting (sleeping, blocking & ...) acceptable during
 *	           operation.
 *	sflags   : bitwise integer that contains socket flags.
 *	addr_len : (VRP) a pointer to an integer that if it is not NULL, at
 *	           return will be filled with length of 'struct sockaddr_netbeui'.
 *
 * Returns: int
 *	positive : indicates how many bytes of data was received.
 *	negative : if a fault occurs.
 *	           (-EINVAL)      : we do not support any flags.
 *	           (-EPIPE)       : 'receive part' of full-duplex connection was
 *	                            closed already.
 *	           (-ENOTCONN)    : socket is not connected yet.
 *	           (-EWOULDBLOCK) : user requests non-blocking operation, but
 *	                            operation would block.
 */

static int 
nbso_session_recvmsg (struct socket *sock, struct msghdr *msg, int size, int nonblock,
						int sflags, int *addr_len)
{
	int   rc = 0,
	      iov_no,
	      bytes_received;
	struct iovec   *iov;
	struct timer_list   rcv_tmr;
	struct netbeui_sock   *sk = (struct netbeui_sock *) sock->data;

	if (sflags)
		return (-EINVAL);

	if (sk->shutdown & NBSO_RCV_SHUTDOWN) {
		send_sig(SIGPIPE, current, 1);
		return (-EPIPE);
	}

	if ((sock->state != SS_CONNECTED) || (sk->state != NBSO_RUNNING))
	 	if (!((sk->session) && (nbss_receive_ready(sk->session) == 0)))
			return (-ENOTCONN);

	if (addr_len)
		*addr_len = sizeof(struct sockaddr_netbeui);

	init_timer(&rcv_tmr);
	if (sk->rto.tv_sec || sk->rto.tv_usec) {
		rcv_tmr.expires = (sk->rto.tv_sec * HZ) + 
		                  (sk->rto.tv_usec/(1000000/HZ)) +
		                  jiffies;
		nbso_start_timer(&rcv_tmr, nbss_abort_receive, (unsigned long) sk->session);
	}

	bytes_received = 0;
	iov = msg->msg_iov;
	iov_no = msg->msg_iovlen;

	while (iov_no--) {

		rc = nbss_receive(sk->session, iov->iov_base, iov->iov_len, nonblock);

		if (rc < 0) {
			rc = (bytes_received ? bytes_received : rc);
			break;
		}

		bytes_received += rc;

		if (rc < iov->iov_len) {
			rc = (bytes_received ? bytes_received : -EWOULDBLOCK);
			break;
		}
		
		++iov;
		rc = bytes_received;
	}

	nbso_stop_timer(&rcv_tmr);

	return rc;
}


/* Dispatcher struct for SOCK_STREAM calls */

struct proto_ops   nbso_session_proto = {
	AF_NETBEUI, 
	nbso_session_create,
	nbso_session_dup,
	nbso_session_release,
	nbso_session_bind,
	nbso_session_connect,
	nbso_session_socketpair,
	nbso_session_accept,
	nbso_session_getname,
	nbso_session_select,
	nbso_session_ioctl,
	nbso_session_listen,
	nbso_session_shutdown,
	nbso_session_setsockopt,
	nbso_session_getsockopt,
	nbso_session_fcntl,
	nbso_session_sendmsg,
	nbso_session_recvmsg
};
