/*
 * dpIO.c --
 *
 *	Handle the some I/O callbacks from TCL.
 *
 * 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>
#endif

#include <fcntl.h>
#include <tk.h>
#include "dpInt.h"


DP_Socket *dpSocketList;

/*
 *----------------------------------------------------------------------
 * Tdp_RemoveSocket --
 *
 *	This procedure is used to remove a dp_socket from the dp_socket
 *	table.
 *
 * Results:
 *	Standard TCL result
 *----------------------------------------------------------------------
 */
int
Tdp_RemoveSocket(interp, sockPtr)
    Tcl_Interp *interp;
    DP_Socket *sockPtr;
{
    DP_Socket *curr, *prev, *next;

    for (prev = NULL, curr = dpSocketList; curr != NULL;
	 prev = curr, curr = next)
    {
	next = curr->next;
	if (curr == sockPtr) {
#ifdef _WINDOWS
	    extern void winio_unregisterSocket(int);
	    winio_unregisterSocket(curr->sockId);
#endif
	    if (prev == NULL) {
		dpSocketList = next;
	    } else {
		prev->next = next;
	    }
	    ckfree((char *) curr);
	    return TCL_OK;
	}
    }
    return TCL_ERROR;
}

/*
 *----------------------------------------------------------------------
 *
 * Tdp_EnterSocket --
 *
 *	This procedure is used to enter an already-open file into the
 *	dp_socket table for an interpreter so that the socket can be read
 *	and written with Tcl commands.
 *
 * Results:
 *	The standard TCL result and interp->result is set to
 *	hold Tdp's id for the open socket, such as "socket1039".
 *
 * Side effects:
 *	"File" is added to the files accessible from interp.
 *
 *----------------------------------------------------------------------
 */

int
Tdp_EnterSocket(interp, socket, permissions, sockPtrPtr)
    Tcl_Interp *interp;		/* Interpreter in which to make file
				 * available. */
    SOCKET socket;		/* Socket to make available in interp */
    int permissions;		/* Ops that may be done on socket:  OR-ed
				 * combinination of TCL_FILE_READABLE and
				 * TCL_FILE_WRITABLE. */
    DP_Socket **sockPtrPtr;	/* The dp_socket is returned through this */
{
    FILE *fp;
    int fd;
    DP_Socket *sockPtr;
#ifdef _WINDOWS
    DWORD nt_version;
    int major, minor;
    extern void winio_registerSocket(int, SOCKET);

    nt_version = GetVersion();
    major = nt_version & 0xff;
    minor = (nt_version & 0xff00) >> 8;

    /* In Windows NT 3.1, you could not open a socket as a file handle.
     * So we use a kludge to get a unique file identifier
     */
    if (major <= 3 && minor <= 10) {
	char fakeName[L_tmpnam];
	tmpnam(fakeName);
	fp = fopen(fakeName, "w");
	unlink(fakeName);
	fd = fileno(fp);
    } else {
	fd = _open_osfhandle(socket, _O_RDWR);
	fp = fdopen (fd, "r+");
    }
    winio_registerSocket(fd, socket);
#else
    /*
     * Open the file with the correct type
     */
    fd = socket;
    fp = fdopen (fd, "r+");
#endif
    clearerr(fp);

    /*
     * Turn off buffering.  Otherwise, we run into nasty interaction
     * problems with gets/puts/read and our transmission commands below.
     */
    setbuf (fp, (char *) NULL);

    Tcl_EnterFile(interp, fp, TCL_FILE_READABLE|TCL_FILE_WRITABLE);

    sockPtr = (DP_Socket *) ckalloc(sizeof(DP_Socket));
    sockPtr->socket = socket;
    sockPtr->sockId = fd;
    sockPtr->filePtr = fp;
    sockPtr->optFlags = 0;
    sockPtr->partial = NULL;
    sockPtr->handlers = NULL;
    sockPtr->permissions = permissions;
    sockPtr->error = 0;

    sockPtr->rpcWaiting = 0;
    sockPtr->rpcCheck = NULL;
    sockPtr->rpcResult = TCL_OK;
    sockPtr->rpcValue = NULL;
    sockPtr->rpcFlush = 0;
    sockPtr->rpcTimeoutReturn = NULL;
    sockPtr->timerToken = (ClientData) -1;
    sockPtr->timerInterp = NULL;

    sockPtr->next = dpSocketList;
    dpSocketList = sockPtr;
    *sockPtrPtr = sockPtr;
    sprintf(interp->result, "file%u", (unsigned int) fd);
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * Tdp_GetOpenSocket --
 *
 *	Given a string identifier for an open socket, find the corresponding
 *	open socket structure, if there is one.
 *
 * Results:
 *	A standard Tcl return value.  If the open socket is successfully
 *	located and meets any usage check requested by checkUsage, TCL_OK
 *	is returned and *sockPtrPtr is modified to hold a pointer to its
 *	FILE structure.  If an error occurs then TCL_ERROR is returned
 *	and interp->result contains an error message.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

int
Tdp_GetOpenSocket(interp, string, forWriting, checkUsage, sockPtrPtr)
    Tcl_Interp *interp;		/* Interpreter in which to find socket. */
    char *string;		/* String that identifies socket. */
    int forWriting;		/* 1 means the socket is going to be used
				 * for writing, 0 means for reading. */
    int checkUsage;		/* 1 means verify that the socket was opened
				 * in a mode that allows the access specified
				 * by "forWriting". */
    DP_Socket **sockPtrPtr;	/* Store pointer to DP_Socket here. */
{
    DP_Socket *sockPtr;
    unsigned int sockid;

    static int last_sockid = -1;
    static DP_Socket *last_sockPtr = NULL;


    if ((string[0] == 'f') && (string[1] == 'i') && (string[2] == 'l')
	    & (string[3] == 'e')) {
	char *end;

	sockid = strtoul(string+4, &end, 10);
	if ((end == string+4) || (*end != 0)) {
	    goto badId;
	}
    } else {
	badId:
	Tcl_AppendResult(interp, "bad socket identifier \"", string,
		"\"", (char *) NULL);
	return TCL_ERROR;
    }

#if 0    
    /* A quick cache check of the last found socket */
    if (sockid == last_sockid && sockid != -1) {
	*sockPtrPtr = last_sockPtr;
	return TCL_OK;
    }
#endif

    for (sockPtr = dpSocketList; sockPtr != NULL;
	 sockPtr = sockPtr->next)
    {
	if (sockPtr->sockId == sockid) {
	    *sockPtrPtr = sockPtr;
	    break;
	}
    }

    if (sockPtr == NULL) {
	sprintf(interp->result, "file \"%s\" isn't open", string);
	return TCL_ERROR;
    }

    if (forWriting) {
	if (checkUsage && !(sockPtr->permissions & TCL_FILE_WRITABLE)) {
	    Tcl_AppendResult(interp, "\"", string,
		    "\" wasn't opened for writing", (char *) NULL);
	    return TCL_ERROR;
	}
    } else {
	if (checkUsage && !(sockPtr->permissions & TCL_FILE_READABLE)) {
	    Tcl_AppendResult(interp, "\"", string,
		    "\" wasn't opened for reading", (char *) NULL);
	    return TCL_ERROR;
	}
    }
    return TCL_OK;
}
