/* BUGS:
 * 1. If the ungetc capability is used, the "dp_isready" command will fail
 *    because it does not check for stuff that was put back.
 * 2. dp_filehandler will still be supported for backwards compatibility.
 *    However, to have portability to Windows, a dp_sockethandler command
 *    has been created.  /


/*
 * dpnetwork.c --
 *
 *	This file implements most of the network connection management
 *	functions of Tcl-DP.  The following comments are inherited from
 *	the progenitors of Tcl-DP.
 *
 * 	This file contains a simple Tcl "dp_connect" command
 *	that returns an standard Tcl File descriptor (as would
 *	be returned by Tcl_OpenCmd).  This part of the file was written by
 *	Pekka Nikander <pnr@innopoli.ajk.tele.fi>
 *
 *	Tim MacKenzie <tym@dibbler.cs.monash.edu.au> extended it to
 *	create servers, accept connections, shutdown parts of full
 *	duplex connections and handle UNIX domain sockets.
 *
 *	Brian Smith <bsmith@cs.berkeley.edu> further modified it to
 *	add support for various send/receive primitives, and connectionless
 *	sockets.
 *
 * Copyright 1992 Telecom Finland
 *
 * Permission to use, copy, modify, and distribute this
 * software and its documentation for any purpose and without
 * fee is hereby granted, provided that this copyright
 * notice appears in all copies.  Telecom Finland
 * makes no representations about the suitability of this
 * software for any purpose.  It is provided "as is" without
 * express or implied warranty.
 *
 * Copyright (c) 1993 The Regents of the University of California.
 * All rights reserved.
 * 
 * Permission to use, copy, modify, and distribute this software and its
 * documentation for any purpose, without fee, and without written agreement is
 * hereby granted, provided that the above copyright notice and the following
 * two paragraphs appear in all copies of this software.
 * 
 * IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR
 * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT
 * OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF THE UNIVERSITY OF
 * CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 * 
 * THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
 * AND FITNESS FOR A PARTICULAR PURPOSE.  THE SOFTWARE PROVIDED HEREUNDER IS
 * ON AN "AS IS" BASIS, AND THE UNIVERSITY OF CALIFORNIA HAS NO OBLIGATION TO
 * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
 */
#ifdef _WINDOWS
# include <windows.h>
# include <io.h>
#endif

#include <stdio.h>
#include <assert.h>
#include <ctype.h>
#include "tk.h"
#include "dpInt.h"
#include <fcntl.h>

#ifndef _WINDOWS
# include <netinet/in.h>
# include <arpa/inet.h>
# include <netdb.h>
# ifdef UNIX_SOCKET
#  include <sys/un.h>
# endif
# include <sys/param.h>
# include <sys/ioctl.h>
# ifndef NO_WRITEV
#  include <sys/uio.h>
# endif
#endif

#ifndef _WINDOWS
extern int errno;
# if HAVE_ERRNO_H
#  include <errno.h>
# else
#  define EAGAIN 11
#  define EINVAL 22
#  define EWOULDBLOCK 35
#  define EPIPE 32
# endif
#endif
/*
 * This is a "magic number" prepended to the beginning of the packet
 * It's used to help resync the packet machanism in the event of errors.
 */
#define PACKET_MAGIC	0x6feeddcc

static SOCKET  Tdp_inet_connect	_ANSI_ARGS_((char *host, int port,
					     int server, 
					     int udp,
					     int reuseAddr,
					     int lingerTime));
#ifdef UNIX_SOCKET
static SOCKET  Tdp_unix_connect	_ANSI_ARGS_((char *path, 
					     int server,
                                             int udp));
#endif

static void Tdp_HandleEvent	_ANSI_ARGS_((ClientData clientData, 
					     int mask));

static void Tdp_FreeHandler	_ANSI_ARGS_((ClientData clientData));

/*
 * For every file descriptor handler created, a structure of 
 * the following type is maintained.
 */
struct DP_FileHandle {
    Tcl_Interp *interp;
    DP_Socket *sockPtr;	/* Open file descriptor (file or socket) */
    int mask;			/* Mask of file descriptor conditions */
    char *rCmd;			/* Command to call on readable condition */
    char *wCmd;			/* Command to call on writable condition */
    char *eCmd;			/* Command to call on exception condition */
    char *sockId;		/* Represents sockPtr*/

};

static DP_FileHandle *handlers[MAX_OPEN_FILES];	/* Indexed by fd. */

/*
 * We keep around a single, large buffer which we can receive data into.
 * The size of this buffer is the maximum size of any of the receive buffers
 * on any open sockets, stored in bufferSize.
 */
static char *buffer;				/* Buffer for receiving data */
static int bufferSize;				/* Size of buffer */

/*
 * For TCP, it's possible to get a line in pieces.  In case everything we
 * want isn't there (e.g., in dp_packetReceive), we need a place to store
 * partial results when we're in non-blocking mode or peeking at the data.
 * The partial buffers below are created dynamically to store incomplete
 * data in these cases.
 */
struct DP_PartialRead {
    char *buffer;		/* Buffer of characters */
    int bufSize;		/* Size of buffer */
    int offset;			/* Offset of current character within the buffer */
    struct DP_PartialRead *next;/* Next buffer in chain */
};

/* static DP_PartialRead *partial[MAX_OPEN_FILES]; */

/*
 * The next array stores state about each socket.  The optFlags is an or'd
 * combination of the following state:
 *	FD_BLOCKING	-- Blocking I/O mode is on
 *	FD_GOTPARTIAL	-- Have received a partial message (only applicable for TCP)
 *	FD_TCP		-- Is a TCP/IP line (otherwise udp)
 *	FD_UNIX		-- Is a unix domain sokcet (otherwise internet)
 *	FD_SERVER	-- Was created with -server
 *	FD_AUTO_CLOSE	-- Socket should auto close on error.
 */
/* static unsigned char optFlags[MAX_OPEN_FILES]; */
#define	FD_BLOCKING	1
#define	FD_GOTPARTIAL	2
#define	FD_TCP		4
#define	FD_UNIX		8
#define	FD_SERVER	16
#define	FD_AUTO_CLOSE	32

/*
 *--------------------------------------------------------------
 *
 * Tdp_SetBlocking --
 *
 *	Make the socket blocking (or non-blocking) as specified,
 *	and be efficient about it (i.e., cache the current state).
 *
 * Results:
 *	Standard TCL result
 *
 * Side effects:
 *	The socket whose file descriptor is passed in will be either
 *	blocking or not, as specified, after this call.
 *
 *--------------------------------------------------------------
 */
static int
Tdp_SetBlocking (interp, sockPtr, block)
    Tcl_Interp *interp;
    DP_Socket *sockPtr;
    int block;			/* 1 if we should block from now on,
				   0 if not */
{
#ifdef _WINDOWS
    if (block) {
	if ((sockPtr->optFlags & FD_BLOCKING) == 0) {
	    /* Set blocking mode */
	    u_long val = 0;
	    sockPtr->optFlags |= FD_BLOCKING;
	    ioctlsocket(sockPtr->socket, FIONBIO, (u_long *) &val);
	}
    } else {
	if (sockPtr->optFlags & FD_BLOCKING) {
	    /* Set non-blocking mode */
	    u_long val = 1;
	    ioctlsocket(sockPtr->socket, FIONBIO, (u_long *) &val);
	    sockPtr->optFlags &= ~FD_BLOCKING;
	}
    }
#else
    int flags;

    if (block) {
	if ((sockPtr->optFlags & FD_BLOCKING) == 0) {
	    /* Set blocking mode */
	    flags = fcntl (sockPtr->socket, F_GETFL, 0);
	    fcntl (sockPtr->socket, F_SETFL, flags & ~O_NDELAY);
	    sockPtr->optFlags |= FD_BLOCKING;
	}
    } else {
	if (sockPtr->optFlags & FD_BLOCKING) {
	    /* Set non-blocking mode */
	    flags = fcntl (sockPtr->socket, F_GETFL, 0);
	    fcntl (sockPtr->socket, F_SETFL, flags | O_NDELAY);
	    sockPtr->optFlags &= ~FD_BLOCKING;
	}
    }
#endif

    return TCL_OK;
}


/*
 *--------------------------------------------------------------
 *
 * Tdp_GetBufferSize --
 *
 *	Get the size of the receive buffer on a socket.
 *
 * Results:
 *	The size of the receive buffer of the specified socket, in bytes,
 *	or -1 on error.
 *
 * Side effects:
 *	None.
 *
 *--------------------------------------------------------------
 */
static int
Tdp_GetBufferSize(sockPtr)
    DP_Socket *sockPtr;
{
    int optlen, optval, result;

    optlen = sizeof(int);
    result = getsockopt(sockPtr->socket, SOL_SOCKET, SO_RCVBUF,
			(char *)&optval, &optlen);
    if (result == -1) {
	return -1;
    } else {
	return optval;
    }
}

/*
 *--------------------------------------------------------------
 *
 * Tdp_AllocateBuffer --
 *
 *	This command is called to allocate (or reallocate) the global
 *	receive buffer when the file descriptor passed in is created or
 *	modified.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	The global variable "buffer" is (re)allocated
 *
 *--------------------------------------------------------------
 */
static void
Tdp_AllocateBuffer (sockPtr)
    DP_Socket *sockPtr;
{
    /*
     * Get the size of the send/receive buffer, and make sure the buffer
     * we have is big enough to receive the largest possible message.
     */
    if (buffer == NULL) {
	bufferSize = Tdp_GetBufferSize(sockPtr) + 32;
	buffer = ckalloc(bufferSize);
    } else if (Tdp_GetBufferSize(sockPtr) > bufferSize) {
	bufferSize = Tdp_GetBufferSize(sockPtr) + 32;
	buffer = ckrealloc(buffer, bufferSize);
    }
}

/*
 *--------------------------------------------------------------
 *
 * Tdp_SocketIsReady --
 *
 *      This function determines if a file descriptor is readable
 *	or writeable.
 *
 * Results:
 *	An or'd combination of TCL_FILE_READABLE and TCL_FILE_WRITABLE
 *
 * Side effects:
 *	None.
 *
 *--------------------------------------------------------------
 */
int
Tdp_SocketIsReady(sockPtr)
    DP_Socket *sockPtr;
{
    fd_set readFdset;
    fd_set writeFdset;
    struct timeval tv;
    int rv;
    SOCKET socket;

    socket = sockPtr->socket;

    FD_ZERO(&readFdset);
    FD_SET (socket, &readFdset);
    FD_ZERO(&writeFdset);
    FD_SET (socket, &writeFdset);

    tv.tv_sec = 0;
    tv.tv_usec = 0;

    select (sockPtr->sockId, &readFdset, &writeFdset, (SELECT_MASK *) NULL, &tv);
    if (FD_ISSET(socket, &readFdset)) {
	rv = TCL_FILE_READABLE;
    } else {
	rv = 0;
    }
    if (FD_ISSET(socket, &writeFdset)) {
	rv |= TCL_FILE_WRITABLE;
    }
    return rv;
}

/*
 *--------------------------------------------------------------
 *
 * Tdp_FreeReadBuffer --
 *
 *	This function is called to free up all the memory associated
 *	with a file once the file is closed.
 *
 * Results:
 *	None
 *
 * Side effects:
 *	Any data buffered locally will be lost.
 *
 *--------------------------------------------------------------
 */
static void
Tdp_FreeReadBuffer(sockPtr)
    DP_Socket *sockPtr;
{
    DP_PartialRead *readList;

    while (sockPtr->partial != NULL) {
	readList = sockPtr->partial;
	sockPtr->partial = readList->next;
	ckfree (readList->buffer);
	ckfree ((char *) readList);
    }
}

/*
 *--------------------------------------------------------------
 *
 * Tdp_Unread --
 *
 *	This function puts data back into the read chain on a
 *	file descriptor.  It's basically an extended "ungetc".
 *
 * Results:
 *	None
 *
 * Side effects:
 *	Subsequent calls to Tdp_Read on the socket will get this data.
 *
 *--------------------------------------------------------------
 */
static void
Tdp_Unread (sockPtr, buffer, numBytes, copy)
    DP_Socket *sockPtr;
    char *buffer;               /* Data to unget */
    int numBytes;               /* Number of bytes to unget */
    int copy;			/* Should we copy the data, or use this buffer? */
{
    DP_PartialRead *new;

    if (numBytes == 0) return;
    new = (DP_PartialRead *)ckalloc (sizeof(DP_PartialRead));
    if (copy) {
	new->buffer = ckalloc (numBytes);
	memcpy (new->buffer, buffer, numBytes);
    } else {
	new->buffer = buffer;
    }
    new->bufSize = numBytes;
    new->offset = 0;
    new->next = sockPtr->partial;
    sockPtr->partial = new;
}

/*
 *--------------------------------------------------------------
 *
 * Tdp_BlockSocket --
 *
 *	This function is needed under Windows NT to get proper
 *	blocking behavior before with calls to recv().  This
 *	is because the call to WSAAsyncSelect() in tkEvent.c
 *	causes the socket to become non-blocking.  dpsh.exe does
 *	not have this problem because it uses an event loop that
 *	does not need to call WSAAsyncSelect().  The call to
 *	select() will only occur if the socket is a blocking socket.
 *	if the socket is not blocking, this function just returns.
 *
 * Results: None
 *
 * Side Effects:
 *	This function will return when an event occurs on the
 *	socket
 *
 *--------------------------------------------------------------
 */
static __inline void
Tdp_BlockSocket(sockPtr, mask)
    DP_Socket *sockPtr;
    int mask;
{
#ifdef _WINDOWS
    /* This next piece of code gets around a problem that occurs with
     * WSAAsyncSelect(): the socket becomes non-blocking when this call
     * is used.  Wait for select() to tell us something is available
     */
    if (sockPtr->optFlags & FD_BLOCKING) {
	fd_set ready[3];
	fd_set *readPtr, *writePtr, *exceptPtr;

	FD_ZERO(&ready[0]);
	FD_ZERO(&ready[1]);
	FD_ZERO(&ready[2]);

	if (mask & TK_READABLE) {
	    FD_SET(sockPtr->socket, &ready[0]);
	    readPtr = &ready[0];
	} else {
	    readPtr = NULL;
	}

	if (mask & TK_WRITABLE) {
	    FD_SET(sockPtr->socket, &ready[1]);
	    writePtr = &ready[1];
	} else {
	    writePtr = NULL;
	}

	if (mask & TK_WRITABLE) {
	    FD_SET(sockPtr->socket, &ready[2]);
	    exceptPtr = &ready[2];
	} else {
	    exceptPtr = NULL;
	}

	select(1, readPtr, writePtr, exceptPtr, NULL);
    }
#endif /* ifdef _WINDOWS */
}
    
/*
 *--------------------------------------------------------------
 *
 * Tdp_Read --
 *
 *	This function implements a "recv"-like command, but
 *	buffers partial reads.  The semantics are the same as
 *	with recv.
 *
 * Results:
 *	Number of bytes read, or -1 on error (with errno set).
 *
 * Side effects:
 *	All available data is read from the file descriptor.
 *
 *--------------------------------------------------------------
 */
static int
Tdp_Read (interp, sockPtr, buffer, numReq, flags)
    Tcl_Interp *interp;
    DP_Socket *sockPtr;	/* File descriptor to read from */
    char *buffer;		/* Place to put the data */
    int numReq;			/* Number of bytes to get */
    int flags;			/* Flags for receive */
{
    int peek;
    DP_PartialRead *readList;
    DP_PartialRead *tmp;
    int numRead;
    int numToCopy;

    readList = sockPtr->partial;

    /*
     * If there's no data left over from a previous read, then just do a recv
     * This is the common case.
     */
    if (readList == NULL) {
	Tdp_BlockSocket(sockPtr, TK_READABLE|TK_EXCEPTION);

	numRead = recv(sockPtr->socket, buffer, numReq, flags);
	return numRead;
    }

    /*
     * There's data left over from a previous read.  Yank it in and
     * only call recv() if we didn't get enough data (this keeps the fd
     * readable if they only request as much data as is in the buffers).
     */
    peek = flags & MSG_PEEK;
    numRead = 0;
    while ((readList != NULL) && (numRead < numReq)) {
	numToCopy = readList->bufSize - readList->offset;
	if (numToCopy + numRead > numReq) {
	    numToCopy = numReq - numRead;
	}
	memcpy (buffer+numRead, readList->buffer+readList->offset, numToCopy);

	/*
	 * Consume the data if we're not peeking at it
	 */
	tmp = readList;
	readList = readList->next;
	if (!peek) {
	    tmp->offset += numToCopy;
	    if (tmp->offset == tmp->bufSize) {
		ckfree (tmp->buffer);
		ckfree ((char *) tmp);
		sockPtr->partial = readList;
	    }
	}
	numRead += numToCopy;
    }

    /*
     * Only call recv if we reached the end of previously read data and they
     * didn't get enough and the socket has data to be consumed.
     */
    if ((numRead < numReq) &&
	(Tdp_SocketIsReady(sockPtr) & TCL_FILE_READABLE))
    {
	Tdp_BlockSocket(sockPtr, TK_READABLE|TK_EXCEPTION);
	numToCopy = numReq - numRead;
	numRead += recv(sockPtr->socket, buffer+numRead, numToCopy, flags);
    }

    return numRead;
}

/*
 *------------------------------------------------------------------
 *
 * Tdp_GetPort --
 *
 *      Converts a string representing a service name or number to an integer.
 *
 * Results:
 *	A standard Tcl result.
 *
 * Side effects:
 *	None.
 *
 *------------------------------------------------------------------
 */

 /* ARGSUSED */
int
Tdp_GetPort(interp, portstr, proto, portp)
    Tcl_Interp *interp;
    char *portstr, *proto;
    int *portp;
{
    int ret = TCL_ERROR;
    char *cp;
    
    for (cp = portstr; *cp != '\0'; cp++) {
	if (!isascii(*cp) || !isdigit(*cp))
	    break;
    }
    if (*cp != '\0') {
	/* string has a non-digit, must be a service name */
	struct servent *sp = getservbyname(portstr, proto);

	if (sp != NULL) {
	    *portp = htons(sp->s_port);
	    ret = TCL_OK;
	}
    } else {
	ret = Tcl_GetInt(interp, portstr, portp);
    }
    return ret;
}

/*
 *------------------------------------------------------------------
 *
 * Tdp_ConnectCmd --
 *
 *      This procedure is the C interface to the "dp_connect"
 *      command. See the user documentation for a description.
 *
 * Results:
 *	A standard Tcl result.
 *
 * Side effects:
 *	An open socket connection.
 *
 *------------------------------------------------------------------
 */

 /* ARGSUSED */
int
Tdp_ConnectCmd(notUsed, interp, argc, argv)
    ClientData notUsed;
    Tcl_Interp *interp;
    int argc;
    char **argv;
{
    SOCKET socket;		/* Open socket */
    DP_Socket *sockPtr;
    int port;			/* User specified port number */
    char *host;			/* Hostname (inet) */
#ifdef UNIX_SOCKET
    Tcl_DString buffer;		/* Buffer for tilde substitution */
    char *pathname;		/* Pathname (unix) */
    int unixSocket;		/* Unix domain socket? */
#endif
    int udp;			/* UDP protocol? */
    char *protoname = "tcp";	/* "udp" or "tcp" */
    int server;			/* Set up listening socket? */
    char tmp[256];
    int lingerTime;		/* For linger socket option */
    int reuseAddr;		/* Allow local reuse of TCP addresses */
    int err;

    host = NULL;
    udp = 0;
    server = 0;
    lingerTime = 0;
    reuseAddr = 0;

#ifdef UNIX_SOCKET
    unixSocket = 0;
    pathname = NULL;
    Tcl_DStringInit(&buffer);
#endif

    if (argc < 2) {
error:
	Tcl_SetResult (interp, argv[0], TCL_VOLATILE);
	Tcl_AppendResult(interp, ": should be one of the forms:\n",
			 (char *) NULL);
	Tcl_AppendResult(interp,
		     " \"dp_connect -server port ?-linger? ?-reuseAddr\"\n",
			 (char *) NULL);
	Tcl_AppendResult(interp, " \"dp_connect host port\"\n",
			 (char *) NULL);
	Tcl_AppendResult(interp, " \"dp_connect -udp ?port?\"\n",
			 (char *) NULL);
#ifdef UNIX_SOCKET
	Tcl_AppendResult(interp, " \"dp_connect -server path\"\n",
			 (char *) NULL);
	Tcl_AppendResult(interp, " or \"dp_connect path\"\n",
			 (char *) NULL);
#endif
	return TCL_ERROR;
    }

    /*
     * Break into one of three catergories:
     *	udp sockets,
     *	server setup
     *	client setup
     */
    if (strcmp (argv[1], "-udp") == 0) {
	udp = 1;
	protoname = "udp";
    } else if (strcmp (argv[1], "-server") == 0) {
	server = 1;
    }

    if (udp) {
	/*
	 * Must be "dp_connect -udp ?port?"
	 */
	host = "";	/* Allow packets from any source */
	if (argc == 3) {
	    if (Tdp_GetPort(interp, argv[2], protoname, &port) != TCL_OK) {
		return TCL_ERROR;
	    }
	} else if (argc == 2) {
	    port = 0;
	} else {
	    goto error;
	}
    } else if (server) {
	/*
	 * Must be either "dp_connect -server port ?-linger? ?-reuseAddr?"
	 * or "dp_connect -server path"
	 */
	if (argc < 3) {
	    goto error;
	}
	host = "";      /* Allow packets from any source */
	if (Tdp_GetPort(interp, argv[2], protoname, &port) != TCL_OK) {
#ifdef UNIX_SOCKET
	    if (argc != 3) {
		goto error;
	    }
	    pathname = Tcl_TildeSubst(interp, argv[2], &buffer);
	    if (pathname == NULL) {
		return TCL_ERROR;
	    }
	    unixSocket = 1;
#else
	    goto error;
#endif
	} else if (argc > 3) {
	    int arg;
	    for (arg = 3; arg < argc; arg++) {
		/* This is a tcp connection */
		if (strcmp (argv[arg], "-linger") == 0) {
		    lingerTime = 1;
		} else if (strcmp(argv[arg], "-reuseAddr") == 0) {
		    reuseAddr = 1;
		} else {
		    goto error;
		}
	    }
	    
	}
    } else {
	/*
	 * Client setup. Must be one of:
	 *	"dp_connect host port" or
	 *	"dp_connect path"
	 */
	if (argc == 3) {
	    host = argv[1];
	    if (Tdp_GetPort(interp, argv[2], protoname, &port) != TCL_OK) {
		return TCL_ERROR;
	    }
#ifdef UNIX_SOCKET
	} else if (argc == 2) {
	    pathname = Tcl_TildeSubst(interp, argv[1], &buffer);
	    if (pathname == NULL) {
		return TCL_ERROR;
	    }
	    unixSocket = 1;
#endif
	} else {
	    goto error;
	}

    }

    /*
     * Create the connection
     */
#ifdef UNIX_SOCKET
    if (unixSocket) {
	socket = Tdp_unix_connect(pathname, server, udp);
    } else
#endif
    {
	socket = Tdp_inet_connect(host, port, server, udp, reuseAddr, lingerTime);
    }

    if (socket == INVALID_SOCKET) {
	/* Tell them why it fell apart */
	err = WSAGetLastError();
#ifdef UNIX_SOCKET
	if (unixSocket) {
	    if (server) {
		Tcl_AppendResult (interp, 
			"Couldn't setup listening socket with path \"",
			pathname, "\": ", Tdp_WSAError(interp, err),
			(char *)NULL);
	    } else {
		Tcl_AppendResult (interp, 
			"Couldn't connect to \"", pathname, "\": ",
			Tdp_WSAError(interp, err), (char *)NULL);
	    }
	    Tcl_DStringFree(&buffer);
	} else
#endif
	if (server) {
	    if (port == 0) {
		Tcl_AppendResult (interp,
			"Couldn't setup listening socket on any port: ",
			Tdp_WSAError(interp, err), (char *)NULL);
	    } else {
		sprintf (tmp, "%d", port);
		Tcl_AppendResult (interp,
			"Couldn't setup listening socket on port ", tmp,
			": ", Tdp_WSAError(interp, err), (char *)NULL);
	    }
	} else if (udp) {
	    sprintf (tmp, "%d", port);
	    Tcl_AppendResult (interp,
		    "Couldn't open udp socket ", tmp, " : ",
		    Tdp_WSAError(interp, err), (char *)NULL);
	} else {
	    sprintf (tmp, "%d", port);
	    Tcl_AppendResult (interp,
		    "Couldn't open connection to ", host, ":", tmp, " : ",
		    Tdp_WSAError(interp, err), (char *)NULL);
	}
	return TCL_ERROR;
    }

#ifdef UNIX_SOCKET
	if (unixSocket) {
	    Tcl_DStringFree(&buffer);
	}
#endif

    if (Tdp_EnterSocket(interp, socket, TCL_FILE_READABLE|TCL_FILE_WRITABLE,
			&sockPtr) != TCL_OK)
    {
	return TCL_ERROR;
    }

    /*
     * Clear up any leftover data that might not have been cleaned
     * up, just in case.
     */
    Tdp_FreeReadBuffer (sockPtr);

#ifdef UNIX_SOCKET
    if (!unixSocket)
#endif
    {
        struct sockaddr_in sockaddr;
        int res, len;
	  
        /* Find the local port we're using for the connection. */
	  
        len = sizeof (sockaddr);
        res = getsockname (socket, (struct sockaddr *) &sockaddr, &len);

        if (res != 0) {
	    err = WSAGetLastError();
	    sprintf (tmp, "%s %d", interp->result, err);
        } else  {
	    sprintf(tmp, "%s %d", interp->result, ntohs(sockaddr.sin_port));
	}
	Tcl_SetResult (interp, tmp, TCL_VOLATILE);
    }

    Tdp_AllocateBuffer (sockPtr);
    if (udp) {
	sockPtr->optFlags = FD_BLOCKING | FD_AUTO_CLOSE;
    } else {
	sockPtr->optFlags = FD_TCP | FD_BLOCKING | FD_AUTO_CLOSE;
    }
#ifdef UNIX_SOCKET
    if (unixSocket) {
	sockPtr->optFlags |= FD_UNIX;
    }
#endif
    if (server) {
	sockPtr->optFlags |= FD_SERVER;
    }
    return TCL_OK;
}

/*
 *--------------------------------------------------------------
 *
 * Tdp_SocketOptionCmd --
 *
 *	This function implements the tcl "dp_socketOption" command.
 *	See the user documentation for details on what it does.
 *
 * Results:
 *	A standard tcl result.
 *
 * Side effects:
 *	The system level properties of the socket may be changed
 *	by this call.
 *
 *--------------------------------------------------------------
 */
            /* ARGSUSED */
int
Tdp_SocketOptionCmd (clientData, interp, argc, argv)
    ClientData *clientData;         /* Often ignored */
    Tcl_Interp *interp;             /* tcl interpreter */
    int argc;                       /* Number of arguments */
    char *argv[];                   /* Arg list */
{
    char c;
    DP_Socket *sockPtr;
    int optname;
    int optval;
    int result;
    int optlen;
    char tmp[256];
    int error;

    optlen = sizeof(int);
    if ((argc != 3) && (argc != 4)) {
	Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
			" socket option ?value?\"", (char *) NULL);
	return TCL_ERROR;
    }

    if (Tdp_GetOpenSocket (interp, argv[1], 0, 0, &sockPtr) != TCL_OK) {
	return TCL_ERROR;
    }

    c = argv[2][0];
    if (isupper(c)) {
	c = tolower(c);
    }

    /* ------------------------ SEND BUFFER ---------------------------- */
    if ((c == 's') && (strcmp(argv[2], "sendBuffer") == 0)) {
	optname = SO_SNDBUF;
	if (argc == 4) {
	    if (Tcl_GetInt(interp, argv[3], &optval) != TCL_OK) {
		return TCL_ERROR;
	    }
	}

    /* ------------------------ RECV BUFFER ---------------------------- */
    } else if ((c == 'r') && (strcmp(argv[2], "recvBuffer") == 0)) {
	optname = SO_RCVBUF;
	if (argc == 4) {
	    if (Tcl_GetInt(interp, argv[3], &optval) != TCL_OK) {
		return TCL_ERROR;
	    }
	}

    /* ------------------------ NON BLOCKING --------------------------- */
    } else if ((c == 'n') && (strcmp(argv[2], "noblock") == 0)) {
	if (argc == 3) {
	    sprintf (tmp, "%s",
		     ((sockPtr->optFlags&FD_BLOCKING)?"no":"yes"));
	    Tcl_SetResult(interp, tmp, TCL_VOLATILE);
	} else {
	    if (strcmp (argv[3], "yes") == 0) {
		return Tdp_SetBlocking (interp, sockPtr, 0);
	    } else if (strcmp (argv[3], "no") == 0) {
		return Tdp_SetBlocking (interp, sockPtr, 1);
	    } else {
		Tcl_AppendResult (interp, argv[0], ": Bad value \"",
				  argv[3], "\"", NULL);
		return TCL_ERROR;
	    }
	}
	return TCL_OK;

    /* ------------------------ KEEP ALIVE ----------------------------- */
    } else if ((c == 'k') && (strcmp(argv[2], "keepAlive") == 0)) {
	optname = SO_KEEPALIVE;
	optval = 1;
	if (argc == 4) {
	    if (strcmp (argv[3], "yes") == 0) {
		optval = 1;
	    } else if (strcmp (argv[3], "no") == 0) {
		optval = 0;
	    } else {
		Tcl_AppendResult (interp, argv[0], ": Bad value \"",
				  argv[3], "\"", NULL);
		return TCL_ERROR;
	    }
	}

    /* ------------------------ AUTO CLOSE ----------------------------- */
    } else if ((c == 'a') && (strcmp(argv[2], "autoClose") == 0)) {
	if (argc == 3) {
	    sprintf (tmp, "%s",
		     ((sockPtr->optFlags&FD_AUTO_CLOSE)?"yes":"no"));
	    Tcl_SetResult(interp, tmp, TCL_VOLATILE);
	} else {
	    if (strcmp (argv[3], "yes") == 0) {
		sockPtr->optFlags |= FD_AUTO_CLOSE;
	    } else if (strcmp (argv[3], "no") == 0) {
		sockPtr->optFlags &= ~FD_AUTO_CLOSE;
	    } else {
		Tcl_AppendResult (interp, argv[0], ": Bad value \"",
				  argv[3], "\"", NULL);
		return TCL_ERROR;
	    }
	}
	return TCL_OK;

    /* ------------------------ ERROR ---------------------------------- */
    } else {
	Tcl_AppendResult(interp, argv[0], "unknown option \"",
			 argv[2], "\"", (char *) NULL);
	return TCL_ERROR;
    }

    if (argc == 4) {
	result = setsockopt(sockPtr->socket, SOL_SOCKET, optname,
			    (char *)&optval, optlen);
    } else {
	result = getsockopt(sockPtr->socket, SOL_SOCKET, optname,
			    (char *)&optval, &optlen);
    }

    if (result != 0) {
	goto wsaError;
    }

    if (optname == SO_KEEPALIVE) {
	sprintf (tmp, "%s", optval?"yes":"no");
    } else {
	sprintf (tmp, "%d", optval);
    }
    Tcl_SetResult(interp, tmp, TCL_VOLATILE);

    if ((optname == SO_RCVBUF) && (optval > bufferSize)) {
	Tdp_AllocateBuffer (sockPtr);
    }
    return TCL_OK;

wsaError:
    error = WSAGetLastError();
    Tcl_AppendResult(interp, argv[0], ": ", Tdp_WSAError(interp, error),
		     (char *) NULL);
    return TCL_ERROR;
}

/*
 *------------------------------------------------------------------
 *
 * Tdp_ShutdownCmd --
 *
 *      This procedure is the C interface to the "dp_shutdown"
 *      command. See the user documentation for a description.
 *
 * Results:
 *	A standard Tcl result.
 *
 * Side effects:
 *	Modifies the OpenFile structure appropriately.
 *	Delete any created filehandlers.
 *
 *------------------------------------------------------------------
 */

 /* ARGSUSED */
int
Tdp_ShutdownCmd(notUsed, interp, argc, argv)
    ClientData notUsed;
    Tcl_Interp *interp;
    int argc;
    char **argv;
{
    DP_Socket *sockPtr;
    DP_FileHandle *handler;
    int permissions;
    int error;

    /*
     * Check args, find file
     */
    if (argc != 3) {
wrong_args:
	Tcl_AppendResult (interp, "wrong # args: should be \"", argv[0],
			  " sockId <option>\"", (char *) NULL);
	return TCL_ERROR;
    }

    if (Tdp_GetOpenSocket(interp, argv[1], 0, 0, &sockPtr) != TCL_OK) {
	return TCL_ERROR;
    }

    permissions = sockPtr->permissions;
    if (permissions == -1) {
	Tcl_AppendResult(interp, "unable to determine access for socket \"",
			 argv[1], "\"", (char *)NULL);
	return TCL_ERROR;
    }
    handler = sockPtr->handlers;

    /*
     * Call shutdown with correct args, update file handler
     */
    if (!strcmp(argv[2], "0") ||
	!strcmp(argv[2], "receives") ||
	!strcmp(argv[2], "read")) {
	if ((permissions & TCL_FILE_READABLE) == 0) {
	    Tcl_AppendResult(interp, "File is not readable", (char *) NULL);
	    return TCL_ERROR;
	}
	if (shutdown(sockPtr->socket, 0)) {
	    error = WSAGetLastError();
	    Tcl_AppendResult(interp, "shutdown: ", Tdp_WSAError(interp, error),
			     (char *) NULL);
	    return TCL_ERROR;
	}
	permissions &= ~TCL_FILE_READABLE;
    } else if (!strcmp(argv[2], "1") ||
	       !strcmp(argv[2], "sends") ||
	       !strcmp(argv[2], "write")) {
	if ((permissions & TCL_FILE_WRITABLE) == 0) {
	    Tcl_AppendResult(interp, "File is not writable", (char *) NULL);
	    return TCL_ERROR;
	}
	fflush(sockPtr->filePtr);
	if (shutdown(sockPtr->socket, 1)) {
	    error = WSAGetLastError();
	    Tcl_AppendResult(interp, "shutdown: ", Tdp_WSAError(interp, error),
			     (char *) NULL);
	    return TCL_ERROR;
	}
	permissions &= ~TCL_FILE_WRITABLE;
    } else if (!strcmp(argv[2], "2") ||
	       !strcmp(argv[2], "all") ||
	       !strcmp(argv[2], "both")) {
	fflush(sockPtr->filePtr);
	if (shutdown(sockPtr->socket, 2)) {
	    error = WSAGetLastError();
	    Tcl_AppendResult(interp, "shutdown: ", Tdp_WSAError(interp, error),
			     (char *) NULL);
	    return TCL_ERROR;
	}
	permissions = 0;
    } else {
	goto wrong_args;
    }

    sockPtr->permissions = permissions;

    /*
     * Update the handler, freeing it if it's dead.
     */
    if (handler) {
	if (((permissions & TCL_FILE_READABLE) == 0) &&
	    (handler->rCmd != NULL)) {
	    ckfree(handler->rCmd);
	    handler->rCmd = NULL;
	}
	if (((permissions & TCL_FILE_WRITABLE) == 0) &&
	    (handler->wCmd != NULL)) {
	    ckfree(handler->wCmd);
	    handler->wCmd = NULL;
	}
	if ((permissions&(TCL_FILE_READABLE|TCL_FILE_WRITABLE)) == 0) {
	    if (handler->eCmd != NULL) {
		ckfree(handler->eCmd);
		handler->eCmd = NULL;
	    }

	    /* XXX: This needs to be entered with the Tk_DeleteIOHandler */
	    /*
	     * Delete handler.
	     */
	    Tk_DeleteFileHandler (sockPtr->socket);

	    sockPtr->handlers = (DP_FileHandle *) NULL;
	    Tk_EventuallyFree((ClientData) handler,
			      (Tk_FreeProc *)Tdp_FreeHandler);
	}
    }
    return TCL_OK;
}

/*
 *------------------------------------------------------------------
 *
 * Tdp_AcceptCmd --
 *
 *      This procedure is the C interface to the "dp_accept"
 *      command. See the user documentation for a description.
 *	It accepts a connection on a listening socket.
 *
 * Results:
 *	a standard tcl result
 *
 * Side effects:
 *	Opens a new file.
 *
 *------------------------------------------------------------------
 */

 /* ARGSUSED */
int
Tdp_AcceptCmd(notUsed, interp, argc, argv)
    ClientData notUsed;
    Tcl_Interp *interp;
    int argc;
    char **argv;
{
    struct sockaddr_in sockaddr;
    int len = sizeof sockaddr;
    SOCKET socket;
    DP_Socket *sockPtr1, *sockPtr2;
    int addr, f1, f2, f3, f4;
    char tmp[128];
    int error;

    if (argc != 2) {
	Tcl_AppendResult (interp, "wrong # args: should be \"", argv[0],
			  " listening_socket\"", (char *) NULL);
	return TCL_ERROR;
    }

    if (Tdp_GetOpenSocket (interp, argv[1], 0, 1, &sockPtr1) != TCL_OK) {
	return TCL_ERROR;
    }

    if (sockPtr1->optFlags & FD_SERVER) {
	socket = accept (sockPtr1->socket, (struct sockaddr *) &sockaddr,
			 &len);
    } else {
	Tcl_AppendResult (interp, argv[0], ": must be a server socket", NULL);
	return TCL_ERROR;
    }

    if (socket == INVALID_SOCKET) {
	error = WSAGetLastError();
	Tcl_AppendResult (interp, "accept: ", wsa_strerror(error), (char *) NULL);
	return TCL_ERROR;
    }

    /*
     * Create the sockId structure.
     */
    if (Tdp_EnterSocket(interp, socket, TCL_FILE_READABLE|TCL_FILE_WRITABLE,
			&sockPtr2) != TCL_OK)
    {
	return TCL_ERROR;
    }
    if (sockaddr.sin_family == AF_INET) {
      addr = htonl(sockaddr.sin_addr.s_addr);
      f1 = (addr >> 24) & 0xff;
      f2 = (addr >> 16) & 0xff;
      f3 = (addr >> 8) & 0xff;
      f4 = addr & 0xff;
    } else {
      f1 = f2 = f3 = f4 = 0;
    }

    sprintf (tmp, "file%u %d.%d.%d.%d", (unsigned) sockPtr2->sockId, f1, f2, f3, f4);
    Tcl_SetResult(interp, tmp, TCL_VOLATILE);

    Tdp_AllocateBuffer(sockPtr2);
    sockPtr2->optFlags = FD_AUTO_CLOSE | FD_TCP |
	FD_BLOCKING | (sockPtr1->optFlags & FD_UNIX);
    return TCL_OK;
}

#ifdef UNIX_SOCKET
/*
 *----------------------------------------------------------------
 *
 * Tdp_unix_connect --
 *
 * 	Create a (unix_domain) fd connection using given rendevous
 *
 * Results:
 *	A socket or INVALID_SOCKET
 *
 * Side effects:
 * 	None.
 *----------------------------------------------------------------
 */
static SOCKET
Tdp_unix_connect(path, server, udp)
    char *path;			/* Path name to create or use */
    int server;			/* 1->make server, 0->connect to server */
    int udp;			/* Make it a udp protocol socket */
{
    struct sockaddr_un sockaddr;
    int status;
    SOCKET sock;

    if (udp) {
	sock = socket(PF_UNIX, SOCK_DGRAM, 0);
    } else {
	sock = socket(PF_UNIX, SOCK_STREAM, 0);
    }
    if (sock == INVALID_SOCKET) {
	return INVALID_SOCKET;
    }

    memset((char *) &sockaddr, 0, sizeof(sockaddr));
    sockaddr.sun_family = AF_UNIX;
    strncpy(sockaddr.sun_path, path, sizeof(sockaddr.sun_path) - 1);

    /* Just in case addr is too long... */
    sockaddr.sun_path[sizeof(sockaddr.sun_path) - 1] = 0;

    if (server | udp) {
	status = bind(sock, (struct sockaddr *)&sockaddr, sizeof(sockaddr));
    } else {
	status = connect(sock, (struct sockaddr *)&sockaddr, sizeof(sockaddr));
    }
    if (status < 0) {
	closesocket(sock);
	return INVALID_SOCKET;
    }
    if (server && !udp) {
	listen(sock, 5);
    }
    return sock;
}
#endif

/*
 *----------------------------------------------------------------
 *
 * Tdp_inet_connect --
 *
 * 	Create a (inet domain) fd connection to given host and port.
 *
 * Results:
 *	The open socket or INVALID_SOCKET
 *
 * Side effects:
 * 	None.
 *----------------------------------------------------------------
 */

static SOCKET
Tdp_inet_connect(host, port, server, udp, reuseAddr, lingerTime)
    char *host;			/* Host to connect, name or IP address */
    int port;			/* Port number to use */
    int server;			/* 1->make server, 0->connect to server */
    int udp;			/* Make it a udp protocol socket */
    int reuseAddr;		/* Allow local reuse of addresses */
    int lingerTime;		/* Time to linger on close */
{
    struct hostent *hostent, _hostent;
    struct sockaddr_in sockaddr;
    SOCKET sock;
    int status;
#ifdef __DGUX__
    struct sockaddr_in hostaddr, hostaddrPtr[2];
#else
    int hostaddr, hostaddrPtr[2];
#endif
    char localhost[MAXHOSTNAMELEN];
    int result;
    int linger[2];

    /*
     * Translate the hostname
     */
    if (host == NULL) {
        gethostname(localhost,MAXHOSTNAMELEN);
        host = localhost;
    }
    hostent = gethostbyname(host);

    if (hostent == NULL) {
#ifdef __DGUX__
	if (strlen(host) == 0) {
            memset(&hostaddr,0,sizeof(hostaddr));
	} else {
	    hostaddr.sin_addr = inet_addr(host);
	}
	if ((int)hostaddr.sin_addr.s_addr == -1) {
	    if (server && !strlen(host)) {
		hostaddr.sin_addr.s_addr = INADDR_ANY;
	    } else {
		WSASetLastError(WSAEINVAL);
		return INVALID_SOCKET;
	    }
	}
#else
	if (strlen(host) == 0) {
	    hostaddr = 0;
	} else {
	    hostaddr = inet_addr(host);
	}
	if (hostaddr == -1) {
	    if (server && !strlen(host)) {
		hostaddr = INADDR_ANY;
	    } else {
		WSASetLastError(WSAEINVAL);
		return INVALID_SOCKET;
	    }
	}
#endif

	_hostent.h_addr_list = (char **) hostaddrPtr;
#ifdef CRAY_HACKS
	hostaddr <<= 32;
#endif

#ifdef __DGUX__
	_hostent.h_addr_list[0] = (char *) &hostaddr.sin_addr;
	_hostent.h_length = sizeof(hostaddr.sin_addr);
#else
	_hostent.h_addr_list[0] = (char *) &hostaddr;
	_hostent.h_length = sizeof(hostaddr);
#endif
	_hostent.h_addr_list[1] = NULL;
	_hostent.h_addrtype = AF_INET;
	hostent = &_hostent;
    }

    /*
     * Create the socket
     */
    if (udp) {
	sock = socket(PF_INET, SOCK_DGRAM, 0);
    } else {
	sock = socket(PF_INET, SOCK_STREAM, 0);
    }
    if (sock == INVALID_SOCKET) {
	return INVALID_SOCKET;
    }

    /*
     * Set the linger and reuseAddr socket options
     */
#ifdef SO_LINGER
    linger[0] = lingerTime > 0;
    linger[1] = lingerTime;
    result = setsockopt(sock, SOL_SOCKET, SO_LINGER,
			(char *)linger, sizeof(linger));
#endif
#ifdef SO_REUSEADDR
    if (reuseAddr) {
	int one = 1;
	result = setsockopt(sock, SOL_SOCKET, SO_REUSEADDR,
			    (char *)&one, sizeof(int));
    }
#endif
    
    /*
     * Bind the socket
     */
    memset((char *)&sockaddr, 0,sizeof(sockaddr));
    sockaddr.sin_family = AF_INET;
#ifdef CRAY_HACKS
    {
    unsigned long foo;

    memcpy((char *)&foo,
	   (char *)hostent->h_addr_list[0],
	   (size_t)hostent->h_length);

    sockaddr.sin_addr.s_addr = foo>>32;
    }
#else
    memcpy((char *)&(sockaddr.sin_addr.s_addr),
	   (char *)hostent->h_addr_list[0],
	   (size_t)hostent->h_length);
#endif

    sockaddr.sin_port = htons((unsigned short) port);

    if (server | udp) {
	status = bind(sock, (struct sockaddr *)&sockaddr, sizeof(sockaddr));
    } else {
	status = connect(sock, (struct sockaddr *)&sockaddr, sizeof(sockaddr));
    }

    if (status < 0) {
	int err = WSAGetLastError();
	closesocket(sock);
	WSASetLastError(err);
	return INVALID_SOCKET;
    }

    /*
     * Finally, listen on the socket if it's a server.
     */
    if (server && !udp) {
	listen(sock, 5);
    }
    return sock;
}

/*
 *--------------------------------------------------------------
 *
 * Tdp_IsReadyCmd --
 *
 *	This procedure implements the "dp_isreadable" function, which
 *	returns whether a file has input pending.
 *
 * Results:
 *	A standard tcl result.
 *
 * Side effects:
 *	None.
 *
 *--------------------------------------------------------------
 */
 /* ARGSUSED */
int
Tdp_IsReadyCmd(clientData, interp, argc, argv)
    ClientData clientData;   /* Ignored */
    Tcl_Interp *interp;               /* Tcl interpreter */
    int argc;                 /* Number of arguments */
    char *argv[];             /* Arg list */
{
    DP_Socket *sockPtr;
    int state, readable, writeable;
    char tmp[32];

    if (argc != 2)
      goto syntaxError;

    if (Tdp_GetOpenSocket(interp, argv[1], 0, 0, &sockPtr) != TCL_OK) {
        return TCL_ERROR;
    }

    state = Tdp_SocketIsReady(sockPtr);
    readable = (state & TCL_FILE_READABLE) != 0;
    writeable = (state & TCL_FILE_WRITABLE) != 0;
    sprintf (tmp,"%d %d", readable, writeable);
    Tcl_SetResult(interp, tmp, TCL_VOLATILE);
    return TCL_OK;

  syntaxError:
    Tcl_AppendResult (interp, "wrong # args: should be \"",
			       argv[0], " sockId\"", (char *) NULL);
    return TCL_ERROR;
}

/*
 *--------------------------------------------------------------
 *
 * Tdp_FindFileHandler --
 *
 *	Find the filehandler associated with the
 *	descriptor passed in.
 *
 * Results:
 *	A pointer to the handler, or NULL if there is none.
 *
 * Side effects:
 *	None.
 *
 *--------------------------------------------------------------
 */

#define	Tdp_FindFileHandler(fd)			\
	(((fd) < 0 || (fd) >= MAX_OPEN_FILES)	\
	 ? ((DP_FileHandle *)NULL)		\
	 : handlers[fd])

/*
 *----------------------------------------------------------------
 *
 * Tdp_FileHandlerCmd --
 *
 *      This procedure is the C interface to the "dp_filehandler"
 *      command. See the user documentation for a description.
 * 	Register a file handler with an open sockId or fileId.  If there is
 *	already an existing handler, it will be no longer called.
 *	If no mask and command are given, any existing handler
 *	will be deleted.
 *
 * Results:
 *	A standard Tcl result. (Always OK).
 *
 * Side effects:
 *	A new file handler is associated with a give TCL open file.
 *	Whenever the file is readable, writeable and/or there is
 *	an expection condition on the file, a user supplied TCL
 *	command is called.
 *
 *----------------------------------------------------------------
 */

 /* ARGSUSED */
int
Tdp_FileHandlerCmd(notUsed, interp, argc, argv)
    ClientData notUsed;
    Tcl_Interp *interp;
    int argc;
    char **argv;
{
    int fd, mask;
    DP_Socket *sockPtr;
    DP_FileHandle *handler;
    int file = 0;

    /*
     * Checks args.
     */
    if (argc != 2 && argc != 4) {
	Tcl_AppendResult (interp, "wrong # args: should be \"", argv[0],
			  " [fileId|sockId] ?mode command?\"", (char *) NULL);
	return TCL_ERROR;
    }

    if (Tdp_GetOpenSocket(interp, argv[1], 0, 0, &sockPtr) != TCL_OK) {
	file = 1;
	fd = sockPtr->sockId;
	assert(fd < MAX_OPEN_FILES);
	handler = Tdp_FindFileHandler (fd);
	if (handler != NULL) {
	    handlers[fd] = NULL;
	}

    } else {
	handler = sockPtr->handlers;
	sockPtr->handlers = NULL;
    }
    if (handler != NULL) {
	Tk_EventuallyFree((ClientData)handler,
			  (Tk_FreeProc *)Tdp_FreeHandler);
	handler = NULL;
    }

    /* XXX: Need to use Tk_DeleteIOHandler here */
    Tk_DeleteFileHandler (sockPtr->socket);
    if (argc == 2)  {
        return TCL_OK;
    }

    /*
     * Find out on what situations the user is interested in. 
     * This is not the most elegant or efficient way to do this, 
     * but who cares?
     */
    mask = 0;
    if (strchr(argv[2], 'r')) {
	mask |= TK_READABLE;
    }
    if (strchr(argv[2], 'w')) {
	mask |= TK_WRITABLE;
    }
    if (strchr(argv[2], 'e')) {
	mask |= TK_EXCEPTION;
    }
    if (mask == 0 || (strlen(argv[2]) != strspn(argv[2], "rwe"))) {
	Tcl_AppendResult(interp, "bad mask argument \"", argv[2],
		  "\": should be any combination of \"r\", \"w\" and \"e\"",
			 (char *) NULL);
	return TCL_ERROR;
    }

    /*
     * Create a new handler.
     */
    handler = (DP_FileHandle *) ckalloc (sizeof (DP_FileHandle));
    handler->interp  = interp;
    handler->sockPtr = sockPtr;
    handler->sockId  = ckalloc (strlen (argv[1]) + 1);
    handler->rCmd = NULL;
    handler->wCmd = NULL;
    handler->eCmd = NULL;
    handler->mask = 0;
    strcpy (handler->sockId, argv[1]);

    if (file) {
	handlers[fd] = handler;
    } else {
	sockPtr->handlers = handler;
    }

    if (mask & TK_READABLE) {
	handler->rCmd = ckalloc(strlen(argv[3]) + 1);
	strcpy(handler->rCmd, argv[3]);
    }
    if (mask & TK_WRITABLE) {
	handler->wCmd = ckalloc(strlen(argv[3]) + 1);
	strcpy(handler->wCmd, argv[3]);
    }
    if (mask & TK_EXCEPTION) {
	handler->eCmd = ckalloc(strlen(argv[3]) + 1);
	strcpy(handler->eCmd, argv[3]);
    }

    handler->mask = mask;

    /* XXX: These need to be entered with Tk_CreateIOHandler */
    /*
     * Finally, get Tk to call Tdp_HandleEvent whenever there is a
     * file descriptor condition.
     */
#ifdef TCM
    Tcm_CreateFileHandler ("rpc", fd, mask, (Tk_FileProc *)Tdp_HandleEvent,
                        (ClientData) handler);
#else
#ifdef TK_EXTENDED
    Tk_CreateFileHandler (fd, (FILE *) NULL, mask, 
			  (Tk_FileProc *)Tdp_HandleEvent, (ClientData) handler);
#else
    Tk_CreateFileHandler (sockPtr->socket, mask, (Tk_FileProc *)Tdp_HandleEvent,
			  (ClientData) handler);
#endif
#endif /* TCM */

    return TCL_OK;
}

/*
 *--------------------------------------------------------------
 *
 * Tdp_FreeHandler --
 *
 *	Free up a file handler and all it's parts.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	None.
 *
 *--------------------------------------------------------------
 */
static void
Tdp_FreeHandler(clientData)
    ClientData clientData;
{
    DP_FileHandle *handler = (DP_FileHandle *)clientData;

    if (handler->rCmd != NULL) {
	ckfree(handler->rCmd);
    }
    if (handler->wCmd != NULL) {
	ckfree(handler->wCmd);
    }
    if (handler->eCmd != NULL) {
	ckfree(handler->eCmd);
    }
    if (handler->sockId != NULL) {
	ckfree((char *)handler->sockId);
    }
    ckfree ((char *)handler);
}

/*
 *----------------------------------------------------------------
 *
 * Tdp_HandleEvent --
 *
 * 	This procedure is called from Tk_DoOneEvent whenever there is
 *	a file descriptor condition on a given file descriptor.  It is
 *	installed by the "dp_filehandler" command.  A Tcl command
 *	given by the user is executed to handle the condition.  If
 *	an EOF or ERROR condition is noticed, the file descriptor
 *	is closed.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	The user supplied command can do anything.
 *
 *----------------------------------------------------------------
 */

static void
Tdp_HandleEvent(clientData, mask)
    ClientData clientData;
    int mask;
{
    int result;
    DP_FileHandle *handler = (DP_FileHandle *) clientData;
    Tcl_Interp *interp;
    DP_Socket *dummy;
    DP_Socket *sockPtr;
    int fd;
    int delete;
    int file = 0;

    if (!handler)
      return;

    interp = handler->interp;
    sockPtr = handler->sockPtr;
    if ((interp == NULL) || (sockPtr == NULL)) {
        return;
    }

    Tk_Preserve ((ClientData) handler);

    delete = 0;
    if (Tdp_GetOpenSocket (interp, handler->sockId, 0, 0,
			   &dummy) != TCL_OK)
    {
#if 0
	if (Tcl_GetOpenFile (interp, handler->sockId, 0, 0,
			     &(FILE *) dummy) != TCL_OK)
	{
	    /*  File descriptor is closed. */
	    Tcl_ResetResult (interp);
	    delete = 1;
	} else {
	    file = 1;
	}
#endif
	/*  File descriptor is closed. */
	Tcl_ResetResult (interp);
	delete = 1;
    } else {
	file = 1;
    }
    if (delete == 0) {
        Tcl_DString	cmd;
	assert (dummy == handler->sockPtr);
	Tcl_DStringInit(&cmd);

	if (mask & TK_EXCEPTION) {
	    if (handler->eCmd != NULL) {
		Tcl_DStringAppend(&cmd, handler->eCmd, -1);
		Tcl_DStringAppend(&cmd, " e ", 3);
		Tcl_DStringAppend(&cmd, handler->sockId, -1);
		result = Tcl_GlobalEval(interp, Tcl_DStringValue(&cmd));
		Tcl_DStringFree(&cmd);
		if (result != TCL_OK) {
		    goto close;
		}
	    } else {
		goto close;
	    }
	} else if ((mask & TK_READABLE) && (handler->rCmd != NULL)) {
	    Tcl_DStringAppend(&cmd, handler->rCmd, -1);
	    Tcl_DStringAppend(&cmd, " r ", 3);
	    Tcl_DStringAppend(&cmd, handler->sockId, -1);
	    result = Tcl_GlobalEval(interp, Tcl_DStringValue(&cmd));
	    Tcl_DStringFree(&cmd);
	    if (result != TCL_OK) {
		Tk_BackgroundError(interp);
	    }
	} else if ((mask & TK_WRITABLE) && (handler->wCmd != NULL)) {
	    Tcl_DStringAppend(&cmd, handler->wCmd, -1);
	    Tcl_DStringAppend(&cmd, " w ", 3);
	    Tcl_DStringAppend(&cmd, handler->sockId, -1);
	    result = Tcl_GlobalEval(interp, Tcl_DStringValue(&cmd));
	    Tcl_DStringFree(&cmd);
	    if (result != TCL_OK) {
		Tk_BackgroundError(interp);
	    }
	}
	else if (feof(sockPtr->filePtr) ||
		   ferror(sockPtr->filePtr))
	{
	  close:
	    if (Tcl_VarEval (interp, "close ", handler->sockId,
			     (char *) NULL) != TCL_OK) {
		Tcl_AppendResult (interp, "Unexpected EOF on ",
				  handler->sockId, (char *) NULL);
		Tk_BackgroundError (interp);
	    }
	    delete = 1;
	}
    }

    Tk_Release ((ClientData) handler);

    /* Because files do not go through our closesocket routine(s),
     * they need to be handled specially here
     */
    if (delete && file) {
	fd = fileno ((FILE *) sockPtr);

        if ((handler = Tdp_FindFileHandler (fd)) != NULL) {
	    handlers[fd] = (DP_FileHandle *) NULL;
	    Tk_EventuallyFree((ClientData)handler,
			      (Tk_FreeProc *)Tdp_FreeHandler);
	}
        Tk_DeleteFileHandler (sockPtr->socket);
    }
}

/*
 *--------------------------------------------------------------
 *
 * Tdp_ReceiveCmd --
 *
 *      This procedure is invoked to process the "dp_receive" Tcl/Tk
 *      command.  See the user documentation for details on what
 *      it does.
 *
 * Results:
 *	A standard tcl result.
 *
 * Side effects:
 *	The file descriptor passed in is read.
 *
 *--------------------------------------------------------------
 */

/* ARGSUSED */
int
Tdp_ReceiveCmd(clientData, interp, argc, argv)
    ClientData clientData;	/* Ignored */
    Tcl_Interp *interp;		/* Tcl interpreter */
    int argc;			/* Number of arguments */
    char *argv[];		/* Arg list */
{
    int count;
    int flags;
    DP_Socket *sockPtr;
    int i, len;
    int error;

    if ((argc < 2) || (argc > 5)) {
	goto syntaxError;
    }

    if (Tdp_GetOpenSocket(interp, argv[1], 0, 1, &sockPtr) != TCL_OK) {
	return TCL_ERROR;
    }

    /* 
     * Make sure this is a non-server TCP socket
     */
    if ((sockPtr->optFlags & FD_TCP) == 0) {
	Tcl_AppendResult(interp, "can't use ", argv[0], " on non TCP socket",
			 (char *) NULL);
	return TCL_ERROR;
    }

    if ((sockPtr->optFlags & FD_SERVER) != 0) {
	Tcl_AppendResult(interp, "can't use ", argv[0], " on server socket",
			 (char *) NULL);
	return TCL_ERROR;
    }

    /*
     * Get the extra parameters, if specified
     */
    count = bufferSize;
    flags = 0;
    for (i=2; i<argc; i++) {
	len = strlen (argv[i]);
	if (strncmp(argv[i], "-peek", len) == 0) {
	    flags |= MSG_PEEK;
	} else if (Tcl_GetInt(interp, argv[i], &count) != TCL_OK) {
	    goto syntaxError;
	}
    }

    /*
     * Read the message into the global buffer and put on trailing
     * 0 at end of string in case we received a partial message.
     */
    count = Tdp_Read(interp, sockPtr, buffer, count, flags);
    Tcl_ResetResult(interp);
    if (count == -1) {
	error = WSAGetLastError();
	/*
	 * If the file is in non-blocking mode, return null string
	 */
	if (error == WSAEWOULDBLOCK /* || error == WSAEAGAIN */) {
	    return TCL_OK;
	} else {
	    Tcl_AppendResult(interp, "error reading ", argv[1],  ": ",
		    Tdp_WSAError(interp, error), (char *) NULL);
	    return TCL_ERROR;
	}
    }

    /*
     * If we get an eof, the connection is closed and we
     * should do some cleanup.
     */
    if (count == 0) {
	if (sockPtr->optFlags & FD_AUTO_CLOSE) {
	    Tdp_CleanupFile(interp, argv[1], sockPtr);
	    Tcl_ResetResult(interp);
	    return TCL_OK;
	} else {
	    Tcl_AppendResult(interp,
			"error reading socket (connection closed) ",
			argv[1], (char *) NULL);
	    return TCL_ERROR;
	}
    }

    /*
     * Ok, we got what we got.  Return it.
     */
    buffer[count] = 0;
    Tcl_SetResult(interp, buffer, TCL_STATIC);
    return TCL_OK;

  syntaxError:
    Tcl_AppendResult (interp,
                      "syntax error: should be \"", argv[0],
                      " sockId ?numBytes? ?-peek?\"",
                      (char *) NULL);
    return TCL_ERROR;
}

/*
 *--------------------------------------------------------------
 *
 * Tdp_CleanupFile --
 *
 *	Clean up a socket on error.
 *
 * Results:
 *	None
 *
 * Side effects:
 *	Will close the file and remove the handler if auto close
 *	is on.  This is the default action.
 *
 *--------------------------------------------------------------
 */
void
Tdp_CleanupFile(interp, file, clientData)
    Tcl_Interp *interp;
    char *file;
    ClientData clientData;
{
    DP_Socket *sockPtr = (DP_Socket *) clientData;
    if (sockPtr->optFlags & FD_AUTO_CLOSE) {
	Tcl_VarEval(interp, "dp_filehandler ", file, (char *) NULL);
	Tcl_VarEval(interp, "close ", file, (char *) NULL);
    }
}

/*
 *--------------------------------------------------------------
 *
 * Tdp_SendCmd --
 *
 *      This procedure is invoked to process the "dp_send" Tcl/Tk
 *      command.  See the user documentation for details on what
 *      it does.
 *
 * Results:
 *	A standard tcl result.
 *
 * Side effects:
 *	The specified string is written to the file descriptor passed
 *	in.
 *
 *--------------------------------------------------------------
 */

 /* ARGSUSED */
int
Tdp_SendCmd(clientData, interp, argc, argv)
    ClientData clientData;	/* Ignored */
    Tcl_Interp *interp;		/* Tcl interpreter */
    int argc;			/* Number of arguments */
    char *argv[];		/* Arg list */
{
    int count;
    int newline;
    char tmp[256];
    int error;
    DP_Socket *sockPtr;

    if ((argc < 3) || (argc > 4)) {
	Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
			 " sockId string ?nonewline?\"", (char *) NULL);
	return TCL_ERROR;
    }

    if (Tdp_GetOpenSocket(interp, argv[1], 1, 1, &sockPtr) != TCL_OK) {
	return TCL_ERROR;
    }

    /* 
     * Make sure this is a non-server TCP socket
     */
    if ((sockPtr->optFlags & FD_TCP) == 0) {
	Tcl_AppendResult(interp, "can't use ", argv[0], " on non TCP socket",
			 (char *) NULL);
	return TCL_ERROR;
    }

    if ((sockPtr->optFlags & FD_SERVER) != 0) {
	Tcl_AppendResult(interp, "can't use ", argv[0], " on server socket",
			 (char *) NULL);
	return TCL_ERROR;
    }

    newline = 1;
    if (argc == 4) {
	if (strncmp(argv[3], "nonewline", strlen(argv[3])) != 0) {
	    Tcl_AppendResult(interp, "bad argument \"", argv[3],
			     "\": should be \"nonewline\"",  (char *) NULL);
	    return TCL_ERROR;
	}
	newline = 0;
    }

#ifndef NO_WRITEV
    {
      struct iovec	iov[2];
      register int	iovcnt = 1;
      iov[0].iov_len = strlen(argv[2]);
      iov[0].iov_base = argv[2];
      if (newline) {
	++iovcnt;
	iov[1].iov_len = 1;
	iov[1].iov_base = "\n";
      }
      /* Use writev to reduce number of kernel calls */
      count = writev(sockPtr->socket, iov, iovcnt);
    }
#else
    /*
     * Simulate writev() call with two write() calls, returning correct
     * value.
     */
    {
	char buf = '\n';
	int len = strlen(argv[2]);

	Tdp_BlockSocket(sockPtr, TK_WRITABLE|TK_EXCEPTION);
	count = send(sockPtr->socket, argv[2], len, 0);
	if (count != -1 && newline) {
	    Tdp_BlockSocket(sockPtr, TK_WRITABLE|TK_EXCEPTION);
	    count = send(sockPtr->socket, &buf, 1, 0);
	    if (count != -1) {
		count = len+1;
	    }
	}
    }
#endif

    if (count == -1) {
	error = WSAGetLastError();
	if (error == WSAEDISCON) {
	    /*
	     * Got a broken pipe signal.  Close the file, delete the file
	     * handler, and return 0 bytes written.
	     */
	    Tdp_CleanupFile(interp, argv[1], sockPtr);
	    Tcl_SetResult(interp, "0", TCL_STATIC);
	    return TCL_OK;
	}

	if (error == WSAEWOULDBLOCK /* || error == WSAEAGAIN */) {
	    Tcl_SetResult(interp, "0", TCL_STATIC);
	    return TCL_OK;
	}

	Tcl_AppendResult(interp, "error writing ", argv[1],  ": ",
			 Tdp_WSAError(interp, error), (char *) NULL);
	return TCL_ERROR;
    }
    sprintf (tmp, "%d", count);
    Tcl_SetResult(interp, tmp, TCL_VOLATILE);
    return TCL_OK;
}

/*
 *--------------------------------------------------------------
 *
 * Tdp_PacketReceive --
 *
 *      This procedure is the C interface to the "dp_packetReceive"
 *      command. See the user documentation for a description.
 *
 * Results:
 *	A standard tcl result.
 *
 * Side effects:
 *	The file descriptor passed in is read.
 *
 *--------------------------------------------------------------
 */
int
Tdp_PacketReceive(interp, sockHandle, peek)
    Tcl_Interp *interp;		/* Tcl interpreter */
    char *sockHandle;
    int peek;
{
    int numRead;
    int packetLen;
    int headerSize;
    DP_Socket *sockPtr;
    unsigned char hbuf[8];
    int header[2];
    char *errMsg;
    int flags;
    int error;

    if (Tdp_GetOpenSocket(interp, sockHandle, 0, 1, &sockPtr) != TCL_OK) {
      return TCL_ERROR;
    }

    /* 
     * Make sure this is a non-server TCP socket
     */
    if ((sockPtr->optFlags & FD_TCP) == 0) {
	Tcl_AppendResult(interp, "can't use \"dp_packetReceive\" on non TCP socket",
			 (char *) NULL);
	return TCL_ERROR;
    }

    if ((sockPtr->optFlags & FD_SERVER) != 0) {
	Tcl_AppendResult(interp, "can't use \"dp_packetReceive\" on server socket",
			 (char *) NULL);
	return TCL_ERROR;
    }


    if (peek) {
	flags = MSG_PEEK;
    } else {
	flags = 0;
    }

    /*
     * Read in the header (8 bytes)
     */
    headerSize = 8;
    numRead = Tdp_Read (interp, sockPtr, hbuf, headerSize, flags);

    if (numRead <= 0) {
	goto readError;
    }

    /*
     * Check for incomplete read.  If so, put it back (only if we consumed it!)
     * and return.
     */
    if (numRead < headerSize) {
	if (!peek) {
	    Tdp_Unread (sockPtr, hbuf, numRead, 1);
	}
	Tcl_ResetResult(interp);
	return TCL_OK;
    }

    /*
     * Convert header character stream into ints.  This works when the
     * connecting machine has a different size int
     * and takes care of endian problems.  It is also mostly
     * backward compatible since network byte ordering (big endian) is
     * used.
     */

    header[0] = 0;
    header[0] |= (unsigned int)hbuf[0] << 24;
    header[0] |= (unsigned int)hbuf[1] << 16;
    header[0] |= (unsigned int)hbuf[2] << 8;
    header[0] |= (unsigned int)hbuf[3];

    header[1] = 0;
    header[1] |= (unsigned int)hbuf[4] << 24;
    header[1] |= (unsigned int)hbuf[5] << 16;
    header[1] |= (unsigned int)hbuf[6] << 8;
    header[1] |= (unsigned int)hbuf[7];

    /*
     * Format of each packet:
     *
     *		First 4 bytes are PACKET_MAGIC.
     *		Next 4 bytes are packetLen.
     *		Next packetLen-headerSize is zero terminated string
     */
    if (header[0] != PACKET_MAGIC) {
        Tcl_AppendResult(interp, "Error reading ", sockHandle,
			 ": badly formatted packet", (char *) NULL);
	goto readError;
    }
    packetLen = header[1] - headerSize;

    /*
     * Expand the size of the global buffer, as needed.
     */
    if (header[1] > bufferSize) {
	ckfree(buffer);
	bufferSize = header[1]+32;
	buffer = ckalloc(bufferSize);
    }

    /*
     * Read in the packet.  If it's only partially there, unread it and
     * return.  If we're peeking, we need to be careful since the header
     * is still in the queue.
     */
    if (peek) {
	numRead = Tdp_Read (interp, sockPtr, buffer, header[1], flags);
	if (numRead <= 0) {
	    goto readError;
	}

	/*
	 * Only partially there.  Return a null string.
	 */
	if (numRead != header[1]) {
	    Tcl_ResetResult(interp);
	    return TCL_OK;
	}

	buffer[numRead] = 0;
	Tcl_SetResult (interp, buffer+headerSize, TCL_STATIC);
	return TCL_OK;
    }

    /*
     * We're not peeking, so we've consumed the header (this is normal mode).
     * Read in the packet, and if it's not all there, put it back.
     *
     * We have to be careful here, because we could block when if just
     * the header came in (making the file readable at the beginning of this
     * function) but the rest of the packet is still out on the network.
     */
    if (Tdp_SocketIsReady(sockPtr) & TCL_FILE_READABLE) {
	numRead = Tdp_Read (interp, sockPtr, buffer, packetLen, flags);
    } else {
	Tdp_Unread (sockPtr, hbuf, headerSize, 1);
	Tcl_ResetResult(interp);
	return TCL_OK;
    }
    if (numRead < 0) {
	goto readError;
    }

    if (numRead != packetLen) {
	Tdp_Unread (sockPtr, buffer, numRead, 1);
	Tdp_Unread (sockPtr, hbuf, headerSize, 1);
	Tcl_ResetResult(interp);
	return TCL_OK;
    }

    buffer[numRead] = 0;
    Tcl_SetResult(interp, buffer, TCL_STATIC);
    return TCL_OK;

readError:
    /*
     *
     * If we're in non-blocking mode, and this would block, return.
     * If the connection is closed (numRead == 0), don't return an
     * error message.  Otherwise, return one.
     *
     * In either case, we close the file, delete the file handler, and
     * return a null string.
     */
    error = WSAGetLastError();

    if (error == WSAEWOULDBLOCK /* || error == WSAEAGAIN */) {
	Tcl_ResetResult(interp);
	return TCL_OK;
    }

    /* Record the error before closing the file */
    if (numRead != 0) {
	errMsg = Tdp_WSAError (interp, error);
    } else {
	errMsg = NULL;	/* Suppresses spurious compiler warning */
    }

    /* 
     * Remove the file handler and close the file.  We want to go through
     * tcl in case the user has overridden the close procedure
     */
    Tdp_CleanupFile(interp, sockHandle, sockPtr);
    Tdp_FreeReadBuffer(sockPtr);

    Tcl_ResetResult(interp);
    if (numRead == 0) {
	return TCL_OK;
    } else {
	Tcl_AppendResult (interp, "Tdp_PacketReceive -- error reading ",
		  sockHandle, ": ", errMsg, (char *) NULL);
	return TCL_ERROR;
    }
}

/*
 *--------------------------------------------------------------
 *
 * Tdp_PacketReceiveCmd --
 *
 *      This procedure is invoked to process the "dp_packetReceive" Tcl/Tk
 *      command.  See the user documentation for details on what
 *      it does.
 *
 * Results:
 *	A standard tcl result.
 *
 * Side effects:
 *	The specified string is written to the file descriptor passed
 *	in.
 *
 *--------------------------------------------------------------
 */

 /* ARGSUSED */
int
Tdp_PacketReceiveCmd(clientData, interp, argc, argv)
    ClientData clientData;	/* Ignored */
    Tcl_Interp *interp;		/* Tcl interpreter */
    int argc;			/* Number of arguments */
    char *argv[];		/* Arg list */
{
    char *sockHandle;
    int len, peek;

    if ((argc < 2) || (argc > 3)) {
      syntaxError:
	Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
			 "\" sockId ?-peek?", (char *) NULL);
	return TCL_ERROR;
    }

    sockHandle = argv[1];

    if (argc == 3) {
	len = strlen(argv[2]);
	if (strncmp(argv[2], "-peek", len) == 0) {
	    peek = 1;
	} else {
	    goto syntaxError;
	}
    } else {
	peek = 0;
    }
    return (Tdp_PacketReceive(interp, sockHandle, peek));
}

/*
 *--------------------------------------------------------------
 *
 * Tdp_PacketSend --
 *
 *      This procedure is the C interface to the "dp_packetSend" command.
 *
 * Results:
 *	A standard tcl result.
 *
 * Side effects:
 *	The specified string is written to the file descriptor passed
 *	in.
 *
 *--------------------------------------------------------------
 */

int
Tdp_PacketSend(interp, sockHandle, message)
    Tcl_Interp *interp;		/* Tcl interpreter */
    char *sockHandle;
    char *message;
{
    DP_Socket *sockPtr;
    int strLen;
    int packetLen;
    int numSent;
    int error;
    unsigned char hbuf[8];
    unsigned long header[2];
    char tmp[256];

    if (Tdp_GetOpenSocket(interp, sockHandle, 1, 1, &sockPtr) != TCL_OK) {
	return TCL_ERROR;
    }

    /* 
     * Make sure this is a non-server TCP socket
     */
    if ((sockPtr->optFlags & FD_TCP) == 0) {
	Tcl_AppendResult(interp, "can't use \"dp_packetSend\" on non TCP socket",
			 (char *) NULL);
	return TCL_ERROR;
    }

    if ((sockPtr->optFlags & FD_SERVER) != 0) {
	Tcl_AppendResult(interp, "can't use \"dp_packetSend\" on server socket",
			 (char *) NULL);
	return TCL_ERROR;
    }


    /*
     * Format up the packet:
     *	  First 4 bytes are PACKET_MAGIC.
     *	  Next 4 bytes are packetLen.
     *	  Next packetLen-(sizeof(int)) bytes are zero terminated message.
     */
    strLen = strlen (message);
    packetLen = strLen + 8;

    header[0] = PACKET_MAGIC;
    header[1] = packetLen;
    /*
     * Convert header ints to character stream.
     * Network byte ordering (big endian) is used.
     */

    hbuf[0] = (unsigned char) ((header[0] & 0xFF000000L) >> 24);
    hbuf[1] = (unsigned char) ((header[0] & 0x00FF0000L) >> 16);
    hbuf[2] = (unsigned char) ((header[0] & 0x0000FF00L) >> 8);
    hbuf[3] = (unsigned char) ((header[0] & 0x000000FFL));

    hbuf[4] = (unsigned char) ((header[1] & 0xFF000000L) >> 24);
    hbuf[5] = (unsigned char) ((header[1] & 0x00FF0000L) >> 16);
    hbuf[6] = (unsigned char) ((header[1] & 0x0000FF00L) >> 8);
    hbuf[7] = (unsigned char) ((header[1] & 0x000000FFL));

#ifndef NO_WRITEV
    {
    struct iovec iov[2];

    /* Set up scatter/gather vector */
    iov[0].iov_len = 8;
    iov[0].iov_base = (char *)hbuf;
    iov[1].iov_len = strLen;
    iov[1].iov_base = message;

    /* Send it off, with error checking */
    numSent = writev (sockPtr->socket, iov, 2);
    }
#else
    /*
     * Again, simulate writev (this time using memcpy to put together
     * the msg so it can go out in a single write() call
     */
    {
    int len;
    char *buffer;

    len = strLen + 8;
    buffer = (char *) ckalloc(len);

    memcpy(buffer, hbuf, 8);
    memcpy(buffer + 8, message, strLen);

    Tdp_BlockSocket(sockPtr, TK_WRITABLE|TK_EXCEPTION);
    numSent = send(sockPtr->socket, buffer, len, 0);

    ckfree(buffer);
    }
#endif

    if (numSent != packetLen) {
	error = WSAGetLastError();
	if ((error == 0) || (error == WSAEWOULDBLOCK /* || error == WSAEAGAIN */)) {
	    /*
	     * Non-blocking I/O: return number of bytes actually sent.
	     */
	    Tcl_ResetResult(interp);
	    sprintf (tmp, "%d", numSent - 8);
	    Tcl_SetResult(interp, tmp, TCL_VOLATILE);
	    return TCL_OK;

	} else if (error == WSAEDISCON) {
	    /*
	     * Got a broken pipe signal, which means the far end closed the
	     * connection.  Close the file, delete the file handler, and
	     * return 0 bytes sent.
	     */
	    Tdp_CleanupFile(interp, sockHandle, sockPtr);
	    sprintf (tmp, "0");
	    Tcl_SetResult(interp, tmp, TCL_VOLATILE);
	    return TCL_OK;

	} else {
	    Tcl_AppendResult (interp, "Tdp_PacketSend -- error writing ",
			      sockHandle, ": ",
			      Tdp_WSAError (interp, error), (char *) NULL);
	}

	return TCL_ERROR;
    }

    /*
     * Return the number of bytes sent (minus the header).
     */
    sprintf (tmp, "%d", numSent - 8);
    Tcl_SetResult(interp, tmp, TCL_VOLATILE);
    return TCL_OK;
}

/*
 *--------------------------------------------------------------
 *
 * Tdp_PacketSendCmd --
 *
 *      This procedure is invoked to process the "dp_packetSend" Tcl/Tk
 *      command.  See the user documentation for details on what
 *      it does.
 *
 * Results:
 *	A standard tcl result.
 *
 * Side effects:
 *	The specified string is written to the file descriptor passed
 *	in.
 *
 *--------------------------------------------------------------
 */

 /* ARGSUSED */
int
Tdp_PacketSendCmd(clientData, interp, argc, argv)
    ClientData clientData;	/* Ignored */
    Tcl_Interp *interp;		/* Tcl interpreter */
    int argc;			/* Number of arguments */
    char *argv[];		/* Arg list */
{
    char *sockHandle;

    if (argc != 3) {
	Tcl_AppendResult (interp, "wrong # args: should be \"", argv[0],
			  " sockId string\"", (char *) NULL);
	return TCL_ERROR;
    }

    sockHandle = argv[1];

    return (Tdp_PacketSend (interp, sockHandle, argv[2]));
}

/*
 *--------------------------------------------------------------
 *
 * Tdp_ReceiveFromCmd --
 *
 *      This procedure is invoked to process the "dp_receiveFrom" Tcl/Tk
 *      command.  See the user documentation for details on what
 *      it does.
 *
 * Results:
 *	A standard tcl result.
 *
 * Side effects:
 *	The file descriptor passed in is read.
 *
 *--------------------------------------------------------------
 */
 /* ARGSUSED */
int
Tdp_ReceiveFromCmd(notUsed, interp, argc, argv)
    ClientData notUsed;
    Tcl_Interp *interp;		/* Tcl interpreter */
    int argc;			/* Number of arguments */
    char *argv[];		/* Arg list */
{
    DP_Socket *sockPtr;
    int flags, numBytes;
    int len, i;
    int error;
    char *addrName;
    int count, addrLen;
    struct sockaddr_in addr;
    int noaddr;

    if ((argc < 2) || (argc > 5)) {
	goto syntaxError;
    }

    if (Tdp_GetOpenSocket(interp, argv[1], 0, 1, &sockPtr) != TCL_OK) {
	return TCL_ERROR;
    }

    /*  Parse flag parameters; */
    flags = 0;
    numBytes = bufferSize;
    noaddr = 0;
    for (i = 2; i < argc; i++) {
	len = strlen(argv[i]);
	if (strncmp(argv[i], "-peek", len) == 0) {
	    flags |= MSG_PEEK;
	} else if (strncmp(argv[i], "-noaddr", len) == 0) {
	    noaddr = 1;
	} else if (Tcl_GetInt(interp, argv[i], &numBytes) != TCL_OK) {
	    goto syntaxError;
	}
    }

    addrLen = sizeof(addr);
    memset((char *) &addr, 0, addrLen);

    /* 
     * Make sure this is a UDP socket
     */
    if (sockPtr->optFlags & FD_TCP) {
	Tcl_AppendResult(interp, "can't use ", argv[0], " on a TCP socket",
			 (char *) NULL);
	return TCL_ERROR;
    }

    /*
     * Read the message and put on trailing 0 at end of string in case we
     * received a partial message.
     */
    Tdp_BlockSocket(sockPtr, TK_READABLE|TK_EXCEPTION);
    count = recvfrom(sockPtr->socket, buffer, numBytes, flags,
		     (struct sockaddr *)&addr, &addrLen);
    if (count == -1) {
	Tcl_ResetResult(interp);
	error = WSAGetLastError();
	if (error == WSAEWOULDBLOCK /* || error == WSAEAGAIN */) {
	    return TCL_OK;
	}
	Tcl_AppendResult (interp, "error reading ", argv[1], ": ",
			  Tdp_WSAError (interp, error), (char *) NULL);

	return TCL_ERROR;
    }
    buffer[count] = 0;
    if (!noaddr) {
	unsigned long the_addr;
	int           the_port;

	the_addr = addr.sin_addr.s_addr;
	the_port = ntohs(addr.sin_port);

	addrName = Tdp_CreateAddress(the_addr, the_port);
	Tcl_SetResult(interp, addrName, TCL_STATIC);
	Tcl_AppendElement(interp, buffer);
    } else {
	Tcl_SetResult(interp, buffer, TCL_STATIC);
    }
    return TCL_OK;

  syntaxError:
    Tcl_AppendResult (interp,
                      "wrong # args: should be \"", argv[0],
                      " sockId ?numBytes? ?-noaddr? ?-peek?\"",
                      (char *) NULL);
    return TCL_ERROR;
}

/*
 *--------------------------------------------------------------
 *
 * Tdp_SendToCmd --
 *
 *      This procedure is invoked to process the "dp_sendTo" Tcl/Tk
 *      command.  See the user documentation for details on what
 *      it does.
 *
 * Results:
 *	A standard tcl result.
 *
 * Side effects:
 *	The specified string is written to the file descriptor passed
 *	in.
 *
 *--------------------------------------------------------------
 */

 /* ARGSUSED */
int
Tdp_SendToCmd(clientData, interp, argc, argv)
    ClientData clientData;	/* Ignored */
    Tcl_Interp *interp;		/* Tcl interpreter */
    int argc;			/* Number of arguments */
    char *argv[];		/* Arg list */
{
    DP_Socket *sockPtr;
    int len, status;
    struct sockaddr_in *addrPtr;
    int error;

    if (argc != 4) {
	Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
			 " sockId string address\"", (char *) NULL);
	return TCL_ERROR;
    }
    if (Tdp_GetOpenSocket(interp, argv[1], 1, 1, &sockPtr) != TCL_OK) {
	return TCL_ERROR;
    }

    len = strlen(argv[2]) + 1;

    /* 
     * Make sure this is a UDP socket
     */
    if (sockPtr->optFlags & FD_TCP) {
	Tcl_AppendResult(interp, "can't use ", argv[0], " on a TCP socket",
			 (char *) NULL);
	return TCL_ERROR;
    }

    addrPtr = (struct sockaddr_in *)Tdp_FindAddr(argv[3]);
    if (addrPtr == NULL) {
	Tcl_AppendResult(interp, argv[0], ": invalid address \"", argv[3],
			 "\"", (char *) NULL);
	return TCL_ERROR;
    }

    status = sendto(sockPtr->socket, argv[2], len, 0,
		    (struct sockaddr *)addrPtr, sizeof(struct sockaddr_in));
    if (status != len) {
	error = WSAGetLastError();
	Tcl_AppendResult(interp, "error writing ", argv[1], ": ",
			 Tdp_WSAError(interp, error), (char *) NULL);
	return TCL_ERROR;
    }
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * Tdp_CloseSocketCmd --
 *
 *	This procedure is invoked to process the "dp_closesocket" Tcl command.
 *
 * Results:
 *	A standard Tcl result.
 *
 * Side effects:
 *
 *----------------------------------------------------------------------
 */

	/* ARGSUSED */
int
Tdp_CloseSocketCmd(dummy, interp, argc, argv)
    ClientData dummy;			/* Not used. */
    Tcl_Interp *interp;			/* Current interpreter. */
    int argc;				/* Number of arguments. */
    char **argv;			/* Argument strings. */
{
    int result = TCL_OK;
    DP_Socket *sockPtr;

    /* XXXX: Need to call Tk_DeleteFileHandler if a handler is active. */
    /* This command should be going away */
    /* Use an Tk_EventuallyFree to free up the handler structure */
    if (argc != 2) {
	Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
		" sockId\"", (char *) NULL);
	return TCL_ERROR;
    }
    if (Tdp_GetOpenSocket(interp, argv[1], 0, 0, &sockPtr) != TCL_OK) {
	return TCL_ERROR;
    }

#if 0
    Tk_EventuallyFree((ClientData) sockPtr->handlers,
		      (Tk_FreeProc *)Tdp_FreeHandler);
#endif
    Tk_DeleteFileHandler (sockPtr->socket);

    closesocket(sockPtr->socket);
    Tdp_RemoveSocket(interp, sockPtr);

    return result;
}

/*
 *----------------------------------------------------------------------
 *
 * Tdp_HostnameCmd
 *
 *	This procedure is invoked to process the "dp_hostname" Tcl command.
 *
 * Results:
 *	A standard Tcl result.
 *
 * Side effects:
 *
 *----------------------------------------------------------------------
 */

	/* ARGSUSED */
int
Tdp_HostnameCmd(dummy, interp, argc, argv)
    ClientData dummy;			/* Not used. */
    Tcl_Interp *interp;			/* Current interpreter. */
    int argc;				/* Number of arguments. */
    char **argv;			/* Argument strings. */
{
    char hostname[MAXHOSTNAMELEN];
    int error;

    if (gethostname(hostname, sizeof(hostname)) == 0) {
	Tcl_SetResult(interp, hostname, TCL_VOLATILE);
	return TCL_OK;
    }

    error = WSAGetLastError();
    Tcl_AppendResult(interp, "Error getting hostname: ",
		     Tdp_WSAError(interp, error), (char *) NULL);
    return TCL_ERROR;
}
    

/*
 *--------------------------------------------------------------
 *
 * Tdp_Tcp_Init -
 *
 *	Initialize the connection management level functions of 
 *	Tcl-DP and register them with the given interpreter.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Serveral new commands are added to the interpreter.
 *
 *--------------------------------------------------------------
 */

void
Tdp_Tcp_Init(interp)
    Tcl_Interp *interp;		/* Tcl interpreter */
{
    Tcl_CreateCommand(interp, "dp_isready",
	(Tcl_CmdProc *)Tdp_IsReadyCmd,
	(ClientData) NULL, (void (*) _ANSI_ARGS_((ClientData))) NULL);
    Tcl_CreateCommand(interp, "dp_socketOption",
	(Tcl_CmdProc *)Tdp_SocketOptionCmd,
	(ClientData) NULL, (void (*) _ANSI_ARGS_((ClientData))) NULL);
    Tcl_CreateCommand(interp, "dp_connect",
	(Tcl_CmdProc *)Tdp_ConnectCmd,
	(ClientData) NULL, (void (*) _ANSI_ARGS_((ClientData))) NULL);
    Tcl_CreateCommand(interp, "dp_shutdown", 
	(Tcl_CmdProc *)Tdp_ShutdownCmd,
	(ClientData) NULL, (void (*) _ANSI_ARGS_((ClientData))) NULL);
    Tcl_CreateCommand(interp, "dp_accept", 
	(Tcl_CmdProc *)Tdp_AcceptCmd,
	(ClientData) NULL, (void (*) _ANSI_ARGS_((ClientData))) NULL);
    Tcl_CreateCommand(interp, "dp_filehandler", 
	(Tcl_CmdProc *)Tdp_FileHandlerCmd,
	(ClientData) NULL, (void (*) _ANSI_ARGS_((ClientData))) NULL);
    Tcl_CreateCommand(interp, "dp_send", 
	(Tcl_CmdProc *)Tdp_SendCmd,
	(ClientData) NULL, (void (*) _ANSI_ARGS_((ClientData))) NULL);
    Tcl_CreateCommand(interp, "dp_receive", 
	(Tcl_CmdProc *)Tdp_ReceiveCmd,
	(ClientData) NULL, (void (*) _ANSI_ARGS_((ClientData))) NULL);
    Tcl_CreateCommand(interp, "dp_packetSend", 
	(Tcl_CmdProc *)Tdp_PacketSendCmd,
	(ClientData) NULL, (void (*) _ANSI_ARGS_((ClientData))) NULL);
    Tcl_CreateCommand(interp, "dp_packetReceive", 
	(Tcl_CmdProc *)Tdp_PacketReceiveCmd,
	(ClientData) NULL, (void (*) _ANSI_ARGS_((ClientData))) NULL);
    Tcl_CreateCommand(interp, "dp_sendTo", 
	(Tcl_CmdProc *)Tdp_SendToCmd,
	(ClientData) NULL, (void (*) _ANSI_ARGS_((ClientData))) NULL);
    Tcl_CreateCommand(interp, "dp_receiveFrom", 
	(Tcl_CmdProc *)Tdp_ReceiveFromCmd,
	(ClientData) NULL, (void (*) _ANSI_ARGS_((ClientData))) NULL);
    Tcl_CreateCommand(interp, "dp_closesocket", Tdp_CloseSocketCmd,
	(ClientData)0, (void (*) _ANSI_ARGS_((ClientData))) NULL);
    Tcl_CreateCommand(interp, "dp_hostname", Tdp_HostnameCmd,
	(ClientData)0, (void (*) _ANSI_ARGS_((ClientData))) NULL);
}
