Files
cdesktop/cde/lib/tt/lib/mp/mp_stream_socket.C

621 lines
16 KiB
C

/*
* 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 librararies 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>
# if defined(OPT_BUG_USL)
extern int t_errno;
# endif
#else
# include <netinet/tcp.h>
#endif
#include <sys/time.h>
#if defined(ultrix)
extern "C" unsigned long inet_addr(char *);
#endif
#include <arpa/inet.h>
#if defined(OPT_BUG_USL)
extern char *t_errlist[];
char *t_strerror(int t_errno)
{
return(t_errlist[t_errno]);
}
#endif
/*
* 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()
{
}
_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;
}
/*
* 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 defined(_AIX) && (OSMAJORVERSION==4) && (OSMINORVERSION==2)
if (getsockname(_sock, (sockaddr *)&_hostaddr, (size_t *)&len)
< 0) {
#else
if (getsockname(_sock, (sockaddr *)&_hostaddr, &len) < 0) {
#endif
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 defined(OPT_BUG_USL)
t_sync(_sock);
#endif
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;
#if defined(_AIX) && (OSMAJORVERSION==4) && (OSMINORVERSION==2)
_msgsock = ::accept(_sock, (struct sockaddr *)&saddr,
(size_t *)&addrlen);
#else
_msgsock = ::accept(_sock, (struct sockaddr *)&saddr,
&addrlen);
#endif
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;
}
}