sl@0: /* sl@0: * tclMacSock.c sl@0: * sl@0: * Channel drivers for Macintosh sockets. sl@0: * sl@0: * Copyright (c) 1996-1997 Sun Microsystems, Inc. sl@0: * sl@0: * See the file "license.terms" for information on usage and redistribution sl@0: * of this file, and for a DISCLAIMER OF ALL WARRANTIES. sl@0: * sl@0: * RCS: @(#) $Id: tclMacSock.c,v 1.14.2.1 2006/03/10 14:27:41 vasiljevic Exp $ sl@0: */ sl@0: sl@0: #include "tclInt.h" sl@0: #include "tclPort.h" sl@0: #include "tclMacInt.h" sl@0: #include sl@0: #include sl@0: #undef Status sl@0: #include sl@0: #include sl@0: #include sl@0: #include sl@0: #include sl@0: #include sl@0: #include sl@0: #include sl@0: sl@0: /* sl@0: * The following variable is used to tell whether this module has been sl@0: * initialized. sl@0: */ sl@0: sl@0: static int initialized = 0; sl@0: sl@0: /* sl@0: * If debugging is on we may drop into the debugger to handle certain cases sl@0: * that are not supposed to happen. Otherwise, we change ignore the error sl@0: * and most code should handle such errors ok. sl@0: */ sl@0: sl@0: #ifndef TCL_DEBUG sl@0: #define Debugger() sl@0: #endif sl@0: sl@0: /* sl@0: * The preferred buffer size for Macintosh channels. sl@0: */ sl@0: sl@0: #define CHANNEL_BUF_SIZE 8192 sl@0: sl@0: /* sl@0: * Port information structure. Used to match service names sl@0: * to a Tcp/Ip port number. sl@0: */ sl@0: sl@0: typedef struct { sl@0: char *name; /* Name of service. */ sl@0: int port; /* Port number. */ sl@0: } PortInfo; sl@0: sl@0: /* sl@0: * This structure describes per-instance state of a tcp based channel. sl@0: */ sl@0: sl@0: typedef struct TcpState { sl@0: TCPiopb pb; /* Parameter block used by this stream. sl@0: * This must be in the first position. */ sl@0: ProcessSerialNumber psn; /* PSN used to wake up process. */ sl@0: StreamPtr tcpStream; /* Macintosh tcp stream pointer. */ sl@0: int port; /* The port we are connected to. */ sl@0: int flags; /* Bit field comprised of the flags sl@0: * described below. */ sl@0: int checkMask; /* OR'ed combination of TCL_READABLE and sl@0: * TCL_WRITABLE as set by an asynchronous sl@0: * event handler. */ sl@0: int watchMask; /* OR'ed combination of TCL_READABLE and sl@0: * TCL_WRITABLE as set by TcpWatch. */ sl@0: Tcl_TcpAcceptProc *acceptProc; /* Proc to call on accept. */ sl@0: ClientData acceptProcData; /* The data for the accept proc. */ sl@0: wdsEntry dataSegment[2]; /* List of buffers to be written async. */ sl@0: rdsEntry rdsarray[5+1]; /* Array used when cleaning out recieve sl@0: * buffers on a closing socket. */ sl@0: Tcl_Channel channel; /* Channel associated with this socket. */ sl@0: int writeBufferSize; /* Size of buffer to hold data for sl@0: * asynchronous writes. */ sl@0: void *writeBuffer; /* Buffer for async write data. */ sl@0: struct TcpState *nextPtr; /* The next socket on the global socket sl@0: * list. */ sl@0: } TcpState; sl@0: sl@0: /* sl@0: * This structure is used by domain name resolver callback. sl@0: */ sl@0: sl@0: typedef struct DNRState { sl@0: struct hostInfo hostInfo; /* Data structure used by DNR functions. */ sl@0: int done; /* Flag to determine when we are done. */ sl@0: ProcessSerialNumber psn; /* Process to wake up when we are done. */ sl@0: } DNRState; sl@0: sl@0: /* sl@0: * The following macros may be used to set the flags field of sl@0: * a TcpState structure. sl@0: */ sl@0: sl@0: #define TCP_ASYNC_SOCKET (1<<0) /* The socket is in async mode. */ sl@0: #define TCP_ASYNC_CONNECT (1<<1) /* The socket is trying to connect. */ sl@0: #define TCP_CONNECTED (1<<2) /* The socket is connected. */ sl@0: #define TCP_PENDING (1<<3) /* A SocketEvent is on the queue. */ sl@0: #define TCP_LISTENING (1<<4) /* This socket is listening for sl@0: * a connection. */ sl@0: #define TCP_LISTEN_CONNECT (1<<5) /* Someone has connect to the sl@0: * listening port. */ sl@0: #define TCP_REMOTE_CLOSED (1<<6) /* The remote side has closed sl@0: * the connection. */ sl@0: #define TCP_RELEASE (1<<7) /* The socket may now be released. */ sl@0: #define TCP_WRITING (1<<8) /* A background write is in progress. */ sl@0: #define TCP_SERVER_ZOMBIE (1<<9) /* The server can no longer accept connects. */ sl@0: sl@0: /* sl@0: * The following structure is what is added to the Tcl event queue when sl@0: * a socket event occurs. sl@0: */ sl@0: sl@0: typedef struct SocketEvent { sl@0: Tcl_Event header; /* Information that is standard for sl@0: * all events. */ sl@0: TcpState *statePtr; /* Socket descriptor that is ready. */ sl@0: StreamPtr tcpStream; /* Low level Macintosh stream. */ sl@0: } SocketEvent; sl@0: sl@0: /* sl@0: * Static routines for this file: sl@0: */ sl@0: sl@0: static pascal void CleanUpExitProc _ANSI_ARGS_((void)); sl@0: static void ClearZombieSockets _ANSI_ARGS_((void)); sl@0: static void CloseCompletionRoutine _ANSI_ARGS_((TCPiopb *pb)); sl@0: static TcpState * CreateSocket _ANSI_ARGS_((Tcl_Interp *interp, sl@0: int port, CONST char *host, CONST char *myAddr, sl@0: int myPort, int server, int async)); sl@0: static pascal void DNRCompletionRoutine _ANSI_ARGS_(( sl@0: struct hostInfo *hostinfoPtr, sl@0: DNRState *dnrStatePtr)); sl@0: static void FreeSocketInfo _ANSI_ARGS_((TcpState *statePtr)); sl@0: static long GetBufferSize _ANSI_ARGS_((void)); sl@0: static OSErr GetHostFromString _ANSI_ARGS_((CONST char *name, sl@0: ip_addr *address)); sl@0: static OSErr GetLocalAddress _ANSI_ARGS_((unsigned long *addr)); sl@0: static void IOCompletionRoutine _ANSI_ARGS_((TCPiopb *pb)); sl@0: static void InitMacTCPParamBlock _ANSI_ARGS_((TCPiopb *pBlock, sl@0: int csCode)); sl@0: static void InitSockets _ANSI_ARGS_((void)); sl@0: static TcpState * NewSocketInfo _ANSI_ARGS_((StreamPtr stream)); sl@0: static OSErr ResolveAddress _ANSI_ARGS_((ip_addr tcpAddress, sl@0: Tcl_DString *dsPtr)); sl@0: static void SocketCheckProc _ANSI_ARGS_((ClientData clientData, sl@0: int flags)); sl@0: static int SocketEventProc _ANSI_ARGS_((Tcl_Event *evPtr, sl@0: int flags)); sl@0: static void SocketFreeProc _ANSI_ARGS_((ClientData clientData)); sl@0: static int SocketReady _ANSI_ARGS_((TcpState *statePtr)); sl@0: static void SocketSetupProc _ANSI_ARGS_((ClientData clientData, sl@0: int flags)); sl@0: static void TcpAccept _ANSI_ARGS_((TcpState *statePtr)); sl@0: static int TcpBlockMode _ANSI_ARGS_((ClientData instanceData, int mode)); sl@0: static int TcpClose _ANSI_ARGS_((ClientData instanceData, sl@0: Tcl_Interp *interp)); sl@0: static int TcpGetHandle _ANSI_ARGS_((ClientData instanceData, sl@0: int direction, ClientData *handlePtr)); sl@0: static int TcpGetOptionProc _ANSI_ARGS_((ClientData instanceData, sl@0: Tcl_Interp *interp, CONST char *optionName, sl@0: Tcl_DString *dsPtr)); sl@0: static int TcpInput _ANSI_ARGS_((ClientData instanceData, sl@0: char *buf, int toRead, int *errorCodePtr)); sl@0: static int TcpOutput _ANSI_ARGS_((ClientData instanceData, sl@0: CONST char *buf, int toWrite, int *errorCodePtr)); sl@0: static void TcpWatch _ANSI_ARGS_((ClientData instanceData, sl@0: int mask)); sl@0: static int WaitForSocketEvent _ANSI_ARGS_((TcpState *infoPtr, sl@0: int mask, int *errorCodePtr)); sl@0: sl@0: pascal void NotifyRoutine ( sl@0: StreamPtr tcpStream, sl@0: unsigned short eventCode, sl@0: Ptr userDataPtr, sl@0: unsigned short terminReason, sl@0: struct ICMPReport *icmpMsg); sl@0: sl@0: /* sl@0: * This structure describes the channel type structure for TCP socket sl@0: * based IO: sl@0: */ sl@0: sl@0: static Tcl_ChannelType tcpChannelType = { sl@0: "tcp", /* Type name. */ sl@0: (Tcl_ChannelTypeVersion)TcpBlockMode, /* Set blocking or sl@0: * non-blocking mode.*/ sl@0: TcpClose, /* Close proc. */ sl@0: TcpInput, /* Input proc. */ sl@0: TcpOutput, /* Output proc. */ sl@0: NULL, /* Seek proc. */ sl@0: NULL, /* Set option proc. */ sl@0: TcpGetOptionProc, /* Get option proc. */ sl@0: TcpWatch, /* Initialize notifier. */ sl@0: TcpGetHandle /* Get handles out of channel. */ sl@0: }; sl@0: sl@0: /* sl@0: * Universal Procedure Pointers (UPP) for various callback sl@0: * routines used by MacTcp code. sl@0: */ sl@0: sl@0: ResultUPP resultUPP = NULL; sl@0: TCPIOCompletionUPP completeUPP = NULL; sl@0: TCPIOCompletionUPP closeUPP = NULL; sl@0: TCPNotifyUPP notifyUPP = NULL; sl@0: sl@0: /* sl@0: * Built-in commands, and the procedures associated with them: sl@0: */ sl@0: sl@0: static PortInfo portServices[] = { sl@0: {"echo", 7}, sl@0: {"discard", 9}, sl@0: {"systat", 11}, sl@0: {"daytime", 13}, sl@0: {"netstat", 15}, sl@0: {"chargen", 19}, sl@0: {"ftp-data", 20}, sl@0: {"ftp", 21}, sl@0: {"telnet", 23}, sl@0: {"telneto", 24}, sl@0: {"smtp", 25}, sl@0: {"time", 37}, sl@0: {"whois", 43}, sl@0: {"domain", 53}, sl@0: {"gopher", 70}, sl@0: {"finger", 79}, sl@0: {"hostnames", 101}, sl@0: {"sunrpc", 111}, sl@0: {"nntp", 119}, sl@0: {"exec", 512}, sl@0: {"login", 513}, sl@0: {"shell", 514}, sl@0: {"printer", 515}, sl@0: {"courier", 530}, sl@0: {"uucp", 540}, sl@0: {NULL, 0}, sl@0: }; sl@0: sl@0: typedef struct ThreadSpecificData { sl@0: /* sl@0: * Every open socket has an entry on the following list. sl@0: */ sl@0: sl@0: TcpState *socketList; sl@0: } ThreadSpecificData; sl@0: sl@0: static Tcl_ThreadDataKey dataKey; sl@0: sl@0: /* sl@0: * Globals for holding information about OS support for sockets. sl@0: */ sl@0: sl@0: static int socketsTestInited = false; sl@0: static int hasSockets = false; sl@0: static short driverRefNum = 0; sl@0: static int socketNumber = 0; sl@0: static int socketBufferSize = CHANNEL_BUF_SIZE; sl@0: static ProcessSerialNumber applicationPSN; sl@0: sl@0: /* sl@0: *---------------------------------------------------------------------- sl@0: * sl@0: * InitSockets -- sl@0: * sl@0: * Load the MacTCP driver and open the name resolver. We also sl@0: * create several UPP's used by our code. Lastly, we install sl@0: * a patch to ExitToShell to clean up socket connections if sl@0: * we are about to exit. sl@0: * sl@0: * Results: sl@0: * 1 if successful, 0 on failure. sl@0: * sl@0: * Side effects: sl@0: * Creates a new event source, loads the MacTCP driver, sl@0: * registers an exit to shell callback. sl@0: * sl@0: *---------------------------------------------------------------------- sl@0: */ sl@0: sl@0: #define gestaltMacTCPVersion 'mtcp' sl@0: static void sl@0: InitSockets() sl@0: { sl@0: ParamBlockRec pb; sl@0: OSErr err; sl@0: long response; sl@0: ThreadSpecificData *tsdPtr; sl@0: sl@0: if (! initialized) { sl@0: /* sl@0: * Do process wide initialization. sl@0: */ sl@0: sl@0: initialized = 1; sl@0: sl@0: if (Gestalt(gestaltMacTCPVersion, &response) == noErr) { sl@0: hasSockets = true; sl@0: } else { sl@0: hasSockets = false; sl@0: } sl@0: sl@0: if (!hasSockets) { sl@0: return; sl@0: } sl@0: sl@0: /* sl@0: * Load MacTcp driver and name server resolver. sl@0: */ sl@0: sl@0: sl@0: pb.ioParam.ioCompletion = 0L; sl@0: pb.ioParam.ioNamePtr = "\p.IPP"; sl@0: pb.ioParam.ioPermssn = fsCurPerm; sl@0: err = PBOpenSync(&pb); sl@0: if (err != noErr) { sl@0: hasSockets = 0; sl@0: return; sl@0: } sl@0: driverRefNum = pb.ioParam.ioRefNum; sl@0: sl@0: socketBufferSize = GetBufferSize(); sl@0: err = OpenResolver(NULL); sl@0: if (err != noErr) { sl@0: hasSockets = 0; sl@0: return; sl@0: } sl@0: sl@0: GetCurrentProcess(&applicationPSN); sl@0: /* sl@0: * Create UPP's for various callback routines. sl@0: */ sl@0: sl@0: resultUPP = NewResultProc(DNRCompletionRoutine); sl@0: completeUPP = NewTCPIOCompletionProc(IOCompletionRoutine); sl@0: closeUPP = NewTCPIOCompletionProc(CloseCompletionRoutine); sl@0: notifyUPP = NewTCPNotifyProc(NotifyRoutine); sl@0: sl@0: /* sl@0: * Install an ExitToShell patch. We use this patch instead sl@0: * of the Tcl exit mechanism because we need to ensure that sl@0: * these routines are cleaned up even if we crash or are forced sl@0: * to quit. There are some circumstances when the Tcl exit sl@0: * handlers may not fire. sl@0: */ sl@0: sl@0: TclMacInstallExitToShellPatch(CleanUpExitProc); sl@0: } sl@0: sl@0: /* sl@0: * Do per-thread initialization. sl@0: */ sl@0: sl@0: tsdPtr = (ThreadSpecificData *)TclThreadDataKeyGet(&dataKey); sl@0: if (tsdPtr == NULL) { sl@0: tsdPtr = TCL_TSD_INIT(&dataKey); sl@0: tsdPtr->socketList = NULL; sl@0: Tcl_CreateEventSource(SocketSetupProc, SocketCheckProc, NULL); sl@0: } sl@0: } sl@0: sl@0: /* sl@0: *---------------------------------------------------------------------- sl@0: * sl@0: * TclpFinalizeSockets -- sl@0: * sl@0: * Invoked during exit clean up to deinitialize the socket module. sl@0: * sl@0: * Results: sl@0: * None. sl@0: * sl@0: * Side effects: sl@0: * Removed event source. sl@0: * sl@0: *---------------------------------------------------------------------- sl@0: */ sl@0: sl@0: void sl@0: TclpFinalizeSockets() sl@0: { sl@0: ThreadSpecificData *tsdPtr; sl@0: sl@0: tsdPtr = (ThreadSpecificData *)TclThreadDataKeyGet(&dataKey); sl@0: if (tsdPtr != NULL) { sl@0: Tcl_DeleteEventSource(SocketSetupProc, SocketCheckProc, NULL); sl@0: } sl@0: } sl@0: sl@0: /* sl@0: *---------------------------------------------------------------------- sl@0: * sl@0: * TclpHasSockets -- sl@0: * sl@0: * This function determines whether sockets are available on the sl@0: * current system and returns an error in interp if they are not. sl@0: * Note that interp may be NULL. sl@0: * sl@0: * Results: sl@0: * Returns TCL_OK if the system supports sockets, or TCL_ERROR with sl@0: * an error in interp. sl@0: * sl@0: * Side effects: sl@0: * None. sl@0: * sl@0: *---------------------------------------------------------------------- sl@0: */ sl@0: sl@0: int sl@0: TclpHasSockets( sl@0: Tcl_Interp *interp) /* Interp for error messages. */ sl@0: { sl@0: InitSockets(); sl@0: sl@0: if (hasSockets) { sl@0: return TCL_OK; sl@0: } sl@0: if (interp != NULL) { sl@0: Tcl_AppendResult(interp, "sockets are not available on this system", sl@0: NULL); sl@0: } sl@0: return TCL_ERROR; sl@0: } sl@0: sl@0: /* sl@0: *---------------------------------------------------------------------- sl@0: * sl@0: * SocketSetupProc -- sl@0: * sl@0: * This procedure is invoked before Tcl_DoOneEvent blocks waiting sl@0: * for an event. sl@0: * sl@0: * Results: sl@0: * None. sl@0: * sl@0: * Side effects: sl@0: * Adjusts the block time if needed. sl@0: * sl@0: *---------------------------------------------------------------------- sl@0: */ sl@0: sl@0: static void sl@0: SocketSetupProc( sl@0: ClientData data, /* Not used. */ sl@0: int flags) /* Event flags as passed to Tcl_DoOneEvent. */ sl@0: { sl@0: TcpState *statePtr; sl@0: Tcl_Time blockTime = { 0, 0 }; sl@0: ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); sl@0: sl@0: if (!(flags & TCL_FILE_EVENTS)) { sl@0: return; sl@0: } sl@0: sl@0: /* sl@0: * Check to see if there is a ready socket. If so, poll. sl@0: */ sl@0: sl@0: for (statePtr = tsdPtr->socketList; statePtr != NULL; sl@0: statePtr = statePtr->nextPtr) { sl@0: if (statePtr->flags & TCP_RELEASE) { sl@0: continue; sl@0: } sl@0: if (SocketReady(statePtr)) { sl@0: Tcl_SetMaxBlockTime(&blockTime); sl@0: break; sl@0: } sl@0: } sl@0: } sl@0: sl@0: /* sl@0: *---------------------------------------------------------------------- sl@0: * sl@0: * SocketCheckProc -- sl@0: * sl@0: * This procedure is called by Tcl_DoOneEvent to check the socket sl@0: * event source for events. sl@0: * sl@0: * Results: sl@0: * None. sl@0: * sl@0: * Side effects: sl@0: * May queue an event. sl@0: * sl@0: *---------------------------------------------------------------------- sl@0: */ sl@0: sl@0: static void sl@0: SocketCheckProc( sl@0: ClientData data, /* Not used. */ sl@0: int flags) /* Event flags as passed to Tcl_DoOneEvent. */ sl@0: { sl@0: TcpState *statePtr; sl@0: SocketEvent *evPtr; sl@0: TcpState dummyState; sl@0: ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); sl@0: sl@0: if (!(flags & TCL_FILE_EVENTS)) { sl@0: return; sl@0: } sl@0: sl@0: /* sl@0: * Queue events for any ready sockets that don't already have events sl@0: * queued (caused by persistent states that won't generate WinSock sl@0: * events). sl@0: */ sl@0: sl@0: for (statePtr = tsdPtr->socketList; statePtr != NULL; sl@0: statePtr = statePtr->nextPtr) { sl@0: /* sl@0: * Check to see if this socket is dead and needs to be cleaned sl@0: * up. We use a dummy statePtr whose only valid field is the sl@0: * nextPtr to allow the loop to continue even if the element sl@0: * is deleted. sl@0: */ sl@0: sl@0: if (statePtr->flags & TCP_RELEASE) { sl@0: if (!(statePtr->flags & TCP_PENDING)) { sl@0: dummyState.nextPtr = statePtr->nextPtr; sl@0: SocketFreeProc(statePtr); sl@0: statePtr = &dummyState; sl@0: } sl@0: continue; sl@0: } sl@0: sl@0: if (!(statePtr->flags & TCP_PENDING) && SocketReady(statePtr)) { sl@0: statePtr->flags |= TCP_PENDING; sl@0: evPtr = (SocketEvent *) ckalloc(sizeof(SocketEvent)); sl@0: evPtr->header.proc = SocketEventProc; sl@0: evPtr->statePtr = statePtr; sl@0: evPtr->tcpStream = statePtr->tcpStream; sl@0: Tcl_QueueEvent((Tcl_Event *) evPtr, TCL_QUEUE_TAIL); sl@0: } sl@0: } sl@0: } sl@0: sl@0: /* sl@0: *---------------------------------------------------------------------- sl@0: * sl@0: * SocketReady -- sl@0: * sl@0: * This function checks the current state of a socket to see sl@0: * if any interesting conditions are present. sl@0: * sl@0: * Results: sl@0: * Returns 1 if an event that someone is watching is present, else sl@0: * returns 0. sl@0: * sl@0: * Side effects: sl@0: * Updates the checkMask for the socket to reflect any newly sl@0: * detected events. sl@0: * sl@0: *---------------------------------------------------------------------- sl@0: */ sl@0: sl@0: static int sl@0: SocketReady( sl@0: TcpState *statePtr) sl@0: { sl@0: TCPiopb statusPB; sl@0: int foundSomething = 0; sl@0: int didStatus = 0; sl@0: int amount; sl@0: OSErr err; sl@0: sl@0: if (statePtr->flags & TCP_LISTEN_CONNECT) { sl@0: foundSomething = 1; sl@0: statePtr->checkMask |= TCL_READABLE; sl@0: } sl@0: if (statePtr->watchMask & TCL_READABLE) { sl@0: if (statePtr->checkMask & TCL_READABLE) { sl@0: foundSomething = 1; sl@0: } else if (statePtr->flags & TCP_CONNECTED) { sl@0: statusPB.ioCRefNum = driverRefNum; sl@0: statusPB.tcpStream = statePtr->tcpStream; sl@0: statusPB.csCode = TCPStatus; sl@0: err = PBControlSync((ParmBlkPtr) &statusPB); sl@0: didStatus = 1; sl@0: sl@0: /* sl@0: * We make the fchannel readable if 1) we get an error, sl@0: * 2) there is more data available, or 3) we detect sl@0: * that a close from the remote connection has arrived. sl@0: */ sl@0: sl@0: if ((err != noErr) || sl@0: (statusPB.csParam.status.amtUnreadData > 0) || sl@0: (statusPB.csParam.status.connectionState == 14)) { sl@0: statePtr->checkMask |= TCL_READABLE; sl@0: foundSomething = 1; sl@0: } sl@0: } sl@0: } sl@0: if (statePtr->watchMask & TCL_WRITABLE) { sl@0: if (statePtr->checkMask & TCL_WRITABLE) { sl@0: foundSomething = 1; sl@0: } else if (statePtr->flags & TCP_CONNECTED) { sl@0: if (!didStatus) { sl@0: statusPB.ioCRefNum = driverRefNum; sl@0: statusPB.tcpStream = statePtr->tcpStream; sl@0: statusPB.csCode = TCPStatus; sl@0: err = PBControlSync((ParmBlkPtr) &statusPB); sl@0: } sl@0: sl@0: /* sl@0: * If there is an error or there if there is room to sl@0: * send more data we make the channel writeable. sl@0: */ sl@0: sl@0: amount = statusPB.csParam.status.sendWindow - sl@0: statusPB.csParam.status.amtUnackedData; sl@0: if ((err != noErr) || (amount > 0)) { sl@0: statePtr->checkMask |= TCL_WRITABLE; sl@0: foundSomething = 1; sl@0: } sl@0: } sl@0: } sl@0: return foundSomething; sl@0: } sl@0: sl@0: /* sl@0: *---------------------------------------------------------------------- sl@0: * sl@0: * InitMacTCPParamBlock-- sl@0: * sl@0: * Initialize a MacTCP parameter block. sl@0: * sl@0: * Results: sl@0: * None. sl@0: * sl@0: * Side effects: sl@0: * Initializes the parameter block. sl@0: * sl@0: *---------------------------------------------------------------------- sl@0: */ sl@0: sl@0: static void sl@0: InitMacTCPParamBlock( sl@0: TCPiopb *pBlock, /* Tcp parmeter block. */ sl@0: int csCode) /* Tcp operation code. */ sl@0: { sl@0: memset(pBlock, 0, sizeof(TCPiopb)); sl@0: pBlock->ioResult = 1; sl@0: pBlock->ioCRefNum = driverRefNum; sl@0: pBlock->csCode = (short) csCode; sl@0: } sl@0: sl@0: /* sl@0: *---------------------------------------------------------------------- sl@0: * sl@0: * TcpBlockMode -- sl@0: * sl@0: * Set blocking or non-blocking mode on channel. sl@0: * sl@0: * Results: sl@0: * 0 if successful, errno when failed. sl@0: * sl@0: * Side effects: sl@0: * Sets the device into blocking or non-blocking mode. sl@0: * sl@0: *---------------------------------------------------------------------- sl@0: */ sl@0: sl@0: static int sl@0: TcpBlockMode( sl@0: ClientData instanceData, /* Channel state. */ sl@0: int mode) /* The mode to set. */ sl@0: { sl@0: TcpState *statePtr = (TcpState *) instanceData; sl@0: sl@0: if (mode == TCL_MODE_BLOCKING) { sl@0: statePtr->flags &= ~TCP_ASYNC_SOCKET; sl@0: } else { sl@0: statePtr->flags |= TCP_ASYNC_SOCKET; sl@0: } sl@0: return 0; sl@0: } sl@0: sl@0: /* sl@0: *---------------------------------------------------------------------- sl@0: * sl@0: * TcpClose -- sl@0: * sl@0: * Close the socket. sl@0: * sl@0: * Results: sl@0: * 0 if successful, the value of errno if failed. sl@0: * sl@0: * Side effects: sl@0: * Closes the socket. sl@0: * sl@0: *---------------------------------------------------------------------- sl@0: */ sl@0: sl@0: static int sl@0: TcpClose( sl@0: ClientData instanceData, /* The socket to close. */ sl@0: Tcl_Interp *interp) /* Interp for error messages. */ sl@0: { sl@0: TcpState *statePtr = (TcpState *) instanceData; sl@0: StreamPtr tcpStream; sl@0: TCPiopb closePB; sl@0: OSErr err; sl@0: sl@0: tcpStream = statePtr->tcpStream; sl@0: statePtr->flags &= ~TCP_CONNECTED; sl@0: sl@0: /* sl@0: * If this is a server socket we can't use the statePtr sl@0: * param block because it is in use. However, we can sl@0: * close syncronously. sl@0: */ sl@0: sl@0: if ((statePtr->flags & TCP_LISTENING) || sl@0: (statePtr->flags & TCP_LISTEN_CONNECT)) { sl@0: InitMacTCPParamBlock(&closePB, TCPClose); sl@0: closePB.tcpStream = tcpStream; sl@0: closePB.ioCompletion = NULL; sl@0: closePB.csParam.close.ulpTimeoutValue = 60 /* seconds */; sl@0: closePB.csParam.close.ulpTimeoutAction = 1 /* 1:abort 0:report */; sl@0: closePB.csParam.close.validityFlags = timeoutValue | timeoutAction; sl@0: err = PBControlSync((ParmBlkPtr) &closePB); sl@0: if (err != noErr) { sl@0: Debugger(); sl@0: goto afterRelease; sl@0: /* panic("error closing server socket"); */ sl@0: } sl@0: statePtr->flags |= TCP_RELEASE; sl@0: sl@0: /* sl@0: * Server sockets are closed sync. Therefor, we know it is OK to sl@0: * release the socket now. sl@0: */ sl@0: sl@0: InitMacTCPParamBlock(&statePtr->pb, TCPRelease); sl@0: statePtr->pb.tcpStream = statePtr->tcpStream; sl@0: err = PBControlSync((ParmBlkPtr) &statePtr->pb); sl@0: if (err != noErr) { sl@0: panic("error releasing server socket"); sl@0: } sl@0: sl@0: /* sl@0: * Free the buffer space used by the socket and the sl@0: * actual socket state data structure. sl@0: */ sl@0: afterRelease: sl@0: sl@0: /* sl@0: * Have to check whether the pointer is NULL, since we could get here sl@0: * on a failed socket open, and then the rcvBuff would never have been sl@0: * allocated. sl@0: */ sl@0: sl@0: if (err == noErr) { sl@0: ckfree((char *) statePtr->pb.csParam.create.rcvBuff); sl@0: } sl@0: FreeSocketInfo(statePtr); sl@0: return 0; sl@0: } sl@0: sl@0: /* sl@0: * If this socket is in the midddle on async connect we can just sl@0: * abort the connect and release the stream right now. sl@0: */ sl@0: sl@0: if (statePtr->flags & TCP_ASYNC_CONNECT) { sl@0: InitMacTCPParamBlock(&closePB, TCPClose); sl@0: closePB.tcpStream = tcpStream; sl@0: closePB.ioCompletion = NULL; sl@0: err = PBControlSync((ParmBlkPtr) &closePB); sl@0: if (err == noErr) { sl@0: statePtr->flags |= TCP_RELEASE; sl@0: sl@0: InitMacTCPParamBlock(&closePB, TCPRelease); sl@0: closePB.tcpStream = tcpStream; sl@0: closePB.ioCompletion = NULL; sl@0: sl@0: err = PBControlSync((ParmBlkPtr) &closePB); sl@0: } sl@0: sl@0: /* sl@0: * Free the buffer space used by the socket and the sl@0: * actual socket state data structure. However, if the sl@0: * RELEASE returns an error, then the rcvBuff is usually sl@0: * bad, so we can't release it. I think this means we will sl@0: * leak the buffer, so in the future, we may want to track the sl@0: * buffers separately, and nuke them on our own (or just not sl@0: * use MacTCP!). sl@0: */ sl@0: sl@0: if (err == noErr) { sl@0: ckfree((char *) closePB.csParam.create.rcvBuff); sl@0: } sl@0: sl@0: FreeSocketInfo(statePtr); sl@0: return err; sl@0: } sl@0: sl@0: /* sl@0: * Client sockets: sl@0: * If a background write is in progress, don't close sl@0: * the socket yet. The completion routine for the sl@0: * write will take care of it. sl@0: */ sl@0: sl@0: if (!(statePtr->flags & TCP_WRITING)) { sl@0: InitMacTCPParamBlock(&statePtr->pb, TCPClose); sl@0: statePtr->pb.tcpStream = tcpStream; sl@0: statePtr->pb.ioCompletion = closeUPP; sl@0: statePtr->pb.csParam.close.userDataPtr = (Ptr) statePtr; sl@0: err = PBControlAsync((ParmBlkPtr) &statePtr->pb); sl@0: if (err != noErr) { sl@0: Debugger(); sl@0: statePtr->flags |= TCP_RELEASE; sl@0: /* return 0; */ sl@0: } sl@0: } sl@0: sl@0: SocketFreeProc(instanceData); sl@0: return 0; sl@0: } sl@0: sl@0: /* sl@0: *---------------------------------------------------------------------- sl@0: * sl@0: * CloseCompletionRoutine -- sl@0: * sl@0: * Handles the close protocol for a Tcp socket. This will do sl@0: * a series of calls to release all data currently buffered for sl@0: * the socket. This is important to do to as it allows the remote sl@0: * connection to recieve and issue it's own close on the socket. sl@0: * Note that this function is running at interupt time and can't sl@0: * allocate memory or do much else except set state. sl@0: * sl@0: * Results: sl@0: * None. sl@0: * sl@0: * Side effects: sl@0: * The buffers for the socket are flushed. sl@0: * sl@0: *---------------------------------------------------------------------- sl@0: */ sl@0: sl@0: static void sl@0: CloseCompletionRoutine( sl@0: TCPiopb *pbPtr) /* Tcp parameter block. */ sl@0: { sl@0: TcpState *statePtr; sl@0: OSErr err; sl@0: sl@0: if (pbPtr->csCode == TCPClose) { sl@0: statePtr = (TcpState *) (pbPtr->csParam.close.userDataPtr); sl@0: } else { sl@0: statePtr = (TcpState *) (pbPtr->csParam.receive.userDataPtr); sl@0: } sl@0: sl@0: /* sl@0: * It's very bad if the statePtr is nNULL - we should probably panic... sl@0: */ sl@0: sl@0: if (statePtr == NULL) { sl@0: Debugger(); sl@0: return; sl@0: } sl@0: sl@0: WakeUpProcess(&statePtr->psn); sl@0: sl@0: /* sl@0: * If there is an error we assume the remote side has already sl@0: * close. We are done closing as soon as we decide that the sl@0: * remote connection has closed. sl@0: */ sl@0: sl@0: if (pbPtr->ioResult != noErr) { sl@0: statePtr->flags |= TCP_RELEASE; sl@0: return; sl@0: } sl@0: if (statePtr->flags & TCP_REMOTE_CLOSED) { sl@0: statePtr->flags |= TCP_RELEASE; sl@0: return; sl@0: } sl@0: sl@0: /* sl@0: * If we just did a recieve we need to return the buffers. sl@0: * Otherwise, attempt to recieve more data until we recieve an sl@0: * error (usually because we have no more data). sl@0: */ sl@0: sl@0: if (statePtr->pb.csCode == TCPNoCopyRcv) { sl@0: InitMacTCPParamBlock(&statePtr->pb, TCPRcvBfrReturn); sl@0: statePtr->pb.tcpStream = statePtr->tcpStream; sl@0: statePtr->pb.ioCompletion = closeUPP; sl@0: statePtr->pb.csParam.receive.rdsPtr = (Ptr) statePtr->rdsarray; sl@0: statePtr->pb.csParam.receive.userDataPtr = (Ptr) statePtr; sl@0: err = PBControlAsync((ParmBlkPtr) &statePtr->pb); sl@0: } else { sl@0: InitMacTCPParamBlock(&statePtr->pb, TCPNoCopyRcv); sl@0: statePtr->pb.tcpStream = statePtr->tcpStream; sl@0: statePtr->pb.ioCompletion = closeUPP; sl@0: statePtr->pb.csParam.receive.commandTimeoutValue = 1; sl@0: statePtr->pb.csParam.receive.rdsPtr = (Ptr) statePtr->rdsarray; sl@0: statePtr->pb.csParam.receive.rdsLength = 5; sl@0: statePtr->pb.csParam.receive.userDataPtr = (Ptr) statePtr; sl@0: err = PBControlAsync((ParmBlkPtr) &statePtr->pb); sl@0: } sl@0: sl@0: if (err != noErr) { sl@0: statePtr->flags |= TCP_RELEASE; sl@0: } sl@0: } sl@0: /* sl@0: *---------------------------------------------------------------------- sl@0: * sl@0: * SocketFreeProc -- sl@0: * sl@0: * This callback is invoked in order to delete sl@0: * the notifier data associated with a file handle. sl@0: * sl@0: * Results: sl@0: * None. sl@0: * sl@0: * Side effects: sl@0: * Removes the SocketInfo from the global socket list. sl@0: * sl@0: *---------------------------------------------------------------------- sl@0: */ sl@0: sl@0: static void sl@0: SocketFreeProc( sl@0: ClientData clientData) /* Channel state. */ sl@0: { sl@0: TcpState *statePtr = (TcpState *) clientData; sl@0: OSErr err; sl@0: TCPiopb statusPB; sl@0: sl@0: /* sl@0: * Get the status of this connection. We need to do a sl@0: * few tests to see if it's OK to release the stream now. sl@0: */ sl@0: sl@0: if (!(statePtr->flags & TCP_RELEASE)) { sl@0: return; sl@0: } sl@0: statusPB.ioCRefNum = driverRefNum; sl@0: statusPB.tcpStream = statePtr->tcpStream; sl@0: statusPB.csCode = TCPStatus; sl@0: err = PBControlSync((ParmBlkPtr) &statusPB); sl@0: if ((statusPB.csParam.status.connectionState == 0) || sl@0: (statusPB.csParam.status.connectionState == 2)) { sl@0: /* sl@0: * If the conection state is 0 then this was a client sl@0: * connection and it's closed. If it is 2 then this a sl@0: * server client and we may release it. If it isn't sl@0: * one of those values then we return and we'll try to sl@0: * clean up later. sl@0: */ sl@0: sl@0: } else { sl@0: return; sl@0: } sl@0: sl@0: /* sl@0: * The Close request is made async. We know it's sl@0: * OK to release the socket when the TCP_RELEASE flag sl@0: * gets set. sl@0: */ sl@0: sl@0: InitMacTCPParamBlock(&statePtr->pb, TCPRelease); sl@0: statePtr->pb.tcpStream = statePtr->tcpStream; sl@0: err = PBControlSync((ParmBlkPtr) &statePtr->pb); sl@0: if (err != noErr) { sl@0: Debugger(); /* Ignoreing leaves stranded stream. Is there an sl@0: alternative? */ sl@0: } sl@0: sl@0: /* sl@0: * Free the buffer space used by the socket and the sl@0: * actual socket state data structure. sl@0: */ sl@0: sl@0: ckfree((char *) statePtr->pb.csParam.create.rcvBuff); sl@0: FreeSocketInfo(statePtr); sl@0: } sl@0: sl@0: /* sl@0: *---------------------------------------------------------------------- sl@0: * sl@0: * TcpInput -- sl@0: * sl@0: * Reads input from the IO channel into the buffer given. Returns sl@0: * count of how many bytes were actually read, and an error sl@0: * indication. sl@0: * sl@0: * Results: sl@0: * A count of how many bytes were read is returned. A value of -1 sl@0: * implies an error occured. A value of zero means we have reached sl@0: * the end of data (EOF). sl@0: * sl@0: * Side effects: sl@0: * Reads input from the actual channel. sl@0: * sl@0: *---------------------------------------------------------------------- sl@0: */ sl@0: sl@0: int sl@0: TcpInput( sl@0: ClientData instanceData, /* Channel state. */ sl@0: char *buf, /* Where to store data read. */ sl@0: int bufSize, /* How much space is available sl@0: * in the buffer? */ sl@0: int *errorCodePtr) /* Where to store error code. */ sl@0: { sl@0: TcpState *statePtr = (TcpState *) instanceData; sl@0: StreamPtr tcpStream; sl@0: OSErr err; sl@0: TCPiopb statusPB; sl@0: int toRead, dataAvail; sl@0: sl@0: *errorCodePtr = 0; sl@0: errno = 0; sl@0: tcpStream = statePtr->tcpStream; sl@0: sl@0: if (bufSize == 0) { sl@0: return 0; sl@0: } sl@0: toRead = bufSize; sl@0: sl@0: /* sl@0: * First check to see if EOF was already detected, to prevent sl@0: * calling the socket stack after the first time EOF is detected. sl@0: */ sl@0: sl@0: if (statePtr->flags & TCP_REMOTE_CLOSED) { sl@0: return 0; sl@0: } sl@0: sl@0: /* sl@0: * If an asynchronous connect is in progress, attempt to wait for it sl@0: * to complete before reading. sl@0: */ sl@0: sl@0: if ((statePtr->flags & TCP_ASYNC_CONNECT) sl@0: && ! WaitForSocketEvent(statePtr, TCL_READABLE, errorCodePtr)) { sl@0: return -1; sl@0: } sl@0: sl@0: /* sl@0: * No EOF, and it is connected, so try to read more from the socket. sl@0: * If the socket is blocking, we keep trying until there is data sl@0: * available or the socket is closed. sl@0: */ sl@0: sl@0: while (1) { sl@0: sl@0: statusPB.ioCRefNum = driverRefNum; sl@0: statusPB.tcpStream = tcpStream; sl@0: statusPB.csCode = TCPStatus; sl@0: err = PBControlSync((ParmBlkPtr) &statusPB); sl@0: if (err != noErr) { sl@0: Debugger(); sl@0: statePtr->flags |= TCP_REMOTE_CLOSED; sl@0: return 0; /* EOF */ sl@0: } sl@0: dataAvail = statusPB.csParam.status.amtUnreadData; sl@0: if (dataAvail < bufSize) { sl@0: toRead = dataAvail; sl@0: } else { sl@0: toRead = bufSize; sl@0: } sl@0: if (toRead != 0) { sl@0: /* sl@0: * Try to read the data. sl@0: */ sl@0: sl@0: InitMacTCPParamBlock(&statusPB, TCPRcv); sl@0: statusPB.tcpStream = tcpStream; sl@0: statusPB.csParam.receive.rcvBuff = buf; sl@0: statusPB.csParam.receive.rcvBuffLen = toRead; sl@0: err = PBControlSync((ParmBlkPtr) &statusPB); sl@0: sl@0: statePtr->checkMask &= ~TCL_READABLE; sl@0: switch (err) { sl@0: case noErr: sl@0: /* sl@0: * The channel remains readable only if this read succeds sl@0: * and we had more data then the size of the buffer we were sl@0: * trying to fill. Use the info from the call to status to sl@0: * determine this. sl@0: */ sl@0: sl@0: if (dataAvail > bufSize) { sl@0: statePtr->checkMask |= TCL_READABLE; sl@0: } sl@0: return statusPB.csParam.receive.rcvBuffLen; sl@0: case connectionClosing: sl@0: *errorCodePtr = errno = ESHUTDOWN; sl@0: statePtr->flags |= TCP_REMOTE_CLOSED; sl@0: return 0; sl@0: case connectionDoesntExist: sl@0: case connectionTerminated: sl@0: *errorCodePtr = errno = ENOTCONN; sl@0: statePtr->flags |= TCP_REMOTE_CLOSED; sl@0: return 0; sl@0: case invalidStreamPtr: sl@0: default: sl@0: *errorCodePtr = EINVAL; sl@0: return -1; sl@0: } sl@0: } sl@0: sl@0: /* sl@0: * No data is available, so check the connection state to sl@0: * see why this is the case. sl@0: */ sl@0: sl@0: if (statusPB.csParam.status.connectionState == 14) { sl@0: statePtr->flags |= TCP_REMOTE_CLOSED; sl@0: return 0; sl@0: } sl@0: if (statusPB.csParam.status.connectionState != 8) { sl@0: Debugger(); sl@0: } sl@0: statePtr->checkMask &= ~TCL_READABLE; sl@0: if (statePtr->flags & TCP_ASYNC_SOCKET) { sl@0: *errorCodePtr = EWOULDBLOCK; sl@0: return -1; sl@0: } sl@0: sl@0: /* sl@0: * In the blocking case, wait until the file becomes readable sl@0: * or closed and try again. sl@0: */ sl@0: sl@0: if (!WaitForSocketEvent(statePtr, TCL_READABLE, errorCodePtr)) { sl@0: return -1; sl@0: } sl@0: } sl@0: } sl@0: sl@0: /* sl@0: *---------------------------------------------------------------------- sl@0: * sl@0: * TcpGetHandle -- sl@0: * sl@0: * Called from Tcl_GetChannelHandle to retrieve handles from inside sl@0: * a file based channel. sl@0: * sl@0: * Results: sl@0: * The appropriate handle or NULL if not present. sl@0: * sl@0: * Side effects: sl@0: * None. sl@0: * sl@0: *---------------------------------------------------------------------- sl@0: */ sl@0: sl@0: static int sl@0: TcpGetHandle( sl@0: ClientData instanceData, /* The file state. */ sl@0: int direction, /* Which handle to retrieve? */ sl@0: ClientData *handlePtr) sl@0: { sl@0: TcpState *statePtr = (TcpState *) instanceData; sl@0: sl@0: *handlePtr = (ClientData) statePtr->tcpStream; sl@0: return TCL_OK; sl@0: } sl@0: sl@0: /* sl@0: *---------------------------------------------------------------------- sl@0: * sl@0: * TcpOutput-- sl@0: * sl@0: * Writes the given output on the IO channel. Returns count of how sl@0: * many characters were actually written, and an error indication. sl@0: * sl@0: * Results: sl@0: * A count of how many characters were written is returned and an sl@0: * error indication is returned in an output argument. sl@0: * sl@0: * Side effects: sl@0: * Writes output on the actual channel. sl@0: * sl@0: *---------------------------------------------------------------------- sl@0: */ sl@0: sl@0: static int sl@0: TcpOutput( sl@0: ClientData instanceData, /* Channel state. */ sl@0: CONST char *buf, /* The data buffer. */ sl@0: int toWrite, /* How many bytes to write? */ sl@0: int *errorCodePtr) /* Where to store error code. */ sl@0: { sl@0: TcpState *statePtr = (TcpState *) instanceData; sl@0: StreamPtr tcpStream; sl@0: OSErr err; sl@0: int amount; sl@0: TCPiopb statusPB; sl@0: sl@0: *errorCodePtr = 0; sl@0: tcpStream = statePtr->tcpStream; sl@0: sl@0: /* sl@0: * If an asynchronous connect is in progress, attempt to wait for it sl@0: * to complete before writing. sl@0: */ sl@0: sl@0: if ((statePtr->flags & TCP_ASYNC_CONNECT) sl@0: && ! WaitForSocketEvent(statePtr, TCL_WRITABLE, errorCodePtr)) { sl@0: return -1; sl@0: } sl@0: sl@0: /* sl@0: * Loop until we have written some data, or an error occurs. sl@0: */ sl@0: sl@0: while (1) { sl@0: statusPB.ioCRefNum = driverRefNum; sl@0: statusPB.tcpStream = tcpStream; sl@0: statusPB.csCode = TCPStatus; sl@0: err = PBControlSync((ParmBlkPtr) &statusPB); sl@0: if ((err == connectionDoesntExist) || ((err == noErr) && sl@0: (statusPB.csParam.status.connectionState == 14))) { sl@0: /* sl@0: * The remote connection is gone away. Report an error sl@0: * and don't write anything. sl@0: */ sl@0: sl@0: *errorCodePtr = errno = EPIPE; sl@0: return -1; sl@0: } else if (err != noErr) { sl@0: return -1; sl@0: } sl@0: amount = statusPB.csParam.status.sendWindow sl@0: - statusPB.csParam.status.amtUnackedData; sl@0: sl@0: /* sl@0: * Attempt to write the data to the socket if a background sl@0: * write isn't in progress and there is room in the output buffers. sl@0: */ sl@0: sl@0: if (!(statePtr->flags & TCP_WRITING) && amount > 0) { sl@0: if (toWrite < amount) { sl@0: amount = toWrite; sl@0: } sl@0: sl@0: /* We need to copy the data, otherwise the caller may overwrite sl@0: * the buffer in the middle of our asynchronous call sl@0: */ sl@0: sl@0: if (amount > statePtr->writeBufferSize) { sl@0: /* sl@0: * need to grow write buffer sl@0: */ sl@0: sl@0: if (statePtr->writeBuffer != (void *) NULL) { sl@0: ckfree(statePtr->writeBuffer); sl@0: } sl@0: statePtr->writeBuffer = (void *) ckalloc(amount); sl@0: statePtr->writeBufferSize = amount; sl@0: } sl@0: memcpy(statePtr->writeBuffer, buf, amount); sl@0: statePtr->dataSegment[0].ptr = statePtr->writeBuffer; sl@0: sl@0: statePtr->dataSegment[0].length = amount; sl@0: statePtr->dataSegment[1].length = 0; sl@0: InitMacTCPParamBlock(&statePtr->pb, TCPSend); sl@0: statePtr->pb.ioCompletion = completeUPP; sl@0: statePtr->pb.tcpStream = tcpStream; sl@0: statePtr->pb.csParam.send.wdsPtr = (Ptr) statePtr->dataSegment; sl@0: statePtr->pb.csParam.send.pushFlag = 1; sl@0: statePtr->pb.csParam.send.userDataPtr = (Ptr) statePtr; sl@0: statePtr->flags |= TCP_WRITING; sl@0: err = PBControlAsync((ParmBlkPtr) &(statePtr->pb)); sl@0: switch (err) { sl@0: case noErr: sl@0: return amount; sl@0: case connectionClosing: sl@0: *errorCodePtr = errno = ESHUTDOWN; sl@0: statePtr->flags |= TCP_REMOTE_CLOSED; sl@0: return -1; sl@0: case connectionDoesntExist: sl@0: case connectionTerminated: sl@0: *errorCodePtr = errno = ENOTCONN; sl@0: statePtr->flags |= TCP_REMOTE_CLOSED; sl@0: return -1; sl@0: case invalidStreamPtr: sl@0: default: sl@0: return -1; sl@0: } sl@0: sl@0: } sl@0: sl@0: /* sl@0: * The socket wasn't writable. In the non-blocking case, return sl@0: * immediately, otherwise wait until the file becomes writable sl@0: * or closed and try again. sl@0: */ sl@0: sl@0: if (statePtr->flags & TCP_ASYNC_SOCKET) { sl@0: statePtr->checkMask &= ~TCL_WRITABLE; sl@0: *errorCodePtr = EWOULDBLOCK; sl@0: return -1; sl@0: } else if (!WaitForSocketEvent(statePtr, TCL_WRITABLE, errorCodePtr)) { sl@0: return -1; sl@0: } sl@0: } sl@0: } sl@0: sl@0: /* sl@0: *---------------------------------------------------------------------- sl@0: * sl@0: * TcpGetOptionProc -- sl@0: * sl@0: * Computes an option value for a TCP socket based channel, or a sl@0: * list of all options and their values. sl@0: * sl@0: * Note: This code is based on code contributed by John Haxby. sl@0: * sl@0: * Results: sl@0: * A standard Tcl result. The value of the specified option or a sl@0: * list of all options and their values is returned in the sl@0: * supplied DString. sl@0: * sl@0: * Side effects: sl@0: * None. sl@0: * sl@0: *---------------------------------------------------------------------- sl@0: */ sl@0: sl@0: static int sl@0: TcpGetOptionProc( sl@0: ClientData instanceData, /* Socket state. */ sl@0: Tcl_Interp *interp, /* For error reporting - can be NULL.*/ sl@0: CONST char *optionName, /* Name of the option to sl@0: * retrieve the value for, or sl@0: * NULL to get all options and sl@0: * their values. */ sl@0: Tcl_DString *dsPtr) /* Where to store the computed sl@0: * value; initialized by caller. */ sl@0: { sl@0: TcpState *statePtr = (TcpState *) instanceData; sl@0: int doPeerName = false, doSockName = false, doError = false, doAll = false; sl@0: ip_addr tcpAddress; sl@0: char buffer[128]; sl@0: OSErr err; sl@0: Tcl_DString dString; sl@0: TCPiopb statusPB; sl@0: int errorCode; sl@0: size_t len = 0; sl@0: sl@0: /* sl@0: * If an asynchronous connect is in progress, attempt to wait for it sl@0: * to complete before accessing the socket state. sl@0: */ sl@0: sl@0: if ((statePtr->flags & TCP_ASYNC_CONNECT) sl@0: && ! WaitForSocketEvent(statePtr, TCL_WRITABLE, &errorCode)) { sl@0: if (interp) { sl@0: /* sl@0: * fix the error message. sl@0: */ sl@0: sl@0: Tcl_AppendResult(interp, "connect is in progress and can't wait", sl@0: NULL); sl@0: } sl@0: return TCL_ERROR; sl@0: } sl@0: sl@0: /* sl@0: * Determine which options we need to do. Do all of them sl@0: * if optionName is NULL. sl@0: */ sl@0: sl@0: if (optionName == (CONST char *) NULL || optionName[0] == '\0') { sl@0: doAll = true; sl@0: } else { sl@0: len = strlen(optionName); sl@0: if (!strncmp(optionName, "-peername", len)) { sl@0: doPeerName = true; sl@0: } else if (!strncmp(optionName, "-sockname", len)) { sl@0: doSockName = true; sl@0: } else if (!strncmp(optionName, "-error", len)) { sl@0: /* SF Bug #483575 */ sl@0: doError = true; sl@0: } else { sl@0: return Tcl_BadChannelOption(interp, optionName, sl@0: "error peername sockname"); sl@0: } sl@0: } sl@0: sl@0: /* sl@0: * SF Bug #483575 sl@0: * sl@0: * Return error information. Currently we ignore sl@0: * this option. IOW, we always return the empty sl@0: * string, signaling 'no error'. sl@0: * sl@0: * FIXME: Get a mac/socket expert to write a correct sl@0: * FIXME: implementation. sl@0: */ sl@0: sl@0: if (doAll || doError) { sl@0: if (doAll) { sl@0: Tcl_DStringAppendElement(dsPtr, "-error"); sl@0: Tcl_DStringAppendElement(dsPtr, ""); sl@0: } else { sl@0: Tcl_DStringAppend (dsPtr, "", -1); sl@0: return TCL_OK; sl@0: } sl@0: } sl@0: sl@0: /* sl@0: * Get status on the stream. Make sure to use a new pb struct because sl@0: * the struct in the statePtr may be part of an asyncronous call. sl@0: */ sl@0: sl@0: statusPB.ioCRefNum = driverRefNum; sl@0: statusPB.tcpStream = statePtr->tcpStream; sl@0: statusPB.csCode = TCPStatus; sl@0: err = PBControlSync((ParmBlkPtr) &statusPB); sl@0: if ((err == connectionDoesntExist) || sl@0: ((err == noErr) && (statusPB.csParam.status.connectionState == 14))) { sl@0: /* sl@0: * The socket was probably closed on the other side of the connection. sl@0: */ sl@0: sl@0: if (interp) { sl@0: Tcl_AppendResult(interp, "can't access socket info: ", sl@0: "connection reset by peer", NULL); sl@0: } sl@0: return TCL_ERROR; sl@0: } else if (err != noErr) { sl@0: if (interp) { sl@0: Tcl_AppendResult(interp, "unknown socket error", NULL); sl@0: } sl@0: Debugger(); sl@0: return TCL_ERROR; sl@0: } sl@0: sl@0: sl@0: /* sl@0: * Get the sockname for the socket. sl@0: */ sl@0: sl@0: Tcl_DStringInit(&dString); sl@0: if (doAll || doSockName) { sl@0: if (doAll) { sl@0: Tcl_DStringAppendElement(dsPtr, "-sockname"); sl@0: Tcl_DStringStartSublist(dsPtr); sl@0: } sl@0: tcpAddress = statusPB.csParam.status.localHost; sl@0: sprintf(buffer, "%d.%d.%d.%d", tcpAddress>>24, sl@0: tcpAddress>>16 & 0xff, tcpAddress>>8 & 0xff, sl@0: tcpAddress & 0xff); sl@0: Tcl_DStringAppendElement(dsPtr, buffer); sl@0: if (ResolveAddress(tcpAddress, &dString) == noErr) { sl@0: Tcl_DStringAppendElement(dsPtr, dString.string); sl@0: } else { sl@0: Tcl_DStringAppendElement(dsPtr, ""); sl@0: } sl@0: sprintf(buffer, "%d", statusPB.csParam.status.localPort); sl@0: Tcl_DStringAppendElement(dsPtr, buffer); sl@0: if (doAll) { sl@0: Tcl_DStringEndSublist(dsPtr); sl@0: } sl@0: } sl@0: sl@0: /* sl@0: * Get the peername for the socket. sl@0: */ sl@0: sl@0: if ((doAll || doPeerName) && (statePtr->flags & TCP_CONNECTED)) { sl@0: if (doAll) { sl@0: Tcl_DStringAppendElement(dsPtr, "-peername"); sl@0: Tcl_DStringStartSublist(dsPtr); sl@0: } sl@0: tcpAddress = statusPB.csParam.status.remoteHost; sl@0: sprintf(buffer, "%d.%d.%d.%d", tcpAddress>>24, sl@0: tcpAddress>>16 & 0xff, tcpAddress>>8 & 0xff, sl@0: tcpAddress & 0xff); sl@0: Tcl_DStringAppendElement(dsPtr, buffer); sl@0: Tcl_DStringSetLength(&dString, 0); sl@0: if (ResolveAddress(tcpAddress, &dString) == noErr) { sl@0: Tcl_DStringAppendElement(dsPtr, dString.string); sl@0: } else { sl@0: Tcl_DStringAppendElement(dsPtr, ""); sl@0: } sl@0: sprintf(buffer, "%d", statusPB.csParam.status.remotePort); sl@0: Tcl_DStringAppendElement(dsPtr, buffer); sl@0: if (doAll) { sl@0: Tcl_DStringEndSublist(dsPtr); sl@0: } sl@0: } sl@0: sl@0: Tcl_DStringFree(&dString); sl@0: return TCL_OK; sl@0: } sl@0: sl@0: /* sl@0: *---------------------------------------------------------------------- sl@0: * sl@0: * TcpWatch -- sl@0: * sl@0: * Initialize the notifier to watch this channel. sl@0: * sl@0: * Results: sl@0: * None. sl@0: * sl@0: * Side effects: sl@0: * Sets the watchMask for the channel. sl@0: * sl@0: *---------------------------------------------------------------------- sl@0: */ sl@0: sl@0: static void sl@0: TcpWatch(instanceData, mask) sl@0: ClientData instanceData; /* The file state. */ sl@0: int mask; /* Events of interest; an OR-ed sl@0: * combination of TCL_READABLE, sl@0: * TCL_WRITABLE and TCL_EXCEPTION. */ sl@0: { sl@0: TcpState *statePtr = (TcpState *) instanceData; sl@0: sl@0: statePtr->watchMask = mask; sl@0: } sl@0: sl@0: /* sl@0: *---------------------------------------------------------------------- sl@0: * sl@0: * NewSocketInfo -- sl@0: * sl@0: * This function allocates and initializes a new SocketInfo sl@0: * structure. sl@0: * sl@0: * Results: sl@0: * Returns a newly allocated SocketInfo. sl@0: * sl@0: * Side effects: sl@0: * Adds the socket to the global socket list, allocates memory. sl@0: * sl@0: *---------------------------------------------------------------------- sl@0: */ sl@0: sl@0: static TcpState * sl@0: NewSocketInfo( sl@0: StreamPtr tcpStream) sl@0: { sl@0: TcpState *statePtr; sl@0: ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); sl@0: sl@0: statePtr = (TcpState *) ckalloc((unsigned) sizeof(TcpState)); sl@0: statePtr->tcpStream = tcpStream; sl@0: statePtr->psn = applicationPSN; sl@0: statePtr->flags = 0; sl@0: statePtr->checkMask = 0; sl@0: statePtr->watchMask = 0; sl@0: statePtr->acceptProc = (Tcl_TcpAcceptProc *) NULL; sl@0: statePtr->acceptProcData = (ClientData) NULL; sl@0: statePtr->writeBuffer = (void *) NULL; sl@0: statePtr->writeBufferSize = 0; sl@0: statePtr->nextPtr = tsdPtr->socketList; sl@0: tsdPtr->socketList = statePtr; sl@0: return statePtr; sl@0: } sl@0: sl@0: /* sl@0: *---------------------------------------------------------------------- sl@0: * sl@0: * FreeSocketInfo -- sl@0: * sl@0: * This function deallocates a SocketInfo structure that is no sl@0: * longer needed. sl@0: * sl@0: * Results: sl@0: * None. sl@0: * sl@0: * Side effects: sl@0: * Removes the socket from the global socket list, frees memory. sl@0: * sl@0: *---------------------------------------------------------------------- sl@0: */ sl@0: sl@0: static void sl@0: FreeSocketInfo( sl@0: TcpState *statePtr) /* The state pointer to free. */ sl@0: { sl@0: ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); sl@0: sl@0: if (statePtr == tsdPtr->socketList) { sl@0: tsdPtr->socketList = statePtr->nextPtr; sl@0: } else { sl@0: TcpState *p; sl@0: for (p = tsdPtr->socketList; p != NULL; p = p->nextPtr) { sl@0: if (p->nextPtr == statePtr) { sl@0: p->nextPtr = statePtr->nextPtr; sl@0: break; sl@0: } sl@0: } sl@0: } sl@0: sl@0: if (statePtr->writeBuffer != (void *) NULL) { sl@0: ckfree(statePtr->writeBuffer); sl@0: } sl@0: sl@0: ckfree((char *) statePtr); sl@0: } sl@0: sl@0: /* sl@0: *---------------------------------------------------------------------- sl@0: * sl@0: * Tcl_MakeTcpClientChannel -- sl@0: * sl@0: * Creates a Tcl_Channel from an existing client TCP socket. sl@0: * sl@0: * Results: sl@0: * The Tcl_Channel wrapped around the preexisting TCP socket. sl@0: * sl@0: * Side effects: sl@0: * None. sl@0: * sl@0: *---------------------------------------------------------------------- sl@0: */ sl@0: sl@0: Tcl_Channel sl@0: Tcl_MakeTcpClientChannel( sl@0: ClientData sock) /* The socket to wrap up into a channel. */ sl@0: { sl@0: TcpState *statePtr; sl@0: char channelName[20]; sl@0: sl@0: if (TclpHasSockets(NULL) != TCL_OK) { sl@0: return NULL; sl@0: } sl@0: sl@0: statePtr = NewSocketInfo((StreamPtr) sock); sl@0: /* TODO: do we need to set the port??? */ sl@0: sl@0: sprintf(channelName, "sock%d", socketNumber++); sl@0: sl@0: statePtr->channel = Tcl_CreateChannel(&tcpChannelType, channelName, sl@0: (ClientData) statePtr, (TCL_READABLE | TCL_WRITABLE)); sl@0: Tcl_SetChannelBufferSize(statePtr->channel, socketBufferSize); sl@0: Tcl_SetChannelOption(NULL, statePtr->channel, "-translation", "auto crlf"); sl@0: return statePtr->channel; sl@0: } sl@0: sl@0: /* sl@0: *---------------------------------------------------------------------- sl@0: * sl@0: * CreateSocket -- sl@0: * sl@0: * This function opens a new socket and initializes the sl@0: * SocketInfo structure. sl@0: * sl@0: * Results: sl@0: * Returns a new SocketInfo, or NULL with an error in interp. sl@0: * sl@0: * Side effects: sl@0: * Adds a new socket to the socketList. sl@0: * sl@0: *---------------------------------------------------------------------- sl@0: */ sl@0: sl@0: static TcpState * sl@0: CreateSocket( sl@0: Tcl_Interp *interp, /* For error reporting; can be NULL. */ sl@0: int port, /* Port number to open. */ sl@0: CONST char *host, /* Name of host on which to open port. */ sl@0: CONST char *myaddr, /* Optional client-side address */ sl@0: int myport, /* Optional client-side port */ sl@0: int server, /* 1 if socket should be a server socket, sl@0: * else 0 for a client socket. */ sl@0: int async) /* 1 create async, 0 do sync. */ sl@0: { sl@0: ip_addr macAddr; sl@0: OSErr err; sl@0: TCPiopb pb; sl@0: StreamPtr tcpStream; sl@0: TcpState *statePtr; sl@0: char * buffer; sl@0: sl@0: /* sl@0: * Figure out the ip address from the host string. sl@0: */ sl@0: sl@0: if (host == NULL) { sl@0: err = GetLocalAddress(&macAddr); sl@0: } else { sl@0: err = GetHostFromString(host, &macAddr); sl@0: } sl@0: if (err != noErr) { sl@0: Tcl_SetErrno(EHOSTUNREACH); sl@0: if (interp != (Tcl_Interp *) NULL) { sl@0: Tcl_AppendResult(interp, "couldn't open socket: ", sl@0: Tcl_PosixError(interp), (char *) NULL); sl@0: } sl@0: return (TcpState *) NULL; sl@0: } sl@0: sl@0: /* sl@0: * Create a MacTCP stream and create the state used for socket sl@0: * transactions from here on out. sl@0: */ sl@0: sl@0: ClearZombieSockets(); sl@0: buffer = ckalloc(socketBufferSize); sl@0: InitMacTCPParamBlock(&pb, TCPCreate); sl@0: pb.csParam.create.rcvBuff = buffer; sl@0: pb.csParam.create.rcvBuffLen = socketBufferSize; sl@0: pb.csParam.create.notifyProc = nil /* notifyUPP */; sl@0: err = PBControlSync((ParmBlkPtr) &pb); sl@0: if (err != noErr) { sl@0: Tcl_SetErrno(0); /* TODO: set to ENOSR - maybe?*/ sl@0: if (interp != (Tcl_Interp *) NULL) { sl@0: Tcl_AppendResult(interp, "couldn't open socket: ", sl@0: Tcl_PosixError(interp), (char *) NULL); sl@0: } sl@0: return (TcpState *) NULL; sl@0: } sl@0: sl@0: tcpStream = pb.tcpStream; sl@0: statePtr = NewSocketInfo(tcpStream); sl@0: statePtr->port = port; sl@0: sl@0: if (server) { sl@0: /* sl@0: * Set up server connection. sl@0: */ sl@0: sl@0: InitMacTCPParamBlock(&statePtr->pb, TCPPassiveOpen); sl@0: statePtr->pb.tcpStream = tcpStream; sl@0: statePtr->pb.csParam.open.localPort = statePtr->port; sl@0: statePtr->pb.ioCompletion = completeUPP; sl@0: statePtr->pb.csParam.open.userDataPtr = (Ptr) statePtr; sl@0: statePtr->pb.csParam.open.ulpTimeoutValue = 100; sl@0: statePtr->pb.csParam.open.ulpTimeoutAction = 1 /* 1:abort 0:report */; sl@0: statePtr->pb.csParam.open.commandTimeoutValue = 0 /* infinity */; sl@0: sl@0: statePtr->flags |= TCP_LISTENING; sl@0: err = PBControlAsync((ParmBlkPtr) &(statePtr->pb)); sl@0: sl@0: /* sl@0: * If this is a server on port 0 then we need to wait until sl@0: * the dynamic port allocation is made by the MacTcp driver. sl@0: */ sl@0: sl@0: if (statePtr->port == 0) { sl@0: EventRecord dummy; sl@0: sl@0: while (statePtr->pb.csParam.open.localPort == 0) { sl@0: WaitNextEvent(0, &dummy, 1, NULL); sl@0: if (statePtr->pb.ioResult != 0) { sl@0: break; sl@0: } sl@0: } sl@0: statePtr->port = statePtr->pb.csParam.open.localPort; sl@0: } sl@0: Tcl_SetErrno(EINPROGRESS); sl@0: } else { sl@0: /* sl@0: * Attempt to connect. The connect may fail at present with an sl@0: * EINPROGRESS but at a later time it will complete. The caller sl@0: * will set up a file handler on the socket if she is interested in sl@0: * being informed when the connect completes. sl@0: */ sl@0: sl@0: InitMacTCPParamBlock(&statePtr->pb, TCPActiveOpen); sl@0: sl@0: statePtr->pb.tcpStream = tcpStream; sl@0: statePtr->pb.csParam.open.remoteHost = macAddr; sl@0: statePtr->pb.csParam.open.remotePort = port; sl@0: statePtr->pb.csParam.open.localHost = 0; sl@0: statePtr->pb.csParam.open.localPort = myport; sl@0: statePtr->pb.csParam.open.userDataPtr = (Ptr) statePtr; sl@0: statePtr->pb.csParam.open.validityFlags = timeoutValue | timeoutAction; sl@0: statePtr->pb.csParam.open.ulpTimeoutValue = 60 /* seconds */; sl@0: statePtr->pb.csParam.open.ulpTimeoutAction = 1 /* 1:abort 0:report */; sl@0: statePtr->pb.csParam.open.commandTimeoutValue = 0; sl@0: sl@0: statePtr->pb.ioCompletion = completeUPP; sl@0: if (async) { sl@0: statePtr->flags |= TCP_ASYNC_CONNECT; sl@0: err = PBControlAsync((ParmBlkPtr) &(statePtr->pb)); sl@0: Tcl_SetErrno(EINPROGRESS); sl@0: } else { sl@0: err = PBControlSync((ParmBlkPtr) &(statePtr->pb)); sl@0: } sl@0: } sl@0: sl@0: switch (err) { sl@0: case noErr: sl@0: if (!async) { sl@0: statePtr->flags |= TCP_CONNECTED; sl@0: } sl@0: return statePtr; sl@0: case duplicateSocket: sl@0: Tcl_SetErrno(EADDRINUSE); sl@0: break; sl@0: case openFailed: sl@0: case connectionTerminated: sl@0: Tcl_SetErrno(ECONNREFUSED); sl@0: break; sl@0: case invalidStreamPtr: sl@0: case connectionExists: sl@0: default: sl@0: /* sl@0: * These cases should never occur. However, we will fail sl@0: * gracefully and hope Tcl can resume. The alternative is to panic sl@0: * which is probably a bit drastic. sl@0: */ sl@0: sl@0: Debugger(); sl@0: Tcl_SetErrno(err); sl@0: } sl@0: sl@0: /* sl@0: * We had error during the connection. Release the stream sl@0: * and file handle. Also report to the interp. sl@0: */ sl@0: sl@0: pb.ioCRefNum = driverRefNum; sl@0: pb.csCode = TCPRelease; sl@0: pb.tcpStream = tcpStream; sl@0: pb.ioCompletion = NULL; sl@0: err = PBControlSync((ParmBlkPtr) &pb); sl@0: sl@0: if (interp != (Tcl_Interp *) NULL) { sl@0: Tcl_AppendResult(interp, "couldn't open socket: ", sl@0: Tcl_PosixError(interp), (char *) NULL); sl@0: } sl@0: sl@0: ckfree(buffer); sl@0: FreeSocketInfo(statePtr); sl@0: return (TcpState *) NULL; sl@0: } sl@0: sl@0: /* sl@0: *---------------------------------------------------------------------- sl@0: * sl@0: * Tcl_OpenTcpClient -- sl@0: * sl@0: * Opens a TCP client socket and creates a channel around it. sl@0: * sl@0: * Results: sl@0: * The channel or NULL if failed. On failure, the routine also sl@0: * sets the output argument errorCodePtr to the error code. sl@0: * sl@0: * Side effects: sl@0: * Opens a client socket and creates a new channel. sl@0: * sl@0: *---------------------------------------------------------------------- sl@0: */ sl@0: sl@0: Tcl_Channel sl@0: Tcl_OpenTcpClient( sl@0: Tcl_Interp *interp, /* For error reporting; can be NULL. */ sl@0: int port, /* Port number to open. */ sl@0: CONST char *host, /* Host on which to open port. */ sl@0: CONST char *myaddr, /* Client-side address */ sl@0: int myport, /* Client-side port */ sl@0: int async) /* If nonzero, attempt to do an sl@0: * asynchronous connect. Otherwise sl@0: * we do a blocking connect. sl@0: * - currently ignored */ sl@0: { sl@0: TcpState *statePtr; sl@0: char channelName[20]; sl@0: sl@0: if (TclpHasSockets(interp) != TCL_OK) { sl@0: return NULL; sl@0: } sl@0: sl@0: /* sl@0: * Create a new client socket and wrap it in a channel. sl@0: */ sl@0: sl@0: statePtr = CreateSocket(interp, port, host, myaddr, myport, 0, async); sl@0: if (statePtr == NULL) { sl@0: return NULL; sl@0: } sl@0: sl@0: sprintf(channelName, "sock%d", socketNumber++); sl@0: sl@0: statePtr->channel = Tcl_CreateChannel(&tcpChannelType, channelName, sl@0: (ClientData) statePtr, (TCL_READABLE | TCL_WRITABLE)); sl@0: Tcl_SetChannelBufferSize(statePtr->channel, socketBufferSize); sl@0: Tcl_SetChannelOption(NULL, statePtr->channel, "-translation", "auto crlf"); sl@0: return statePtr->channel; sl@0: } sl@0: sl@0: /* sl@0: *---------------------------------------------------------------------- sl@0: * sl@0: * Tcl_OpenTcpServer -- sl@0: * sl@0: * Opens a TCP server socket and creates a channel around it. sl@0: * sl@0: * Results: sl@0: * The channel or NULL if failed. sl@0: * sl@0: * Side effects: sl@0: * Opens a server socket and creates a new channel. sl@0: * sl@0: *---------------------------------------------------------------------- sl@0: */ sl@0: sl@0: Tcl_Channel sl@0: Tcl_OpenTcpServer( sl@0: Tcl_Interp *interp, /* For error reporting - may be sl@0: * NULL. */ sl@0: int port, /* Port number to open. */ sl@0: CONST char *host, /* Name of local host. */ sl@0: Tcl_TcpAcceptProc *acceptProc, /* Callback for accepting connections sl@0: * from new clients. */ sl@0: ClientData acceptProcData) /* Data for the callback. */ sl@0: { sl@0: TcpState *statePtr; sl@0: char channelName[20]; sl@0: sl@0: if (TclpHasSockets(interp) != TCL_OK) { sl@0: return NULL; sl@0: } sl@0: sl@0: /* sl@0: * Create a new client socket and wrap it in a channel. sl@0: */ sl@0: sl@0: statePtr = CreateSocket(interp, port, host, NULL, 0, 1, 1); sl@0: if (statePtr == NULL) { sl@0: return NULL; sl@0: } sl@0: sl@0: statePtr->acceptProc = acceptProc; sl@0: statePtr->acceptProcData = acceptProcData; sl@0: sl@0: sprintf(channelName, "sock%d", socketNumber++); sl@0: sl@0: statePtr->channel = Tcl_CreateChannel(&tcpChannelType, channelName, sl@0: (ClientData) statePtr, 0); sl@0: Tcl_SetChannelBufferSize(statePtr->channel, socketBufferSize); sl@0: Tcl_SetChannelOption(NULL, statePtr->channel, "-translation", "auto crlf"); sl@0: return statePtr->channel; sl@0: } sl@0: sl@0: /* sl@0: *---------------------------------------------------------------------- sl@0: * sl@0: * SocketEventProc -- sl@0: * sl@0: * This procedure is called by Tcl_ServiceEvent when a socket event sl@0: * reaches the front of the event queue. This procedure is sl@0: * responsible for notifying the generic channel code. sl@0: * sl@0: * Results: sl@0: * Returns 1 if the event was handled, meaning it should be removed sl@0: * from the queue. Returns 0 if the event was not handled, meaning sl@0: * it should stay on the queue. The only time the event isn't sl@0: * handled is if the TCL_FILE_EVENTS flag bit isn't set. sl@0: * sl@0: * Side effects: sl@0: * Whatever the channel callback procedures do. sl@0: * sl@0: *---------------------------------------------------------------------- sl@0: */ sl@0: sl@0: static int sl@0: SocketEventProc( sl@0: Tcl_Event *evPtr, /* Event to service. */ sl@0: int flags) /* Flags that indicate what events to sl@0: * handle, such as TCL_FILE_EVENTS. */ sl@0: { sl@0: TcpState *statePtr; sl@0: SocketEvent *eventPtr = (SocketEvent *) evPtr; sl@0: int mask = 0; sl@0: ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); sl@0: sl@0: if (!(flags & TCL_FILE_EVENTS)) { sl@0: return 0; sl@0: } sl@0: sl@0: /* sl@0: * Find the specified socket on the socket list. sl@0: */ sl@0: sl@0: for (statePtr = tsdPtr->socketList; statePtr != NULL; sl@0: statePtr = statePtr->nextPtr) { sl@0: if ((statePtr == eventPtr->statePtr) && sl@0: (statePtr->tcpStream == eventPtr->tcpStream)) { sl@0: break; sl@0: } sl@0: } sl@0: sl@0: /* sl@0: * Discard events that have gone stale. sl@0: */ sl@0: sl@0: if (!statePtr) { sl@0: return 1; sl@0: } sl@0: statePtr->flags &= ~(TCP_PENDING); sl@0: if (statePtr->flags & TCP_RELEASE) { sl@0: SocketFreeProc(statePtr); sl@0: return 1; sl@0: } sl@0: sl@0: sl@0: /* sl@0: * Handle connection requests directly. sl@0: */ sl@0: sl@0: if (statePtr->flags & TCP_LISTEN_CONNECT) { sl@0: if (statePtr->checkMask & TCL_READABLE) { sl@0: TcpAccept(statePtr); sl@0: } sl@0: return 1; sl@0: } sl@0: sl@0: /* sl@0: * Mask off unwanted events then notify the channel. sl@0: */ sl@0: sl@0: mask = statePtr->checkMask & statePtr->watchMask; sl@0: if (mask) { sl@0: Tcl_NotifyChannel(statePtr->channel, mask); sl@0: } sl@0: return 1; sl@0: } sl@0: sl@0: /* sl@0: *---------------------------------------------------------------------- sl@0: * sl@0: * WaitForSocketEvent -- sl@0: * sl@0: * Waits until one of the specified events occurs on a socket. sl@0: * sl@0: * Results: sl@0: * Returns 1 on success or 0 on failure, with an error code in sl@0: * errorCodePtr. sl@0: * sl@0: * Side effects: sl@0: * Processes socket events off the system queue. sl@0: * sl@0: *---------------------------------------------------------------------- sl@0: */ sl@0: sl@0: static int sl@0: WaitForSocketEvent( sl@0: TcpState *statePtr, /* Information about this socket. */ sl@0: int mask, /* Events to look for. */ sl@0: int *errorCodePtr) /* Where to store errors? */ sl@0: { sl@0: OSErr err; sl@0: TCPiopb statusPB; sl@0: EventRecord dummy; sl@0: sl@0: /* sl@0: * Loop until we get the specified condition, unless the socket is sl@0: * asynchronous. sl@0: */ sl@0: sl@0: do { sl@0: statusPB.ioCRefNum = driverRefNum; sl@0: statusPB.tcpStream = statePtr->tcpStream; sl@0: statusPB.csCode = TCPStatus; sl@0: err = PBControlSync((ParmBlkPtr) &statusPB); sl@0: if (err != noErr) { sl@0: /* sl@0: * I am not sure why it is right to return 1 - indicating success sl@0: * for synchronous sockets when an attempt to get status on the sl@0: * driver yeilds an error. But it is CERTAINLY wrong for async sl@0: * sockect which have not yet connected. sl@0: */ sl@0: sl@0: if (statePtr->flags & TCP_ASYNC_CONNECT) { sl@0: *errorCodePtr = EWOULDBLOCK; sl@0: return 0; sl@0: } else { sl@0: statePtr->checkMask |= (TCL_READABLE | TCL_WRITABLE); sl@0: return 1; sl@0: } sl@0: } sl@0: statePtr->checkMask = 0; sl@0: sl@0: /* sl@0: * The "6" below is the "connection being established" flag. I couldn't sl@0: * find a define for this in MacTCP.h, but that's what the programmer's sl@0: * guide says. sl@0: */ sl@0: sl@0: if ((statusPB.csParam.status.connectionState != 0) sl@0: && (statusPB.csParam.status.connectionState != 4) sl@0: && (statusPB.csParam.status.connectionState != 6)) { sl@0: if (statusPB.csParam.status.amtUnreadData > 0) { sl@0: statePtr->checkMask |= TCL_READABLE; sl@0: } sl@0: if (!(statePtr->flags & TCP_WRITING) sl@0: && (statusPB.csParam.status.sendWindow - sl@0: statusPB.csParam.status.amtUnackedData) > 0) { sl@0: statePtr->flags &= ~(TCP_ASYNC_CONNECT); sl@0: statePtr->checkMask |= TCL_WRITABLE; sl@0: } sl@0: if (mask & statePtr->checkMask) { sl@0: return 1; sl@0: } sl@0: } else { sl@0: break; sl@0: } sl@0: sl@0: /* sl@0: * Call the system to let other applications run while we sl@0: * are waiting for this event to occur. sl@0: */ sl@0: sl@0: WaitNextEvent(0, &dummy, 1, NULL); sl@0: } while (!(statePtr->flags & TCP_ASYNC_SOCKET)); sl@0: *errorCodePtr = EWOULDBLOCK; sl@0: return 0; sl@0: } sl@0: sl@0: /* sl@0: *---------------------------------------------------------------------- sl@0: * sl@0: * TcpAccept -- sl@0: * Accept a TCP socket connection. This is called by the event sl@0: * loop, and it in turns calls any registered callbacks for this sl@0: * channel. sl@0: * sl@0: * Results: sl@0: * None. sl@0: * sl@0: * Side effects: sl@0: * Evals the Tcl script associated with the server socket. sl@0: * sl@0: *---------------------------------------------------------------------- sl@0: */ sl@0: sl@0: static void sl@0: TcpAccept( sl@0: TcpState *statePtr) sl@0: { sl@0: TcpState *newStatePtr; sl@0: StreamPtr tcpStream; sl@0: char remoteHostname[255]; sl@0: OSErr err; sl@0: ip_addr remoteAddress; sl@0: long remotePort; sl@0: char channelName[20]; sl@0: sl@0: statePtr->flags &= ~TCP_LISTEN_CONNECT; sl@0: statePtr->checkMask &= ~TCL_READABLE; sl@0: sl@0: /* sl@0: * Transfer sever stream to new connection. sl@0: */ sl@0: sl@0: tcpStream = statePtr->tcpStream; sl@0: newStatePtr = NewSocketInfo(tcpStream); sl@0: newStatePtr->tcpStream = tcpStream; sl@0: sprintf(channelName, "sock%d", socketNumber++); sl@0: sl@0: sl@0: newStatePtr->flags |= TCP_CONNECTED; sl@0: newStatePtr->channel = Tcl_CreateChannel(&tcpChannelType, channelName, sl@0: (ClientData) newStatePtr, (TCL_READABLE | TCL_WRITABLE)); sl@0: Tcl_SetChannelBufferSize(newStatePtr->channel, socketBufferSize); sl@0: Tcl_SetChannelOption(NULL, newStatePtr->channel, "-translation", sl@0: "auto crlf"); sl@0: sl@0: remoteAddress = statePtr->pb.csParam.open.remoteHost; sl@0: remotePort = statePtr->pb.csParam.open.remotePort; sl@0: sl@0: /* sl@0: * Reopen passive connect. Make new tcpStream the server. sl@0: */ sl@0: sl@0: ClearZombieSockets(); sl@0: InitMacTCPParamBlock(&statePtr->pb, TCPCreate); sl@0: statePtr->pb.csParam.create.rcvBuff = ckalloc(socketBufferSize); sl@0: statePtr->pb.csParam.create.rcvBuffLen = socketBufferSize; sl@0: err = PBControlSync((ParmBlkPtr) &statePtr->pb); sl@0: if (err != noErr) { sl@0: /* sl@0: * Hmmm... We can't reopen the server. We'll go ahead sl@0: * an continue - but we are kind of broken now... sl@0: */ sl@0: Debugger(); sl@0: statePtr->tcpStream = -1; sl@0: statePtr->flags |= TCP_SERVER_ZOMBIE; sl@0: } sl@0: sl@0: tcpStream = statePtr->tcpStream = statePtr->pb.tcpStream; sl@0: sl@0: InitMacTCPParamBlock(&statePtr->pb, TCPPassiveOpen); sl@0: statePtr->pb.tcpStream = tcpStream; sl@0: statePtr->pb.csParam.open.localHost = 0; sl@0: statePtr->pb.csParam.open.localPort = statePtr->port; sl@0: statePtr->pb.ioCompletion = completeUPP; sl@0: statePtr->pb.csParam.open.userDataPtr = (Ptr) statePtr; sl@0: statePtr->flags |= TCP_LISTENING; sl@0: err = PBControlAsync((ParmBlkPtr) &(statePtr->pb)); sl@0: /* sl@0: * TODO: deal with case where we can't recreate server socket... sl@0: */ sl@0: sl@0: /* sl@0: * Finally we run the accept procedure. We must do this last to make sl@0: * sure we are in a nice clean state. This Tcl code can do anything sl@0: * including closing the server or client sockets we've just delt with. sl@0: */ sl@0: sl@0: if (statePtr->acceptProc != NULL) { sl@0: sprintf(remoteHostname, "%d.%d.%d.%d", remoteAddress>>24, sl@0: remoteAddress>>16 & 0xff, remoteAddress>>8 & 0xff, sl@0: remoteAddress & 0xff); sl@0: sl@0: (statePtr->acceptProc)(statePtr->acceptProcData, newStatePtr->channel, sl@0: remoteHostname, remotePort); sl@0: } sl@0: } sl@0: sl@0: /* sl@0: *---------------------------------------------------------------------- sl@0: * sl@0: * Tcl_GetHostName -- sl@0: * sl@0: * Returns the name of the local host. sl@0: * sl@0: * Results: sl@0: * A string containing the network name for this machine, or sl@0: * an empty string if we can't figure out the name. The caller sl@0: * must not modify or free this string. sl@0: * sl@0: * Side effects: sl@0: * None. sl@0: * sl@0: *---------------------------------------------------------------------- sl@0: */ sl@0: sl@0: CONST char * sl@0: Tcl_GetHostName() sl@0: { sl@0: static int hostnameInited = 0; sl@0: static char hostname[255]; sl@0: ip_addr ourAddress; sl@0: Tcl_DString dString; sl@0: OSErr err; sl@0: sl@0: if (hostnameInited) { sl@0: return hostname; sl@0: } sl@0: sl@0: if (TclpHasSockets(NULL) == TCL_OK) { sl@0: err = GetLocalAddress(&ourAddress); sl@0: if (err == noErr) { sl@0: /* sl@0: * Search for the doman name and return it if found. Otherwise, sl@0: * just print the IP number to a string and return that. sl@0: */ sl@0: sl@0: Tcl_DStringInit(&dString); sl@0: err = ResolveAddress(ourAddress, &dString); sl@0: if (err == noErr) { sl@0: strcpy(hostname, dString.string); sl@0: } else { sl@0: sprintf(hostname, "%d.%d.%d.%d", ourAddress>>24, ourAddress>>16 & 0xff, sl@0: ourAddress>>8 & 0xff, ourAddress & 0xff); sl@0: } sl@0: Tcl_DStringFree(&dString); sl@0: sl@0: hostnameInited = 1; sl@0: return hostname; sl@0: } sl@0: } sl@0: sl@0: hostname[0] = '\0'; sl@0: hostnameInited = 1; sl@0: return hostname; sl@0: } sl@0: sl@0: /* sl@0: *---------------------------------------------------------------------- sl@0: * sl@0: * ResolveAddress -- sl@0: * sl@0: * This function is used to resolve an ip address to it's full sl@0: * domain name address. sl@0: * sl@0: * Results: sl@0: * An os err value. sl@0: * sl@0: * Side effects: sl@0: * Treats client data as int we set to true. sl@0: * sl@0: *---------------------------------------------------------------------- sl@0: */ sl@0: sl@0: static OSErr sl@0: ResolveAddress( sl@0: ip_addr tcpAddress, /* Address to resolve. */ sl@0: Tcl_DString *dsPtr) /* Returned address in string. */ sl@0: { sl@0: int i; sl@0: EventRecord dummy; sl@0: DNRState dnrState; sl@0: OSErr err; sl@0: sl@0: /* sl@0: * Call AddrToName to resolve our ip address to our domain name. sl@0: * The call is async, so we must wait for a callback to tell us sl@0: * when to continue. sl@0: */ sl@0: sl@0: for (i = 0; i < NUM_ALT_ADDRS; i++) { sl@0: dnrState.hostInfo.addr[i] = 0; sl@0: } sl@0: dnrState.done = 0; sl@0: GetCurrentProcess(&(dnrState.psn)); sl@0: err = AddrToName(tcpAddress, &dnrState.hostInfo, resultUPP, (Ptr) &dnrState); sl@0: if (err == cacheFault) { sl@0: while (!dnrState.done) { sl@0: WaitNextEvent(0, &dummy, 1, NULL); sl@0: } sl@0: } sl@0: sl@0: /* sl@0: * If there is no error in finding the domain name we set the sl@0: * result into the dynamic string. We also work around a bug in sl@0: * MacTcp where an extranious '.' may be found at the end of the name. sl@0: */ sl@0: sl@0: if (dnrState.hostInfo.rtnCode == noErr) { sl@0: i = strlen(dnrState.hostInfo.cname) - 1; sl@0: if (dnrState.hostInfo.cname[i] == '.') { sl@0: dnrState.hostInfo.cname[i] = '\0'; sl@0: } sl@0: Tcl_DStringAppend(dsPtr, dnrState.hostInfo.cname, -1); sl@0: } sl@0: sl@0: return dnrState.hostInfo.rtnCode; sl@0: } sl@0: sl@0: /* sl@0: *---------------------------------------------------------------------- sl@0: * sl@0: * DNRCompletionRoutine -- sl@0: * sl@0: * This function is called when the Domain Name Server is done sl@0: * seviceing our request. It just sets a flag that we can poll sl@0: * in functions like Tcl_GetHostName to let them know to continue. sl@0: * sl@0: * Results: sl@0: * None. sl@0: * sl@0: * Side effects: sl@0: * Treats client data as int we set to true. sl@0: * sl@0: *---------------------------------------------------------------------- sl@0: */ sl@0: sl@0: static pascal void sl@0: DNRCompletionRoutine( sl@0: struct hostInfo *hostinfoPtr, /* Host infor struct. */ sl@0: DNRState *dnrStatePtr) /* Completetion state. */ sl@0: { sl@0: dnrStatePtr->done = true; sl@0: WakeUpProcess(&(dnrStatePtr->psn)); sl@0: } sl@0: sl@0: /* sl@0: *---------------------------------------------------------------------- sl@0: * sl@0: * CleanUpExitProc -- sl@0: * sl@0: * This procedure is invoked as an exit handler when ExitToShell sl@0: * is called. It aborts any lingering socket connections. This sl@0: * must be called or the Mac OS will more than likely crash. sl@0: * sl@0: * Results: sl@0: * None. sl@0: * sl@0: * Side effects: sl@0: * None. sl@0: * sl@0: *---------------------------------------------------------------------- sl@0: */ sl@0: sl@0: static pascal void sl@0: CleanUpExitProc() sl@0: { sl@0: TCPiopb exitPB; sl@0: TcpState *statePtr; sl@0: ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); sl@0: sl@0: while (tsdPtr->socketList != NULL) { sl@0: statePtr = tsdPtr->socketList; sl@0: tsdPtr->socketList = statePtr->nextPtr; sl@0: sl@0: /* sl@0: * Close and Release the connection. sl@0: */ sl@0: sl@0: exitPB.ioCRefNum = driverRefNum; sl@0: exitPB.csCode = TCPClose; sl@0: exitPB.tcpStream = statePtr->tcpStream; sl@0: exitPB.csParam.close.ulpTimeoutValue = 60 /* seconds */; sl@0: exitPB.csParam.close.ulpTimeoutAction = 1 /* 1:abort 0:report */; sl@0: exitPB.csParam.close.validityFlags = timeoutValue | timeoutAction; sl@0: exitPB.ioCompletion = NULL; sl@0: PBControlSync((ParmBlkPtr) &exitPB); sl@0: sl@0: exitPB.ioCRefNum = driverRefNum; sl@0: exitPB.csCode = TCPRelease; sl@0: exitPB.tcpStream = statePtr->tcpStream; sl@0: exitPB.ioCompletion = NULL; sl@0: PBControlSync((ParmBlkPtr) &exitPB); sl@0: } sl@0: } sl@0: sl@0: /* sl@0: *---------------------------------------------------------------------- sl@0: * sl@0: * GetHostFromString -- sl@0: * sl@0: * Looks up the passed in domain name in the domain resolver. It sl@0: * can accept strings of two types: 1) the ip number in string sl@0: * format, or 2) the domain name. sl@0: * sl@0: * Results: sl@0: * We return a ip address or 0 if there was an error or the sl@0: * domain does not exist. sl@0: * sl@0: * Side effects: sl@0: * None. sl@0: * sl@0: *---------------------------------------------------------------------- sl@0: */ sl@0: sl@0: static OSErr sl@0: GetHostFromString( sl@0: CONST char *name, /* Host in string form. */ sl@0: ip_addr *address) /* Returned IP address. */ sl@0: { sl@0: OSErr err; sl@0: int i; sl@0: EventRecord dummy; sl@0: DNRState dnrState; sl@0: sl@0: if (TclpHasSockets(NULL) != TCL_OK) { sl@0: return 0; sl@0: } sl@0: sl@0: /* sl@0: * Call StrToAddr to get the ip number for the passed in domain sl@0: * name. The call is async, so we must wait for a callback to sl@0: * tell us when to continue. sl@0: */ sl@0: sl@0: for (i = 0; i < NUM_ALT_ADDRS; i++) { sl@0: dnrState.hostInfo.addr[i] = 0; sl@0: } sl@0: dnrState.done = 0; sl@0: GetCurrentProcess(&(dnrState.psn)); sl@0: err = StrToAddr((char*)name, &dnrState.hostInfo, resultUPP, (Ptr) &dnrState); sl@0: if (err == cacheFault) { sl@0: while (!dnrState.done) { sl@0: WaitNextEvent(0, &dummy, 1, NULL); sl@0: } sl@0: } sl@0: sl@0: /* sl@0: * For some reason MacTcp may return a cachFault a second time via sl@0: * the hostinfo block. This seems to be a bug in MacTcp. In this case sl@0: * we run StrToAddr again - which seems to then work just fine. sl@0: */ sl@0: sl@0: if (dnrState.hostInfo.rtnCode == cacheFault) { sl@0: dnrState.done = 0; sl@0: err = StrToAddr((char*)name, &dnrState.hostInfo, resultUPP, (Ptr) &dnrState); sl@0: if (err == cacheFault) { sl@0: while (!dnrState.done) { sl@0: WaitNextEvent(0, &dummy, 1, NULL); sl@0: } sl@0: } sl@0: } sl@0: sl@0: if (dnrState.hostInfo.rtnCode == noErr) { sl@0: *address = dnrState.hostInfo.addr[0]; sl@0: } sl@0: sl@0: return dnrState.hostInfo.rtnCode; sl@0: } sl@0: sl@0: /* sl@0: *---------------------------------------------------------------------- sl@0: * sl@0: * IOCompletionRoutine -- sl@0: * sl@0: * This function is called when an asynchronous socket operation sl@0: * completes. Since this routine runs as an interrupt handler, sl@0: * it will simply set state to tell the notifier that this socket sl@0: * is now ready for action. Note that this function is running at sl@0: * interupt time and can't allocate memory or do much else except sl@0: * set state. sl@0: * sl@0: * Results: sl@0: * None. sl@0: * sl@0: * Side effects: sl@0: * Sets some state in the socket state. May also wake the process sl@0: * if we are not currently running. sl@0: * sl@0: *---------------------------------------------------------------------- sl@0: */ sl@0: sl@0: static void sl@0: IOCompletionRoutine( sl@0: TCPiopb *pbPtr) /* Tcp parameter block. */ sl@0: { sl@0: TcpState *statePtr; sl@0: sl@0: if (pbPtr->csCode == TCPSend) { sl@0: statePtr = (TcpState *) pbPtr->csParam.send.userDataPtr; sl@0: } else { sl@0: statePtr = (TcpState *) pbPtr->csParam.open.userDataPtr; sl@0: } sl@0: sl@0: /* sl@0: * Always wake the process in case it's in WaitNextEvent. sl@0: * If an error has a occured - just return. We will deal sl@0: * with the problem later. sl@0: */ sl@0: sl@0: WakeUpProcess(&statePtr->psn); sl@0: if (pbPtr->ioResult != noErr) { sl@0: return; sl@0: } sl@0: sl@0: if (statePtr->flags & TCP_ASYNC_CONNECT) { sl@0: statePtr->flags &= ~TCP_ASYNC_CONNECT; sl@0: statePtr->flags |= TCP_CONNECTED; sl@0: statePtr->checkMask |= TCL_READABLE & TCL_WRITABLE; sl@0: } else if (statePtr->flags & TCP_LISTENING) { sl@0: if (statePtr->port == 0) { sl@0: Debugger(); sl@0: } sl@0: statePtr->flags &= ~TCP_LISTENING; sl@0: statePtr->flags |= TCP_LISTEN_CONNECT; sl@0: statePtr->checkMask |= TCL_READABLE; sl@0: } else if (statePtr->flags & TCP_WRITING) { sl@0: statePtr->flags &= ~TCP_WRITING; sl@0: statePtr->checkMask |= TCL_WRITABLE; sl@0: if (!(statePtr->flags & TCP_CONNECTED)) { sl@0: InitMacTCPParamBlock(&statePtr->pb, TCPClose); sl@0: statePtr->pb.tcpStream = statePtr->tcpStream; sl@0: statePtr->pb.ioCompletion = closeUPP; sl@0: statePtr->pb.csParam.close.userDataPtr = (Ptr) statePtr; sl@0: if (PBControlAsync((ParmBlkPtr) &statePtr->pb) != noErr) { sl@0: statePtr->flags |= TCP_RELEASE; sl@0: } sl@0: } sl@0: } sl@0: } sl@0: sl@0: /* sl@0: *---------------------------------------------------------------------- sl@0: * sl@0: * GetLocalAddress -- sl@0: * sl@0: * Get the IP address for this machine. The result is cached so sl@0: * the result is returned quickly after the first call. sl@0: * sl@0: * Results: sl@0: * Macintosh error code. sl@0: * sl@0: * Side effects: sl@0: * None. sl@0: * sl@0: *---------------------------------------------------------------------- sl@0: */ sl@0: sl@0: static OSErr sl@0: GetLocalAddress( sl@0: unsigned long *addr) /* Returns host IP address. */ sl@0: { sl@0: struct GetAddrParamBlock pBlock; sl@0: OSErr err = noErr; sl@0: static unsigned long localAddress = 0; sl@0: sl@0: if (localAddress == 0) { sl@0: memset(&pBlock, 0, sizeof(pBlock)); sl@0: pBlock.ioResult = 1; sl@0: pBlock.csCode = ipctlGetAddr; sl@0: pBlock.ioCRefNum = driverRefNum; sl@0: err = PBControlSync((ParmBlkPtr) &pBlock); sl@0: sl@0: if (err != noErr) { sl@0: return err; sl@0: } sl@0: localAddress = pBlock.ourAddress; sl@0: } sl@0: sl@0: *addr = localAddress; sl@0: return noErr; sl@0: } sl@0: sl@0: /* sl@0: *---------------------------------------------------------------------- sl@0: * sl@0: * GetBufferSize -- sl@0: * sl@0: * Get the appropiate buffer size for our machine & network. This sl@0: * value will be used by the rest of Tcl & the MacTcp driver for sl@0: * the size of its buffers. If out method for determining the sl@0: * optimal buffer size fails for any reason - we return a sl@0: * reasonable default. sl@0: * sl@0: * Results: sl@0: * Size of optimal buffer in bytes. sl@0: * sl@0: * Side effects: sl@0: * None. sl@0: * sl@0: *---------------------------------------------------------------------- sl@0: */ sl@0: sl@0: static long sl@0: GetBufferSize() sl@0: { sl@0: UDPiopb iopb; sl@0: OSErr err = noErr; sl@0: long bufferSize; sl@0: sl@0: memset(&iopb, 0, sizeof(iopb)); sl@0: err = GetLocalAddress(&iopb.csParam.mtu.remoteHost); sl@0: if (err != noErr) { sl@0: return CHANNEL_BUF_SIZE; sl@0: } sl@0: iopb.ioCRefNum = driverRefNum; sl@0: iopb.csCode = UDPMaxMTUSize; sl@0: err = PBControlSync((ParmBlkPtr)&iopb); sl@0: if (err != noErr) { sl@0: return CHANNEL_BUF_SIZE; sl@0: } sl@0: bufferSize = (iopb.csParam.mtu.mtuSize * 4) + 1024; sl@0: if (bufferSize < CHANNEL_BUF_SIZE) { sl@0: bufferSize = CHANNEL_BUF_SIZE; sl@0: } sl@0: return bufferSize; sl@0: } sl@0: sl@0: /* sl@0: *---------------------------------------------------------------------- sl@0: * sl@0: * TclSockGetPort -- sl@0: * sl@0: * Maps from a string, which could be a service name, to a port. sl@0: * Used by socket creation code to get port numbers and resolve sl@0: * registered service names to port numbers. sl@0: * sl@0: * Results: sl@0: * A standard Tcl result. On success, the port number is sl@0: * returned in portPtr. On failure, an error message is left in sl@0: * the interp's result. sl@0: * sl@0: * Side effects: sl@0: * None. sl@0: * sl@0: *---------------------------------------------------------------------- sl@0: */ sl@0: sl@0: int sl@0: TclSockGetPort( sl@0: Tcl_Interp *interp, /* Interp for error messages. */ sl@0: char *string, /* Integer or service name */ sl@0: char *proto, /* "tcp" or "udp", typically - sl@0: * ignored on Mac - assumed to be tcp */ sl@0: int *portPtr) /* Return port number */ sl@0: { sl@0: PortInfo *portInfoPtr = NULL; sl@0: sl@0: if (Tcl_GetInt(interp, string, portPtr) == TCL_OK) { sl@0: if (*portPtr > 0xFFFF) { sl@0: Tcl_AppendResult(interp, "couldn't open socket: port number too high", sl@0: (char *) NULL); sl@0: return TCL_ERROR; sl@0: } sl@0: if (*portPtr < 0) { sl@0: Tcl_AppendResult(interp, "couldn't open socket: negative port number", sl@0: (char *) NULL); sl@0: return TCL_ERROR; sl@0: } sl@0: return TCL_OK; sl@0: } sl@0: for (portInfoPtr = portServices; portInfoPtr->name != NULL; portInfoPtr++) { sl@0: if (!strcmp(portInfoPtr->name, string)) { sl@0: break; sl@0: } sl@0: } sl@0: if (portInfoPtr != NULL && portInfoPtr->name != NULL) { sl@0: *portPtr = portInfoPtr->port; sl@0: Tcl_ResetResult(interp); sl@0: return TCL_OK; sl@0: } sl@0: sl@0: return TCL_ERROR; sl@0: } sl@0: sl@0: /* sl@0: *---------------------------------------------------------------------- sl@0: * sl@0: * ClearZombieSockets -- sl@0: * sl@0: * This procedure looks through the socket list and removes the sl@0: * first stream it finds that is ready for release. This procedure sl@0: * should be called before we ever try to create new Tcp streams sl@0: * to ensure we can least allocate one stream. sl@0: * sl@0: * Results: sl@0: * None. sl@0: * sl@0: * Side effects: sl@0: * Tcp streams may be released. sl@0: * sl@0: *---------------------------------------------------------------------- sl@0: */ sl@0: sl@0: static void sl@0: ClearZombieSockets() sl@0: { sl@0: TcpState *statePtr; sl@0: ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); sl@0: sl@0: for (statePtr = tsdPtr->socketList; statePtr != NULL; sl@0: statePtr = statePtr->nextPtr) { sl@0: if (statePtr->flags & TCP_RELEASE) { sl@0: SocketFreeProc(statePtr); sl@0: return; sl@0: } sl@0: } sl@0: } sl@0: sl@0: sl@0: /* sl@0: *---------------------------------------------------------------------- sl@0: * sl@0: * NotifyRoutine -- sl@0: * sl@0: * This routine does nothing currently, and is not being used. But sl@0: * it is useful if you want to experiment with what MacTCP thinks that sl@0: * it is doing... sl@0: * sl@0: * Results: sl@0: * None. sl@0: * sl@0: * Side effects: sl@0: * None. sl@0: * sl@0: *---------------------------------------------------------------------- sl@0: */ sl@0: pascal void NotifyRoutine ( sl@0: StreamPtr tcpStream, sl@0: unsigned short eventCode, sl@0: Ptr userDataPtr, sl@0: unsigned short terminReason, sl@0: struct ICMPReport *icmpMsg) sl@0: { sl@0: StreamPtr localTcpStream; sl@0: unsigned short localEventCode; sl@0: unsigned short localTerminReason; sl@0: struct ICMPReport localIcmpMsg; sl@0: sl@0: localTcpStream = tcpStream; sl@0: localEventCode = eventCode; sl@0: localTerminReason = terminReason; sl@0: localIcmpMsg = *icmpMsg; sl@0: sl@0: }