/*	$Id$
 *	Author:	Roger A. Cole
 *	Date:	11-23-90
 *
 *	Experimental Physics and Industrial Control System (EPICS)
 *
 *	Copyright 1991-92, the Regents of the University of California,
 *	and the University of Chicago Board of Governors.
 *
 *	This software was produced under  U.S. Government contracts:
 *	(W-7405-ENG-36) at the Los Alamos National Laboratory,
 *	and (W-31-109-ENG-38) at Argonne National Laboratory.
 *
 *	Initial development by:
 *		The Controls and Automation Group (AT-8)
 *		Ground Test Accelerator
 *		Accelerator Technology Division
 *		Los Alamos National Laboratory
 *
 *	Co-developed with
 *		The Controls and Computing Group
 *		Accelerator Systems Division
 *		Advanced Photon Source
 *		Argonne National Laboratory
 *
 * Modification Log:
 * -----------------
 *  .00 11-23-90 rac	initial version
 *  .01 06-18-91 rac	installed in SCCS
 *  .02 09-05-91 joh	updated for v5 vxWorks; included systime.h for utime.h
 *  .03 12-08-91 rac	added a comment for ezsFopenToFd
 *  .04 10-09-92 rac	use SO_REUSEADDR with socket
 *  .05 08-11-93 mrk	removed V5_vxWorks
 *  .06 05-04-94 pg	HPUX port changes.
 *
 * make options
 *	-DvxWorks	makes a version for VxWorks
 *	-DNDEBUG	don't compile assert() checking
 *      -DDEBUG         compile various debug code, including checks on
 *                      malloc'd memory
 */
/*+/mod***********************************************************************
* TITLE	ezsSockSubr.c - easy-to-use socket routines
*
* DESCRIPTION
*	This set of routines supports using Internet sockets for transferring
*	text, with a bias toward use by server/client programs.
*
*	All of these routines use the sockets as TCP/IP sockets, with the
*	sockets specified as SOCK_STREAM.
*
* QUICK REFERENCE
* #include <ezsSockSubr.h>
*     int  ezsCheckFdRead(          fd					    )
*     int  ezsCheckFpRead(          fp					    )
*     int  ezsConnectToServer(     >pServerSock,  portNum, hostName	    )
*     int  ezsCreateListenSocket(  >pListenSock,  portNum		    )
*     int  ezsFopenToFd(            pFp,          pFd			    )
*     int  ezsFreopenToFd(          fp,           pFd,     >pOldFd	    )
*     int  ezsListenExclusiveUse(   pClientSock,  pListenSock, pInUseFlag,
*                                            >clientName,  greetingMsg	    )
*    void  ezsSleep(                seconds,      usec			    )
*
* BUGS
* o	doesn't provide a way for client to distinguish between server output
*	to stdout and stderr, since if server redirects those streams to the
*	same socket, their individual identity is lost
* o	needs examples
*-***************************************************************************/

#include <genDefs.h>
#include <ezsSockSubr.h>
#ifdef vxWorks
#   include <vxWorks.h>
#   include <stdioLib.h>
#   include <ioLib.h>
#   include <taskLib.h>
#   include <types.h>
#   include <systime.h>
#   include <socket.h>
#   include <inetLib.h>
#   include <in.h>
#   include <ctype.h>
#else
#   include <stdio.h>
#   include <sys/types.h>
#   include <sys/time.h>
#   include <sys/socket.h>
#   include <netinet/in.h>
#   include <arpa/inet.h>
#   include <netdb.h>
#   include <ctype.h>
#endif

/*+/subr**********************************************************************
* NAME	ezsCheckFpRead - check fp to see if read is possible
*
* DESCRIPTION
*	Checks the specified stream to see if a read operation is possible.
*
*	ezsCheckFdRead(fd) performs a similar function for fd's.
*
* RETURNS
*	>0	if a read can be done
*	0	if a read can't presently be done
*
* BUGS
* o	due to restrictions in VxWorks 4.0.2, cannot be used for stdin,
*	stdout, or stderr
*
*-*/
int
ezsCheckFpRead(fp)
FILE	*fp;		/* I pointer to FILE to check for "input available" */
{
#ifdef vxWorks
    if (fp == stdin)		assertAlways(0);
    else if (fp == stdout)	assertAlways(0);
    else if (fp == stderr)	assertAlways(0);
#endif

    return(ezsCheckFdRead(fileno(fp)));
}
ezsCheckFdRead(fd)
int	fd;
{
    fd_set readbits, other;
    struct timeval timer;
    int ret;

    timer.tv_sec = 0;
    timer.tv_usec = 0;
    FD_ZERO(&readbits);
    FD_ZERO(&other);
    FD_SET(fd, &readbits);

    ret = select(fd+1, &readbits, &other, &other, &timer);
#ifdef vxWorks
    return ret;
#else
    if (FD_ISSET(fd, &readbits))
	return 1;
    return 0;
#endif
}

/*+/subr**********************************************************************
* NAME	ezsConnectToServer - attempt to connect via socket to server
*
* DESCRIPTION
*
* RETURNS
*	0, or
*	-1 if an error occurs, or
*	-2 if the host name can't be found or the server rejects the
*	    connection.  The message buffer will contain a relevant message.
*
* BUGS
* o	under VxWorks 4.0.2, if the server is on the same machine as the
*	client, then the connect will fail unless the machine's name is
*	in the host table
*
* SEE ALSO
*
* EXAMPLE
*
*-*/
int
ezsConnectToServer(pServerSock, portNum, hostName, message)
int	*pServerSock;	/* O ptr to socket connected to server */
int	portNum;	/* I port number of server */
char	*hostName;	/* I name (or Internet address) of host on which
				the server runs */
char	*message;	/* O message from server (dimension of 80 assumed) */
{
    struct sockaddr_in server;
    struct hostent *hp;
#ifndef vxWorks
    struct hostent *gethostbyname();
#endif
    int		i;
    int		optval=1;

    assert(pServerSock != NULL);
    assert(portNum > 0);
    assert(hostName != NULL);
    assert(message != NULL);

/*-----------------------------------------------------------------------------
*    set up to create the socket and connect it to the server
*----------------------------------------------------------------------------*/
    bzero(&server, sizeof(server));
    server.sin_family = AF_INET;
    server.sin_port = htons(portNum);

    if (isdigit(hostName[0]))
	server.sin_addr.s_addr = inet_addr(hostName);
    else {
#ifdef vxWorks
	if ((server.sin_addr.s_addr = hostGetByName(hostName)) == ERROR) {
	    sprintf(message, "host not in VxWorks host table: %s", hostName);
	    return -2;
	}
#else
	if ((hp = gethostbyname(hostName)) == NULL) {
	    sprintf(message, "host unknown: %s", hostName);
	    return -2;
	}
	bcopy(hp->h_addr, &server.sin_addr, hp->h_length);
#endif
    }

    *pServerSock = socket(AF_INET, SOCK_STREAM, 0);
    if (*pServerSock < 0)
	return -1;
    if (setsockopt(*pServerSock, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval)) != 0) {
	close(*pServerSock);
	*pServerSock = -1;
	return -1;
    }
    if (connect(*pServerSock, &server, sizeof(server)) < 0) {
	close(*pServerSock);
	*pServerSock = -1;
	return -1;
    }

/*-----------------------------------------------------------------------------
*    now, with connection completed to server, wait for the message from
*    server--the message will either identify the server, or else indicate
*    that the connection is rejected.
*----------------------------------------------------------------------------*/
    if ((i=read(*pServerSock, message, 79)) <= 0) {
	close(*pServerSock);
	*pServerSock = -1;
	sprintf(message, "connection rejected");
	return -2;
    }
    message[i] = '\0';
    if (strncmp(message, "***", 3) == 0) {
	close(*pServerSock);
	*pServerSock = -1;
	return -2;
    }

    return 0;
}

/*+/subr**********************************************************************
* NAME	ezsCreateListenSocket - create socket to listen for connections
*
* DESCRIPTION
*	Create a socket to be used by a server to listen for connections
*	by potential clients.  The socket is bound to the caller-specified
*	port number.
*
*	The port number specified, in conjunction with the name of the
*	host, constitutes a network "address" at which the server can be
*	reached.
*
*	Ideally, the port number and service name would be registered with
*	the system manager, who would place the information in the
*	/etc/services file.  If this is done, then getservbyname(3N) can be
*	used by a potential client to determine the proper port number to
*	use.  In actual practice, potential clients would "know" what port
*	number to use by means of an include file provided by the service
*	for compiling clients.
*
* RETURNS
*	0, or
*	-1 if an error occurs (errno will have error code)
*
* SEE ALSO
*
* EXAMPLE
*
*-*/
int
ezsCreateListenSocket(pListenSock, portNum)
int	*pListenSock;	/* O ptr to socket for listening for connects */
int	portNum;	/* I number of service's port */
{
    struct sockaddr_in server;	/* temporary Internet socket structure */

    assert(pListenSock != NULL);
    assert(portNum > 0);

    if ((*pListenSock = socket(AF_INET, SOCK_STREAM, 0)) < 0)
	return -1;

    bzero(&server, sizeof(server));
    server.sin_family = AF_INET;		/* internet */
    server.sin_addr.s_addr = INADDR_ANY;	/* anybody can connect */
    server.sin_port = htons(portNum);		/* server's port */
    if (bind(*pListenSock, (struct sockaddr *)&server, sizeof(server)) < 0) {
	close(*pListenSock);
	return -1;
    }

    if (listen(*pListenSock, 1) < 0)
	return -1;

    return 0;
}

/*+/subr**********************************************************************
* NAME	ezsFopenToFd - open a specified stream to an fd
*
* DESCRIPTION
*	Open a stream to an fd.  Typically, this is used to open a stream
*	to a socket.
*
*	This routine should not be used for stdin, stdout, or stderr.  For
*	these streams, ezsFreopenToFd() should be used.
*
* RETURNS
*	0, or
*	-1 if errors are encountered (errno has error code)
*
* BUGS
* o	the stream is always opened in "r+" (update) mode
*
* SEE ALSO
*	ezsFreopenToFd(), ezsFrestoreToOldFd()
*
* NOTES	
* 1.	For input streams in a client monitoring the output of a server,
*	it will often be useful to set the stream to unbuffered:
*		FILE	*myIn;
*		ezsFopenToFd(&myIn, socket);
*		setbuf(myIn, NULL);
*
*-*/
int
ezsFopenToFd(pFp, pFd)
FILE	**pFp;		/* O pointer to pointer to new FILE */
int	*pFd;		/* I pointer tofd to be used by file pointer */
{
    assert(pFp != NULL);
    assert(pFd != NULL);

    if ((*pFp=fdopen(*pFd, "r+")) == NULL)
	return -1;
    return 0;
}

/*+/subr**********************************************************************
* NAME	ezsFreopenToFd - reopen a specified stream to an fd
*
* DESCRIPTION
*	Reopens a stream (assumed to presently be open) to an fd.  Typically,
*	this is used in a server program to redirect stdin, stdout, or stderr
*	to use a socket.
*
*	Under VxWorks, this is a pseudo re-open, which applies only to
*	the current task.  If a "program" consists of several tasks, each
*	task must re-direct its I/O.  Typically, one task will supply a
*	non-NULL pOldFd argument, and other tasks will use NULL.
*
* RETURNS
*	0, or
*	-1 if errors are encountered (errno has error code)
*	-2 if illegal stream is used
*
* BUGS
* o	the only streams which are handled are stdin, stdout, and stderr
* o	stdout and stderr are line buffered under VxWorks, and output won't
*	get sent (even if fflush() is used, as of 4.0.2) until '\n' is sent.
*
* SEE ALSO
*	ezsFopenToFd(), ezsFrestoreToOldFd()
*
* EXAMPLE
*
*-*/
int
ezsFreopenToFd(fp, pFd, pOldFd)
FILE	*fp;		/* IO pointer to FILE, such as stdin or stdout */
int	*pFd;		/* I pointer to fd to be used by file pointer */
int	*pOldFd;	/* O NULL, or pointer to place to store fd
			     previously used by file pointer */
{
    int		fdNumber;

    assert(fp != NULL);
    assert(pFd != NULL);

    if (fp == stdin)		fdNumber = 0;
    else if (fp == stdout)	fdNumber = 1;
    else if (fp == stderr)	fdNumber = 2;
    else			return -2;

#ifdef vxWorks
    if (pOldFd != NULL)
	*pOldFd = ioTaskStdGet(0, fdNumber);
    ioTaskStdSet(0, fdNumber, *pFd);
#else
    if (pOldFd != NULL) {
	if ((*pOldFd = dup(fileno(fp))) < 0)
	    return -1;
    }
    if (dup2(*pFd, fileno(fp)) < 0)
	return -1;
#endif

    return 0;
}

/*+/subr**********************************************************************
* NAME	ezsListenExclusiveUse - listen for client connect; allow only 1
*
* DESCRIPTION
*	Checks to see if a client connect request is queued.  If so, the
*	action depends on whether a client is presently connected:
*
*	o   if no client is presently connected, then the connection is made:
*	    -   a socket is created to converse with the client,
*	    -   a greeting message is sent to the new client,
*	    -   the inUseFlag is set to 1, and
*	    -   the new socket is returned to the caller
*	o   if a client is presently connected, then the connection is
*	    refused, with a rejection message sent to the hopeful new client.
*	    The rejection message has the form "*** server inuse by host",
*	    where "host" is the name of the host from which the present client
*	    is connected.
*
*	This routine simply polls the listen socket.  It must be called
*	periodically.  Only one queued request is processed on each call.
*
* RETURNS
*	-1 if an error occurs
*
* BUGS
* o	text
*
* SEE ALSO
*
* EXAMPLE
*
*-*/
int
ezsListenExclusiveUse(pClientSock, pListenSock, pInUseFlag, name, greetMsg)
int	*pClientSock;	/* IO ptr to socket (to be) connected to client */
int	*pListenSock;	/* I socket for listening for connects */
int	*pInUseFlag;	/* I ptr to flag; 1 indicates server already engaged */
char	*name;		/* O ptr to buf[30] where client name will go */
char	*greetMsg;	/* I greeting message to sent to new client */
{
    int		i;
    static char	inUseText[80];
    int		connSock;	/* socket connected to potential client */
    struct sockaddr_in clientAddr;/* address of client socket */
    struct hostent *clientHost;
    char	*clientHostAddr;
    char	clientHostAddrText[30];
    char	clientBuf[80];

    if (ezsCheckFdRead(*pListenSock)) {
	i = sizeof(clientAddr);
	connSock = accept(*pListenSock, (struct sockaddr *)&clientAddr, &i);
	if (connSock < 0)
	    return -1;
	if (*pInUseFlag) {
/*-----------------------------------------------------------------------------
*	server is already engaged; send rejection message to hopeful client
*----------------------------------------------------------------------------*/
	    write(connSock, inUseText, strlen(inUseText));
	    close(connSock);
	}
	else {
/*-----------------------------------------------------------------------------
*	server available; consummate connection:
*	o   set inUseFlag to 1
*	o   build a rejection message to send if other clients attempt connect
*	o   send greeting message to new client
*----------------------------------------------------------------------------*/
#ifndef vxWorks
	    clientHost =
		    gethostbyaddr((char *)&clientAddr.sin_addr, i, AF_INET);
	    if (clientHost != NULL)
		sprintf(clientHostAddrText, "%s", clientHost->h_name);
	    else {
#endif
		clientHostAddr = inet_ntoa(clientAddr.sin_addr);
		sprintf(clientHostAddrText, "%s", clientHostAddr);
#ifndef vxWorks
	    }
#endif
	    (void)strcpy(name, clientHostAddrText);
	    sprintf(inUseText, "*** server in use by %s", clientHostAddrText);
	    write(connSock, greetMsg, strlen(greetMsg));
	    *pClientSock = connSock;
	    *pInUseFlag = 1;
	}
    }
    return 0;
}

/*+/subr**********************************************************************
* NAME	ezsSleep - sleep
*
* DESCRIPTION
*	The current task sleeps for the specified time.
*
*	This routine should not be used by programs implemented with the
*	UNIX lwp (light-weight process) library--taskSleep() in genTaskSubr.c
*	should be used, instead.
*
* RETURNS
*	void
*
* SEE ALSO
*	taskSleep()
*
*-*/
void
ezsSleep(seconds, usec)
int	seconds;	/* I number of seconds (added to usec) to sleep */
int	usec;		/* I number of micro-sec (added to sec) to sleep */
{
#ifndef vxWorks
    sleep((unsigned int)seconds);
/* MDA - usleep isn't POSIX 
    usleep((unsigned)(seconds*1000000 + usec));
*/
#else
    int		ticks;
    static int ticksPerSec=0;
    static int usecPerTick;

    if (ticksPerSec == 0) {
	ticksPerSec = sysClkRateGet();
	usecPerTick = 1000000 / ticksPerSec;
    }

    ticks = seconds*ticksPerSec + usec/usecPerTick + 1;
    taskDelay(ticks);
#endif
}
