archie/prospero/lib/pfs/opentcp.c
2024-05-27 16:13:40 +02:00

204 lines
6.7 KiB
C

/*
* Copyright (c) 1991-1994 by the University of Southern California
*
* For copying and distribution information, please see the file
* <usc-license.h>.
*/
#include <usc-license.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h> /* for fcntl() */
#include <fcntl.h> /* for fcntl() */
#include <errno.h> /* for EINPROGRESS. etc.*/
#ifdef AIX /* lucb */
#include <sys/select.h>
#endif
#include <errno.h>
#include <pfs_threads.h>
#include <ardp.h>
#include <perrno.h>
#include <pfs.h>
#include <sys/time.h> /* For gettimeofday */
#include <sockettime.h> /* for xxx_APPROACH */
/* Open a TCP stream from here to the HOST at the PORT. */
/* Return socket descriptor on success, or -1 on failure. */
/* This is mostly swiped from user/vcache/gopherget.c */
/* It is part of the Prospero library. It includes special goodies to keep
the connect() operation from blocking in a multi-threaded environment.
Non-blocking or briefly-blocking TCP opens are important here. */
/* Uses hostname2addr() in the ARDP library. */
/* Like close, but try again if EINTR */
static int
sure_close(int s)
{
int retval;
while ((((retval = close(s)) == -1) && (errno == EINTR)));
return(retval);
}
/* Wait till the file descriptor is writable or times out */
/* Returns: 1 success, 0, timeout, -1 (& errno) error */
static int
wait_till_writable(int s,int timeout)
{
struct timeval time_out;
fd_set writefds;
int retval;
int soerror;
int lensoerrors = sizeof(soerror);
time_out.tv_sec = timeout;
time_out.tv_usec = 0;
FD_ZERO(&writefds);
FD_SET(s,&writefds);
switch (retval = select(FD_SETSIZE, NULL, &writefds, NULL, &time_out)) {
case 1:
retval = getsockopt(s, SOL_SOCKET, SO_ERROR,
(char *)&soerror, &lensoerrors);
/* While we'd like the error back in soerror,
it fails when there is an error*/
if (retval) return -1;
return 1;
case 0:
case -1:
default:
return(retval);
}
}
/* Wait till the file descriptor is readable or times out */
/* Returns: 1 success, 0, timeout, -1 (& errno) error */
int
wait_till_readable(int s,int timeout)
{
struct timeval time_out;
fd_set readfds;
time_out.tv_sec = timeout;
time_out.tv_usec = 0;
FD_ZERO(&readfds);
FD_SET(s,&readfds);
return(select(FD_SETSIZE, &readfds, NULL, NULL, &time_out));
}
int
quick_open_tcp_stream(const char host[], int port, int timeout)
{
extern int ardp_hostname2addr(const char *hostname, struct sockaddr_in *hostaddr);
struct sockaddr_in server; /* server side socket. */
int s; /* the socket */
int tmp; /* return from subfunctions. */
if(ardp_hostname2addr(host, &server)) {
p_err_string = qsprintf_stcopyr(p_err_string,
"%s: unknown host\n", host);
return -1; /* p_err_string not set in callee */
}
server.sin_port = htons(port);
if ((s = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
p_err_string = qsprintf_stcopyr(p_err_string,
"Couldn't reach host %s: Call to socket(AF_INET, SOCK_STREAM,\
0) failed: errno %d: %s", host, errno, unixerrstr());
return -1;
}
#ifdef SETSOCKOPTS
/* Actually, these may be generally usefull. */
#define M_SETSOCKOPT(f,sol,p1,p2,p3) \
if (setsockopt(f,sol,p1,p2,p3)) { \
p_err_string = qsprintf_stcopyr(p_err_string, \
"INTERNAL: setsockopt failure: %s %s", "##p1##",unixerrstr()); \
sure_close(f); \
return -1; \
}
#ifndef UCX
M_SETSOCKOPT(s, SOL_SOCKET, ~SO_LINGER, 0, 0);
#endif
/* Allow address reuse, I forget exactly why, but its something to do
with a time delay before address:port pairs can be reused otherwise */
M_SETSOCKOPT(s, SOL_SOCKET, SO_REUSEADDR, 0, 0);
/* Attempt to keep a silent connection alive, and so fail if it goes down */
M_SETSOCKOPT(s, SOL_SOCKET, SO_KEEPALIVE, 0, 0);
#endif /*SETSOCKOPTS*/
#if defined(NONBLOCKING_APPROACH) || defined(SELECT_APPROACH)
/* This is one approach to multi-threading. The problem with it is
that we seem to get 'address in use' errors. */
/* Set the socket non-blocking if we're running multithreaded. The main
downside of this approach is that we're not going to be able to get a
great error message if the call to connect() fails, which is why we
don't normally enable this in the non-threaded case. */
/* This code has not been fully tested. If the FNDELAY operation is
unavailable on your version of unix, you will need an alternative
approach. Mitra has one in his TCPTIME patch. */
/* This may be FDELAY on some systems -
its O_NODELAY or O_NONBLOCK on Solaris2.3 & POSIX*/
if(fcntl(s, F_SETFL, O_NONBLOCK) == -1) {
p_err_string = qsprintf_stcopyr(p_err_string,
"call to fcntl(s, F_SETFL, \
FNDELAY) failed: errno %d: %s", errno, unixerrstr());
/* Close the socket so we don't have trash data hanging around. */
sure_close(s);
return -1;
}
#endif /*NONBLOCKING_APPROACH||SELECT_APPROACH*/
/* Connect will do the bind for us! Hooray! That's a relief. */
/* connect() will return -1 if the connection is already in progress. */
#ifdef TIMEOUT_APPROACH
if ((quick_connect(s, (struct sockaddr *) &server, sizeof server, timeout))
==-1) {
#else /*!TIMEOUT_APPROACH*/
redo_connect:
tmp = connect(s, (struct sockaddr *) &server, sizeof server);
if (tmp == -1) {
/* Ick - doing an fprintf, meant that in the non-debugging mode,
where there is no stderr, this will fail and CHANGE errno to "Bad
FileNo */
switch (errno) {
case EINPROGRESS:
#ifdef SELECT_APPROACH
switch (wait_till_writable(s,timeout)) {
case -1: p_err_string = qsprintf_stcopyr(p_err_string,
"unable to connect to: %s(%d)", host, port);
/* "call to select failed: %s", unixerrstr()); */
sure_close(s);
return -1;
case 0:
p_err_string = qsprintf_stcopyr(p_err_string,
"took more than %d secs to connect to: %s(%d)",
timeout, host, port);
sure_close(s);
return -1;
}
/* Default is going to be 1 - which is success */
#endif /*SELECT_APPROACH*/
return s; /* Non-blocking*/
case EINTR: goto redo_connect; /* Interrupted*/
}
#endif /*!TIMEOUT_APPROACH*/
/* I've changed this cos users will see it all the time, lets make
it friendly - this has all the relevant info! - Mitra */
p_err_string = qsprintf_stcopyr(p_err_string,
"Couldn't connect to host %s: %s", host, unixerrstr());
/* Now close it so we don't have the descriptor and associated data
hanging around. */
sure_close(s);
return -1;
}
return s;
}