/*
 * CDE - Common Desktop Environment
 *
 * Copyright (c) 1993-2012, The Open Group. All rights reserved.
 *
 * These libraries and programs are free software; you can
 * redistribute them and/or modify them under the terms of the GNU
 * Lesser General Public License as published by the Free Software
 * Foundation; either version 2 of the License, or (at your option)
 * any later version.
 *
 * These libraries and programs are distributed in the hope that
 * they will be useful, but WITHOUT ANY WARRANTY; without even the
 * implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
 * PURPOSE. See the GNU Lesser General Public License for more
 * details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with these libraries and programs; if not, write
 * to the Free Software Foundation, Inc., 51 Franklin Street, Fifth
 * Floor, Boston, MA 02110-1301 USA
 */
//%%  (c) Copyright 1993, 1994 Hewlett-Packard Company			
//%%  (c) Copyright 1993, 1994 International Business Machines Corp.	
//%%  (c) Copyright 1993, 1994 Sun Microsystems, Inc.			
//%%  (c) Copyright 1993, 1994 Novell, Inc. 				
//%%  $TOG: mp_stream_socket.C /main/10 1998/03/19 18:58:53 mgreess $ 			 				
/*
 *
 * mp_socket.cc
 *
 * Copyright (c) 1990 by Sun Microsystems, Inc.
 */
#include "tt_options.h"
#include <stdio.h>
#include "mp/mp_stream_socket.h"
#if defined(__linux__)
#include <sys/poll.h>
#else
#include <poll.h>
#endif
#include <sys/socket.h>
#include <fcntl.h>
#include <unistd.h>
#include <memory.h>
#include <errno.h>
#include "util/tt_global_env.h"
#include "util/tt_host.h"
#include "util/tt_port.h"

#if defined(OPT_TLI)
#	include <mp/mp_rpc_fns.h>
#	include <tiuser.h>
#else
#	include <netinet/tcp.h>
#endif

#include <sys/time.h>

#include <arpa/inet.h>

/* 
 * Constructs a socket object. Using (char *)0 for host means use the
 * current host. Specifying a portnum of 0 indicates that the first
 * available port number should be chosen.
 */
_Tt_stream_socket::
_Tt_stream_socket()
{
	_is_source = 0;
	_msgsock = -1;
	_sock = -1;
	_hostaddr.sin_port = 0;
	_hostaddr.sin_family = 0;
        memset(&_hostaddr.sin_addr, 0, sizeof(struct in_addr));

}

_Tt_stream_socket::
_Tt_stream_socket(_Tt_host_ptr &host, int portnum)
{
	_msgsock = -1;
	_host = host;
	memset(&_hostaddr, 0, sizeof(_hostaddr));
        _hostaddr.sin_addr.s_addr = htonl(INADDR_ANY);
	_hostaddr.sin_port = htons(portnum);
	_hostaddr.sin_family = AF_INET;
	_is_source = 0;
	_sock = -1;
}


/* 
 * Closes the connection on the socket.
 */
_Tt_stream_socket::
~_Tt_stream_socket()
{
#ifndef OPT_TLI
	close(_sock);
	close(_msgsock);
#else
	t_close(_sock);
	if (_msgsock != _sock) {
		t_close(_msgsock);
	}
#endif
}

int _Tt_stream_socket::
sock()
{
	return(_sock);
}

/* 
 * Returns the file descriptor associated with a socket.
 */
int _Tt_stream_socket::
fd()
{
	if (_is_source) {
		if ((_msgsock != -1) || (accept() != -1)) {
			return(_msgsock);
		} else {
			return(-1);
		}
	} else {
		return(_sock);
	}
}


/* 
 * Returns the external port number attached to a socket.
 */
int _Tt_stream_socket::
port()
{
#if defined(OPT_TLI)
	return(_port);
#else
        return((int)ntohs(_hostaddr.sin_port));
#endif
}


/* 
 * Initializes a stream socket. This method must be called before using a
 * socket to receive messages. The method assumes that if hostname is
 * (char *)0 then this is the "from" end of a socket. Otherwise, it is
 * assumed that this is an object which connects to a socket (presumed
 * already open) on the host named in _host.
 */
int _Tt_stream_socket::
init(int init_as_source)
{
	_Tt_host	host;

#if defined(OPT_TLI)
	_sock = t_open("/dev/tcp", O_RDWR, 0);
	if (_sock < 0) {
		_tt_syslog( 0, LOG_ERR,
			    "_Tt_stream_socket::init(): t_open(): %s",
			    t_strerror( t_errno ) );
		return 0;
	}
#else
#if defined(__linux__) || defined(CSRG_BASED)
	socklen_t	len;
#else
	int		len;
#endif
	int		optval = 1;

	_sock = socket(AF_INET, SOCK_STREAM, 0);
	if (_sock < 0) {
		_tt_syslog( 0, LOG_ERR,
			    "_Tt_stream_socket::init(): socket(): %m" );
		return 0;
	}
#endif
	if (-1==fcntl(_sock, F_SETFD, 1)) {
		_tt_syslog( 0, LOG_ERR, "_Tt_stream_socket::init(): "
			    "fcntl(F_SETFD): %m");
	}		

	_is_source = init_as_source;
	if (init_as_source) { /* 'from' end of socket */
#if !defined(OPT_TLI)
#ifndef linux
		if (setsockopt(_sock, SOL_SOCKET, SO_USELOOPBACK,
			       (char *)&optval, sizeof(int)) == -1) {
			_tt_syslog( 0, LOG_ERR, "_Tt_stream_socket::init(): "
				    "setsockopt(SO_USELOOPBACK): %m" );
			close(_sock);
			return(0);
		}
#endif
		if (setsockopt(_sock, IPPROTO_TCP, TCP_NODELAY,
			       (char *)&optval, sizeof(int)) == -1) {
			_tt_syslog( 0, LOG_ERR, "_Tt_stream_socket::init(): "
				    "setsockopt(TCP_NODELAY): %m" );
		}

		if (bind(_sock, (struct sockaddr *)&_hostaddr,
			 sizeof(_hostaddr)) < 0) {
			close(_sock);
			return(0);
		}
		len = sizeof(sockaddr_in);
		if (getsockname(_sock, (sockaddr *)&_hostaddr, &len) < 0) {
			return(0);
		}
		return(listen(_sock,5) == 0);
#else
		struct t_bind	*bind;

		if ((bind = (struct t_bind *)t_alloc(_sock, T_BIND, T_ADDR)) ==
		    (struct t_bind *)0) {
			_tt_syslog( 0, LOG_ERR, "_Tt_stream_socket::init(): "
				    "t_alloc(T_BIND): %s",
				    t_strerror( t_errno ) );
			return(0);
		}
		// We use random port selection.
		// This means that we always let t_bind choose a
		// suitable port number for us and we ignore the
		// portnum argument given in the constructor for this
		// class.  If we were ever to claim _Tt_stream_socket
		// as a general C++ wrapper class for socket/TLI, this
		// would have to change.
		bind->addr.len = 0;
		bind->qlen = 8;
		if (t_bind(_sock, bind, bind) < 0) {
			_tt_syslog( 0, LOG_ERR, "_Tt_stream_socket::init(): "
				    "t_bind(): %s", t_strerror( t_errno ) );
			t_free((char *)bind, T_BIND);
			return(0);
		}
		_port = ntohs(((sockaddr_in *)(bind->addr.buf))->sin_port);
		t_free((char *)bind, T_BIND);
		_srequest = (struct t_call *)t_alloc(_sock, T_CALL, T_ADDR);
		if (_srequest == (t_call *)0) {
			_tt_syslog( 0, LOG_ERR, "_Tt_stream_socket::init(): "
				    "t_alloc(T_CALL): %s",
				    t_strerror( t_errno ) );
			return(0);
		}
#endif // !OPT_TLI
	} else { // 'to' end of socket
		//
		// If both Client and Server or (To and From) sockets are 
		// on the same host, it is better to use the localhost ip 
		// address. This removes ToolTalks dependency on the state 
		// of the network and  permits standalone operation.
		//
		// In this instance 'local_host' is the host where ttsession 
		// is running. '_host' is the host where the 'procid' 
		// proccess is running.  If both are on the same host, copy 
		// in the the localhost ip address rather than the actual 
		// "configured" ip address.
		// 
		_Tt_host_ptr	local_host;
		if ( (_tt_global->get_local_host(local_host) ) && 
			( _host->stringaddr() == local_host->stringaddr() ) ) {
			//
			// _host_ == local_host => (both on same host)
			//
			_hostaddr.sin_addr.s_addr = inet_addr((char *)"127.0.0.1");    	    
		} else {
			//
			// _host_ != local_host => (on different hosts)
			//
			memcpy((char *)&_hostaddr.sin_addr,
			(char *)_host->addr(),
			_host->addr_length());
		}	   
#if !defined(OPT_TLI)
		// set up socket options to insure that a close will
		// immediately send the message to a socket. This is
		// essential for the use of sockets as signalling 
		// mechanisms.

		if (setsockopt(_sock, IPPROTO_TCP, TCP_NODELAY,
			       (char *)&optval, sizeof(int)) == -1) {
			_tt_syslog( 0, LOG_ERR, "_Tt_stream_socket::init(): "
				    "setsockopt(TCP_NODELAY): %m" );
		}


#ifndef linux
		if (setsockopt(_sock, SOL_SOCKET, SO_USELOOPBACK,
			       (char *)&optval, sizeof(int)) == -1) {
			_tt_syslog( 0, LOG_ERR, "_Tt_stream_socket::init(): "
				    "setsockopt(SO_USELOOPBACK): %m" );
			close(_sock);
			return(0);
		}
#endif
		if (setsockopt(_sock, SOL_SOCKET, SO_REUSEADDR,
			       (char *)&optval, sizeof(int)) == -1) {
			_tt_syslog( 0, LOG_ERR, "_Tt_stream_socket::init(): "
				    "setsockopt(SO_REUSEADDR): %m" );
			close(_sock);
			return(0);
		}

#if defined(sun)
		// XXX: It's not at all clear that we need to do this
		// anywhere.. default seems to be don't linger anyway.
		if (setsockopt(_sock, SOL_SOCKET, ~SO_LINGER,
			       (char *)&optval, sizeof(int)) == -1) {
			_tt_syslog( 0, LOG_WARNING, "_Tt_stream_socket::init(): "
				    "setsockopt(~SO_LINGER): %m" );
		}
		
#endif // sun
		if (connect(_sock,
			    (struct sockaddr *)&_hostaddr,
			    sizeof(_hostaddr)) < 0) {
			close(_sock);
			return(0);
		}
#else
		t_call		*sndcall, *rcvcall;

		if (t_bind(_sock, 0, 0) < 0) {
			_tt_syslog( 0, LOG_ERR, "_Tt_stream_socket::init(): "
				    "t_bind(,0,0): %s", t_strerror( t_errno ) );
			return(0);
		}
		(void)_tt_tli_set_nodelay(_sock);
		sndcall = (t_call *)t_alloc(_sock, T_CALL, 0);
		if (sndcall == 0) {
			_tt_syslog( 0, LOG_ERR, "_Tt_stream_socket::init(): "
				    "t_alloc(T_CALL,0): %s",
				    t_strerror( t_errno ) );
			return(0);
		}
		sndcall->addr.maxlen = sizeof(_hostaddr);
		sndcall->addr.len = sizeof(_hostaddr);
		sndcall->addr.buf = (char *)&_hostaddr;
		sndcall->opt.len = 0;
		sndcall->udata.len = 0;
		rcvcall = (t_call *)t_alloc(_sock, T_CALL, T_OPT|T_ADDR);
		if (rcvcall == 0) {
			_tt_syslog( 0, LOG_ERR, "_Tt_stream_socket::init(): "
				    "t_alloc(T_CALL, T_OPT|T_ADDR): %s",
				    t_strerror( t_errno ) );
			if (t_free((char *)sndcall, T_CALL) < 0) {
				_tt_syslog( 0, LOG_ERR, "t_free(): %s",
					    t_strerror( t_errno ) );
			}
			return(0);
		}
		rcvcall->udata.maxlen = 0;
		if (t_connect(_sock, sndcall, rcvcall) < 0) {
			_tt_syslog( 0, LOG_ERR, "t_connect(): %s",
				    t_strerror( t_errno ) );
			sndcall->addr.buf = 0;
			if (t_free((char *)sndcall, T_CALL) < 0) {
				_tt_syslog( 0, LOG_ERR, "t_free(sndcall): %s",
					    t_strerror( t_errno ) );
			}
			if (t_free((char *)rcvcall, T_CALL) < 0) {
				_tt_syslog( 0, LOG_ERR, "t_free(rcvcall): %s",
					    t_strerror( t_errno ) );
			}
			t_close(_sock);
			return(0);
		}
		sndcall->addr.buf = 0;
		if (t_free((char *)sndcall, T_CALL) < 0) {
			_tt_syslog( 0, LOG_ERR, "t_free(sndcall): %s",
				    t_strerror( t_errno ) );
		}
		if (t_free((char *)rcvcall, T_CALL) < 0) {
			_tt_syslog( 0, LOG_ERR, "t_free(rcvcall): %s",
				    t_strerror( t_errno ) );
		}
#endif				// !OPT_TLI

	}

	return(1);
}


/* 
 * Sends a message to a socket. Since the socket object is used by the mp
 * primarily to signal clients, a close is done after the write to insure
 * the message gets flushed out to the receiving socket.
 * 
 * --> It would be extremely helpful if all the really "fatal" cases of
 * failing to write to a socket can be identified since this information
 * could be fed back to the mp server to determine when a process has
 * died or is no longer listening on a socket.
 */
int _Tt_stream_socket::
send(char *msg, int len)
{
	int	rval;

#ifndef OPT_TLI
	if ((rval = ::send(_sock, msg, len, 0)) == len) {
		return(rval);
	} else {
		close(_sock);
		return(0);
	}
#else
	if ((rval = t_snd(_sock, msg, len, 0)) == len) {
		return(rval);
	} else {
		_tt_syslog(0, LOG_ERR,
			   "==> ERROR FROM SEND: len = %d, rval = %d, err = %d\n",
			   len, rval, t_errno);
		t_close(_sock);
		return(0);
	}
#endif				// !OPT_TLI
}


int _Tt_stream_socket::
accept()
{
	if (_msgsock == -1) {
#ifndef OPT_TLI
#if defined(__linux__) || defined(CSRG_BASED)
		socklen_t		addrlen = sizeof(sockaddr_in);
#else
		int			addrlen = sizeof(sockaddr_in);
#endif
		sockaddr_in		saddr;

		_msgsock = ::accept(_sock, (struct sockaddr *)&saddr,
				    &addrlen);

		if (_msgsock < 0) {
			_tt_syslog( 0, LOG_ERR, "_Tt_stream_socket::accept(): "
				    "accept(): %m" );
			return -1;
		}
		if (-1==fcntl(_msgsock, F_SETFD, 1)) {
			_tt_syslog( 0, LOG_ERR, "_Tt_stream_socket::accept(): "
				    "fcntl(F_SETFD): %m");
		}		
#else
		int			rval;
		struct t_call	       *call_data;
		struct t_bind	       *bind_data;

		call_data = (struct t_call *)t_alloc(_sock, T_CALL, T_ALL);

		if (t_listen(_sock, call_data) < 0) {
			_Tt_string errstr(t_strerror(t_errno));
			if (t_errno == TSYSERR) {
				// Add in errno info
				errstr = errstr.cat(": ").cat(strerror(errno));
			}
			_tt_syslog( 0, LOG_ERR, "_Tt_stream_socket::accept(): "
				    "t_listen(): %s", (char *) errstr );
			return(-1);
		}
		// Since we expect no further connections on this
		// endpoint, it would theoretically be possible
		// to use the same fd in arg 1 and arg 2 of t_accept.
		// This appears to be an actual advantage of TLI over
		// sockets -- I don't think you can do this with
		// sockets.  However, I can't get it to work!
		// So what I do is open a new endpoint, accept to
		// that, and then close the original fd since we
		// don't need it any more.
		
		_msgsock = t_open("/dev/tcp", O_RDWR, 0);
		if (_msgsock < 0) {
			_tt_syslog( 0, LOG_ERR,
				    "_Tt_stream_socket::accept(): "
				    "t_open(): %s",
				    t_strerror( t_errno ) );
			t_free((char *)call_data, T_CALL);
			return -1;
		}
		bind_data = (struct t_bind *)t_alloc(_msgsock, T_BIND, T_ALL);
		if (t_bind(_msgsock, bind_data, bind_data) < 0) {
			_tt_syslog( 0, LOG_ERR, "_Tt_stream_socket::accept(): "
				    "t_bind(): %s", t_strerror( t_errno ) );
			if (bind_data) t_free((char *)bind_data, T_BIND);
			if (call_data) t_free((char *)call_data, T_CALL);
			return -1;
		}
		
		rval = t_accept(_sock, _msgsock, call_data);
		if (rval == -1) {
			_tt_syslog( 0, LOG_ERR, "t_accept(): %s",
				    t_strerror( t_errno ) );
			t_free((char *)bind_data, T_BIND);
			t_free((char *)call_data, T_CALL);
			return(-1);
		} else {
			(void)_tt_tli_set_nodelay(_msgsock);
			if (-1==fcntl(_msgsock, F_SETFD, 1)) {
				_tt_syslog( 0, LOG_ERR,
					    "_Tt_stream_socket::accept(): "
					    "fcntl(F_SETFD): %m");
			}		
		}
		t_free((char *)bind_data, T_BIND);
		t_free((char *)call_data, T_CALL);
		t_close(_sock);
		_sock = _msgsock;
#endif				// !OPT_TLI
	}

	return(_msgsock);
}


/* 
* Receives a message from a socket. This method will block if there is
* no input so if nonblocking is required it should be called only after
* the socket fd has been checked for activity.
* 
* --> sockets can be set to be nonblocking. Should this be the default
* for the sockets the mp opens?
*/
int _Tt_stream_socket::
recv(char *msg, int msglen)
{
	int rval;
	
	if (_msgsock == -1 || accept() == -1) {
		return(-1);
	}
	
#ifndef OPT_TLI
	if ((rval = ::recv(_msgsock, msg, msglen, 0)) < 0) {
		close(_msgsock); 
		return(-1);
	}
#else
	int			flags;
	
	rval = t_rcv(_msgsock, msg, msglen, &flags);
	if (rval == -1) {
		if  (t_errno == TLOOK) {
			if (t_look(_msgsock) == T_DISCONNECT &&
			    t_rcvdis(_msgsock,(struct t_discon *)0) < 0) {
				_tt_syslog( 0, LOG_ERR, "t_rcvdis(): %s",
					    t_strerror( t_errno ) );
				return(-1);
			} else {
				return(0);
			}
		} else {
			_tt_syslog( 0, LOG_ERR, "t_rcv(): %s",
				    t_strerror( t_errno ) );
			return(-1);
		}
	}
#endif				// !OPT_TLI
	msg[rval] = 0;
	return(rval);
}



// read_would_block is not a predicate.  It returns 1 if a read
// is safe (would not block.  It returns 0 if a read would block,
// and -1 if there is some error condition.

int _Tt_stream_socket::
read_would_block()
{
	struct pollfd fds[1];

	fds[0].fd = _msgsock;
	fds[0].events = POLLIN;
	fds[0].revents = 0;

	while(-1 == poll(fds, (sizeof fds)/(sizeof (struct pollfd)), 0)) {
		if (errno==EAGAIN || errno==EINTR) {
			// interrupted, try again.
		} else {
			// something is wrong
			return -1;
		}
	}

	if (0 != (fds[0].revents & (POLLHUP|POLLNVAL|POLLERR)) ) {
		return -1;
	} else if (0 != (fds[0].revents & POLLIN) ) {
		return 1;
	} else {
		return 0;
	}
}


