/***********************************************************************/
/* Open Visualization Data Explorer                                    */
/* (C) Copyright IBM Corp. 1989,1999                                   */
/* ALL RIGHTS RESERVED                                                 */
/* This code licensed under the                                        */
/*    "IBM PUBLIC LICENSE - Open Visualization Data Explorer"          */
/***********************************************************************/

#include <dxconfig.h>


#include <stdio.h>
#include <ctype.h>
#include <string.h>
#include <dx/arch.h>
#if DXD_HAS_WINSOCKETS
/* here's where to put NT windows socket specific includes */
/* will need to fix other code as necessary */
#define	EADDRINUSE	WSAEADDRINUSE
#include <winsock.h>
#else
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#if DXD_SOCKET_UNIXDOMAIN_OK
#include <sys/un.h>
#endif
#ifndef DXD_HAS_WINSOCKETS
#include <netdb.h>
#endif
#if DXD_NEEDS_SYS_SELECT_H
#include <sys/select.h>
#endif
#endif
#ifdef DXD_WIN
#include <sys/timeb.h>
#else
#include <sys/time.h>
#endif
#include <errno.h>
#include <stdlib.h>

#define SOCK_QUEUE_LENGTH	1
#define SOCK_ACCEPT_TIMEOUT	60	/* Seconds */

/*
 * Parse the DXHOST environment variable and determine the host name and
 * socket port number to use for remote connections.  The format of the
 * varible is 'hostname[,portnumber]'.  The default port number (if one
 * is not given) is 1900.  This number is arbitrary, but should not collide
 * with anything in /etc/services.  In general, I believe that most numbers
 * over 1024 will work.
 */
_dxf_ExGetSocket(char *name, int *port)
{
    char *getenv();
    char *env;
    char *p;

    if (name)
	*name = '\0';
    *port = 1900;

    env = getenv("DXHOST");
    if (env == NULL)
	return(0);

    p = env;
    while (*p && *p != ',')
	p++;

    if (*p == ',')
    {
	if (name)
	{
	    p = env;
	    while (*p && *p != ',')
		*name++ = *p++;
	    *name = '\0';
	}
	p++;
        *port = atoi(p);
    }
    return(1);
}

/*
 * Open a socket port and wait for a client to connect.
 * This opens 2 sockets (except on the server), one internet domain, and
 * one unix domain.  It then selects on both sockets to see to which one the
 * ui connects, and returns the new connection, closing the one not
 * selected.
 */
_dxf_ExInitServer(int pport)
{
    int sock = -1;
    struct sockaddr_in server;
#if DXD_SOCKET_UNIXDOMAIN_OK
    int usock = -1;
    struct sockaddr_un userver;
    int oldUmask;
#endif
    struct linger sl;
    int length;
    ushort port;
    int fd;
    int sts;
    int oldPort;
    extern int errno;
    int tries;
    fd_set fds;
#if DXD_HAS_GETDTABLESIZE
    int width = getdtablesize();
#else
#ifdef DXD_HAS_WINSOCKETS
    int width = FD_SETSIZE;
#else
    int width = MAXFUPLIM;
#endif
#endif
    struct timeval to;

    port = pport;

retry:
    sock = socket(AF_INET, SOCK_STREAM, 0);
    if (sock < 0)
    {
	perror ("socket");
	fd = -1;
	goto error;
    }

    sl.l_onoff = 1;
    sl.l_linger = 0;
    setsockopt(sock, SOL_SOCKET, SO_LINGER, (char *)&sl, sizeof(sl));
#if defined(DXD_HAS_IBM_OS2_SOCKETS)  || defined(DXD_HAS_WINSOCKETS)
    SOCK_SETSOCKET(sock);
#endif

#if DXD_SOCKET_UNIXDOMAIN_OK
    usock = socket(AF_UNIX, SOCK_STREAM, 0);
    if (usock < 0)
    {
	perror ("socket");
	fd = -1;
	goto error;
    }

    setsockopt(usock, SOL_SOCKET, SO_LINGER, &sl, sizeof(sl));
#if defined(DXD_HAS_IBM_OS2_SOCKETS)  || defined(DXD_HAS_WINSOCKETS)
    SOCK_SETSOCKET(sock);
#endif
#endif

    server.sin_family = AF_INET;
    server.sin_addr.s_addr = INADDR_ANY;
    server.sin_port = htons(port);
    length = sizeof(struct sockaddr_in);

    /* Try to find a working port, and keep trying if you get EADDRINUSE.
     * If we get some other error, try a few times because we sometimes
     * get bad error numbers on the server.
     */
    tries = 0;
    while ((sts = bind(sock, (struct sockaddr *)&server, length)) < 0 && 
	    (errno == EADDRINUSE || tries++ < 5))
    {
	oldPort = port;
	server.sin_port = htons(++port);
	if (ntohs(server.sin_port) < oldPort)
	    break;
    }
    if (sts < 0)
    {
	perror ("bind");
	fd = -1;
	goto error;
    }

    if (getsockname(sock, (struct sockaddr *)&server, &length) < 0)
    {
	perror ("getsockname");
	fd = -1;
	goto error;
    }

#if DXD_SOCKET_UNIXDOMAIN_OK
    userver.sun_family = AF_UNIX;
    sprintf(userver.sun_path, "/tmp/.DX-unix/DX%d", port);
    length = sizeof (userver) - sizeof(userver.sun_path) +
	     strlen (userver.sun_path);

    oldUmask = umask(0);
    mkdir("/tmp/.DX-unix", 0777);
    chmod("/tmp/.DX-unix", 0777);
    umask(oldUmask);

    unlink(userver.sun_path);


    /* Try to find a working port, and keep trying if you get EADDRINUSE.
     * If we get some other error, try a few times because we sometimes
     * get bad error numbers on the server.
     */
    if ((sts = bind(usock, (struct sockaddr *)&userver, length)) < 0 && 
	    errno == EADDRINUSE)
    {
	oldPort = port;
	server.sin_port = htons(++port);
	close (sock);
	close (usock);
	sock = -1;
	usock = -1;
	if (ntohs(server.sin_port) > oldPort)
	    goto retry;
    }
    if (sts < 0)
    {
	perror ("bind");
	fd = -1;
	goto error;
    }
#endif

    if (listen(sock, SOCK_QUEUE_LENGTH) < 0)
    {
	perror ("listen");
	fd = -1;
	goto error;
    }
#if DXD_SOCKET_UNIXDOMAIN_OK
    if (listen(usock, SOCK_QUEUE_LENGTH) < 0)
    {
	perror ("listen");
	fd = -1;
	goto error;
    }
#endif

    printf ("port = %d\n", ntohs(server.sin_port)); 
    fflush(stdout);

    /* Wait (in select) for someone to connect.
     * if we get through the error checks, someone must want to connect to 
     * our port, accept him/her.  Otherwize, block in accept until someone
     * connects.  If stdin is not a terminal, timeout after SOCK_ACCEPT_TIMEOUT
     * seconds.
     */
#ifndef DXD_HAS_LIBIOP 
    FD_ZERO(&fds);
    FD_SET(sock, &fds);
#if DXD_SOCKET_UNIXDOMAIN_OK
    FD_SET(usock, &fds);
#endif
    if (!isatty(0)) {
	to.tv_sec = SOCK_ACCEPT_TIMEOUT;
	to.tv_usec = 0;
	sts = select(width, (SelectPtr) &fds, NULL, NULL, &to);
    }
    else
    {
	sts = select(width, (SelectPtr) &fds, NULL, NULL, NULL);
    }
    if (sts < 0) {
	perror("select");
	fd = -1;
	goto error;
    }
    else if (sts == 0) {
	fprintf (stderr, "connection timed out\n");
	fd = -1;
	goto error;
    }
#else
    {
	double beforeSelect;
	double SVS_double_time();
	int    tty = isatty(0);

	beforeSelect = SVS_double_time();
	do {
	    FD_ZERO(&fds);
	    FD_SET(sock, &fds);
	    to.tv_sec = 0;
	    to.tv_usec = 0;
	    sts = select(width, (SelectPtr) &fds, NULL, NULL, &to);
	    if (sts < 0) {
		perror("select");
		fd = -1;
		goto error;
	    }
	} while (sts == 0 && 
		 (tty ||
		  SVS_double_time() - beforeSelect < SOCK_ACCEPT_TIMEOUT));
	if (sts == 0) {
	    fprintf (stderr, "connection timed out\n");
	    fd = -1;
	    goto error;
	}
    }
#endif

#if DXD_SOCKET_UNIXDOMAIN_OK
    if (FD_ISSET(sock, &fds))
    {
#endif
	if ((fd = accept(sock, (struct sockaddr *)&server, &length)) < 0)
	{
	    perror ("accept");
	    fd = -1;
	    goto error;
	}
#if defined(DXD_HAS_IBM_OS2_SOCKETS)  || defined(DXD_HAS_WINSOCKETS)
        SOCK_SETSOCKET(fd);
#endif

#if DXD_SOCKET_UNIXDOMAIN_OK
    }
    else
    {
	if ((fd = accept(usock, (struct sockaddr *)&userver, &length)) < 0)
	{
	    perror ("accept");
	    fd = -1;
	    goto error;
	}
#if defined(DXD_HAS_IBM_OS2_SOCKETS)  || defined(DXD_HAS_WINSOCKETS)
        SOCK_SETSOCKET(fd);
#endif
    }
#endif

    printf ("server: accepted connection from client\n");

error:
#if DXD_SOCKET_UNIXDOMAIN_OK
    if (userver.sun_path[0] != '\0')
	unlink (userver.sun_path);
    if (usock >= 0)
	close (usock);
#endif
    if (sock >= 0)
	close (sock);
    return (fd);
}

/*
 * Initiate a connection to a server.
 */
static
init_client(char *host, int pport)
{
    int sock;
    struct sockaddr_in server;
#if DXD_SOCKET_UNIXDOMAIN_OK
    struct sockaddr_un userver;
#endif
    struct hostent *hp;
    int length;
#if DXD_HAS_GETHOSTBYNAME
    struct hostent *gethostbyname();
#endif
    u_short port = pport;

#if DXD_SOCKET_UNIXDOMAIN_OK
    if (strcmp(host, "unix") == 0)
    {
	memset((char *)&userver, 0, sizeof(userver));
	userver.sun_family = AF_INET;
	sprintf(userver.sun_path, "/tmp/.DX-unix/DX%d", port);
	length = sizeof (userver) - sizeof(userver.sun_path) +
		 strlen (userver.sun_path);

	sock = socket(AF_UNIX, SOCK_STREAM, 0);
	if (sock < 0)
	    return(-1);
	
	if (connect(sock, (struct sockaddr *) &userver, length) < 0)
	{
	    perror("connect");
	    close(sock);
	    return(-1);
	}
#if defined(DXD_HAS_IBM_OS2_SOCKETS)  || defined(DXD_HAS_WINSOCKETS)
        SOCK_SETSOCKET(sock);
#endif
    }
    else
#endif
    {
	memset((char *)&server, 0, sizeof(server));
	server.sin_family = AF_INET;
	server.sin_port = htons(port);

	/* get host address */
	if (isdigit(host[0]))
	    server.sin_addr.s_addr = inet_addr(host);
	else
	{
	    hp = gethostbyname(host);
	    if (hp == NULL)
		return(-1);
	    memcpy(&server.sin_addr, hp->h_addr, hp->h_length);
	}

	sock = socket(AF_INET, SOCK_STREAM, 0);
	if (sock < 0)
	    return(-1);
	
	if (connect(sock, (struct sockaddr*)&server, sizeof(server)) < 0)
	{
	    close(sock);
	    return(-1);
	}
#if defined(DXD_HAS_IBM_OS2_SOCKETS)  || defined(DXD_HAS_WINSOCKETS)
        SOCK_SETSOCKET(sock);
#endif
    }

    printf ("client:  connected to server\n");

    return(sock);
}


#ifdef TEST
main(int argc, char *argv[])
{
    int fd;
    char buffer[1000];

    if (argc < 3)
    {
	fd = _dxf_ExInitServer(1959);

	write(fd, "good bye\n", 9);
	buffer[read(fd, buffer, 1000)] = '\0';
	fprintf(stdout, buffer);

	close (fd);
    }
    else
    {
	fd = init_client(argv[1], atol(argv[2]));

	buffer[read(fd, buffer, 1000)] = '\0';
	fprintf(stdout, buffer);

	printf("> "); gets(buffer);

	write(fd, "hello\n", 6);


	close (fd);
    }
}
#endif
