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