diff options
Diffstat (limited to 'unix/tclUnixSock.c')
-rw-r--r-- | unix/tclUnixSock.c | 656 |
1 files changed, 405 insertions, 251 deletions
diff --git a/unix/tclUnixSock.c b/unix/tclUnixSock.c index 35728e1..49a6460 100644 --- a/unix/tclUnixSock.c +++ b/unix/tclUnixSock.c @@ -20,6 +20,12 @@ #define SET_BITS(var, bits) ((var) |= (bits)) #define CLEAR_BITS(var, bits) ((var) &= ~(bits)) +/* "sock" + a pointer in hex + \0 */ +#define SOCK_CHAN_LENGTH (4 + sizeof(void *) * 2 + 1) +#define SOCK_TEMPLATE "sock%lx" + +#undef SOCKET /* Possible conflict with win32 SOCKET */ + /* * This is needed to comply with the strict aliasing rules of GCC, but it also * simplifies casting between the different sockaddr types. @@ -46,12 +52,29 @@ typedef struct TcpFdList { struct TcpState { Tcl_Channel channel; /* Channel associated with this file. */ - TcpFdList *fds; /* The file descriptors of the sockets. */ + TcpFdList fds; /* The file descriptors of the sockets. */ int flags; /* ORed combination of the bitfields defined * below. */ + /* + * Only needed for server sockets + */ + Tcl_TcpAcceptProc *acceptProc; - /* Proc to call on accept. */ - ClientData acceptProcData; /* The data for the accept proc. */ + /* Proc to call on accept. */ + ClientData acceptProcData; /* The data for the accept proc. */ + + /* + * Only needed for client sockets + */ + + struct addrinfo *addrlist; /* Addresses to connect to. */ + struct addrinfo *addr; /* Iterator over addrlist. */ + struct addrinfo *myaddrlist;/* Local address. */ + struct addrinfo *myaddr; /* Iterator over myaddrlist. */ + int filehandlers; /* Caches FileHandlers that get set up while + * an async socket is not yet connected. */ + int status; /* Cache status of async socket. */ + int cachedBlocking; /* Cache blocking mode of async socket. */ }; /* @@ -71,9 +94,7 @@ struct TcpState { #ifndef SOMAXCONN # define SOMAXCONN 100 -#endif /* SOMAXCONN */ - -#if (SOMAXCONN < 100) +#elif (SOMAXCONN < 100) # undef SOMAXCONN # define SOMAXCONN 100 #endif /* SOMAXCONN < 100 */ @@ -89,9 +110,8 @@ struct TcpState { * Static routines for this file: */ -static TcpState * CreateClientSocket(Tcl_Interp *interp, int port, - const char *host, const char *myaddr, - int myport, int async); +static int CreateClientSocket(Tcl_Interp *interp, + TcpState *state); static void TcpAccept(ClientData data, int mask); static int TcpBlockModeProc(ClientData data, int mode); static int TcpCloseProc(ClientData instanceData, @@ -199,7 +219,7 @@ InitializeHostName( if (native == NULL) { native = tclEmptyStringRep; } -#else +#else /* !NO_UNAME */ /* * Uname doesn't exist; try gethostname instead. * @@ -224,7 +244,7 @@ InitializeHostName( if (gethostname(buffer, sizeof(buffer)) > -1) { /* INTL: Native. */ native = buffer; } -#endif +#endif /* NO_UNAME */ *encodingPtr = Tcl_GetEncoding(NULL, NULL); *lengthPtr = strlen(native); @@ -326,14 +346,18 @@ TcpBlockModeProc( * TCL_MODE_BLOCKING or * TCL_MODE_NONBLOCKING. */ { - TcpState *statePtr = (TcpState *) instanceData; + TcpState *statePtr = instanceData; if (mode == TCL_MODE_BLOCKING) { CLEAR_BITS(statePtr->flags, TCP_ASYNC_SOCKET); } else { SET_BITS(statePtr->flags, TCP_ASYNC_SOCKET); } - if (TclUnixSetBlockingMode(statePtr->fds->fd, mode) < 0) { + if (statePtr->flags & TCP_ASYNC_CONNECT) { + statePtr->cachedBlocking = mode; + return 0; + } + if (TclUnixSetBlockingMode(statePtr->fds.fd, mode) < 0) { return errno; } return 0; @@ -375,7 +399,7 @@ WaitForConnect( timeOut = -1; } errno = 0; - state = TclUnixWaitForFile(statePtr->fds->fd, + state = TclUnixWaitForFile(statePtr->fds.fd, TCL_WRITABLE | TCL_EXCEPTION, timeOut); if (state & TCL_EXCEPTION) { return -1; @@ -421,14 +445,14 @@ TcpInputProc( * buffer? */ int *errorCodePtr) /* Where to store error code. */ { - TcpState *statePtr = (TcpState *) instanceData; + TcpState *statePtr = instanceData; int bytesRead; *errorCodePtr = 0; if (WaitForConnect(statePtr, errorCodePtr) != 0) { return -1; } - bytesRead = recv(statePtr->fds->fd, buf, (size_t) bufSize, 0); + bytesRead = recv(statePtr->fds.fd, buf, (size_t) bufSize, 0); if (bytesRead > -1) { return bytesRead; } @@ -471,14 +495,14 @@ TcpOutputProc( int toWrite, /* How many bytes to write? */ int *errorCodePtr) /* Where to store error code. */ { - TcpState *statePtr = (TcpState *) instanceData; + TcpState *statePtr = instanceData; int written; *errorCodePtr = 0; if (WaitForConnect(statePtr, errorCodePtr) != 0) { return -1; } - written = send(statePtr->fds->fd, buf, (size_t) toWrite, 0); + written = send(statePtr->fds.fd, buf, (size_t) toWrite, 0); if (written > -1) { return written; } @@ -510,7 +534,7 @@ TcpCloseProc( ClientData instanceData, /* The socket to close. */ Tcl_Interp *interp) /* For error reporting - unused. */ { - TcpState *statePtr = (TcpState *) instanceData; + TcpState *statePtr = instanceData; int errorCode = 0; TcpFdList *fds; @@ -522,13 +546,27 @@ TcpCloseProc( * that called this function, so we do not have to delete them here. */ - for (fds = statePtr->fds; fds != NULL; fds = statePtr->fds) { - statePtr->fds = fds->next; + for (fds = &statePtr->fds; fds != NULL; fds = fds->next) { + if (fds->fd < 0) { + continue; + } Tcl_DeleteFileHandler(fds->fd); if (close(fds->fd) < 0) { errorCode = errno; } - ckfree(fds); + + } + fds = statePtr->fds.next; + while (fds != NULL) { + TcpFdList *next = fds->next; + ckfree(fds); + fds = next; + } + if (statePtr->addrlist != NULL) { + freeaddrinfo(statePtr->addrlist); + } + if (statePtr->myaddrlist != NULL) { + freeaddrinfo(statePtr->myaddrlist); } ckfree(statePtr); return errorCode; @@ -557,7 +595,7 @@ TcpClose2Proc( Tcl_Interp *interp, /* For error reporting. */ int flags) /* Flags that indicate which side to close. */ { - TcpState *statePtr = (TcpState *) instanceData; + TcpState *statePtr = instanceData; int errorCode = 0; int sd; @@ -574,12 +612,12 @@ TcpClose2Proc( break; default: if (interp) { - Tcl_AppendResult(interp, - "Socket close2proc called bidirectionally", NULL); + Tcl_SetObjResult(interp, Tcl_NewStringObj( + "socket close2proc called bidirectionally", -1)); } return TCL_ERROR; } - if (shutdown(statePtr->fds->fd,sd) < 0) { + if (shutdown(statePtr->fds.fd,sd) < 0) { errorCode = errno; } @@ -589,6 +627,74 @@ TcpClose2Proc( /* *---------------------------------------------------------------------- * + * TcpHostPortList -- + * + * This function is called by the -gethostname and -getpeername + * switches of TcpGetOptionProc() to add three list elements + * with the textual representation of the given address to the + * given DString. + * + * Results: + * None. + * + * Side effects: + * Adds three elements do dsPtr + * + *---------------------------------------------------------------------- + */ +static void +TcpHostPortList( + Tcl_Interp *interp, + Tcl_DString *dsPtr, + address addr, + socklen_t salen) +{ +#define SUPPRESS_RDNS_VAR "::tcl::unsupported::noReverseDNS" + char host[NI_MAXHOST], nhost[NI_MAXHOST], nport[NI_MAXSERV]; + int flags = 0; + + getnameinfo(&addr.sa, salen, + nhost, sizeof(nhost), nport, sizeof(nport), + NI_NUMERICHOST | NI_NUMERICSERV); + Tcl_DStringAppendElement(dsPtr, nhost); + /* + * We don't want to resolve INADDR_ANY and sin6addr_any; they + * can sometimes cause problems (and never have a name). + */ + if (addr.sa.sa_family == AF_INET) { + if (addr.sa4.sin_addr.s_addr == INADDR_ANY) { + flags |= NI_NUMERICHOST; + } +#ifndef NEED_FAKE_RFC2553 + } else if (addr.sa.sa_family == AF_INET6) { + if ((IN6_ARE_ADDR_EQUAL(&addr.sa6.sin6_addr, + &in6addr_any)) + || (IN6_IS_ADDR_V4MAPPED(&addr.sa6.sin6_addr) && + addr.sa6.sin6_addr.s6_addr[12] == 0 && + addr.sa6.sin6_addr.s6_addr[13] == 0 && + addr.sa6.sin6_addr.s6_addr[14] == 0 && + addr.sa6.sin6_addr.s6_addr[15] == 0)) { + flags |= NI_NUMERICHOST; + } +#endif /* NEED_FAKE_RFC2553 */ + } + /* Check if reverse DNS has been switched off globally */ + if (interp != NULL && Tcl_GetVar(interp, SUPPRESS_RDNS_VAR, 0) != NULL) { + flags |= NI_NUMERICHOST; + } + if (getnameinfo(&addr.sa, salen, host, sizeof(host), NULL, 0, flags) == 0) { + /* Reverse mapping worked */ + Tcl_DStringAppendElement(dsPtr, host); + } else { + /* Reverse mappong failed - use the numeric rep once more */ + Tcl_DStringAppendElement(dsPtr, nhost); + } + Tcl_DStringAppendElement(dsPtr, nport); +} + +/* + *---------------------------------------------------------------------- + * * TcpGetOptionProc -- * * Computes an option value for a TCP socket based channel, or a list of @@ -617,11 +723,8 @@ TcpGetOptionProc( Tcl_DString *dsPtr) /* Where to store the computed value; * initialized by caller. */ { - TcpState *statePtr = (TcpState *) instanceData; - char host[NI_MAXHOST], port[NI_MAXSERV]; + TcpState *statePtr = instanceData; size_t len = 0; - int reverseDNS = 0; -#define SUPPRESS_RDNS_VAR "::tcl::unsupported::noReverseDNS" if (optionName != NULL) { len = strlen(optionName); @@ -632,40 +735,36 @@ TcpGetOptionProc( socklen_t optlen = sizeof(int); int err, ret; - ret = getsockopt(statePtr->fds->fd, SOL_SOCKET, SO_ERROR, - (char *)&err, &optlen); - if (ret < 0) { - err = errno; - } + if (statePtr->status == 0) { + ret = getsockopt(statePtr->fds.fd, SOL_SOCKET, SO_ERROR, + (char *) &err, &optlen); + if (statePtr->flags & TCP_ASYNC_CONNECT) { + statePtr->status = err; + } + if (ret < 0) { + err = errno; + } + } else { + err = statePtr->status; + statePtr->status = 0; + } if (err != 0) { Tcl_DStringAppend(dsPtr, Tcl_ErrnoMsg(err), -1); } return TCL_OK; } - if (interp != NULL && Tcl_GetVar(interp, SUPPRESS_RDNS_VAR, 0) != NULL) { - reverseDNS = NI_NUMERICHOST; - } - - if ((len == 0) || - ((len > 1) && (optionName[1] == 'p') && - (strncmp(optionName, "-peername", len) == 0))) { + if ((len == 0) || ((len > 1) && (optionName[1] == 'p') && + (strncmp(optionName, "-peername", len) == 0))) { address peername; socklen_t size = sizeof(peername); - if (getpeername(statePtr->fds->fd, &peername.sa, &size) >= 0) { + if (getpeername(statePtr->fds.fd, &peername.sa, &size) >= 0) { if (len == 0) { Tcl_DStringAppendElement(dsPtr, "-peername"); Tcl_DStringStartSublist(dsPtr); } - - getnameinfo(&peername.sa, size, host, sizeof(host), NULL, 0, - NI_NUMERICHOST); - Tcl_DStringAppendElement(dsPtr, host); - getnameinfo(&peername.sa, size, host, sizeof(host), port, - sizeof(port), reverseDNS | NI_NUMERICSERV); - Tcl_DStringAppendElement(dsPtr, host); - Tcl_DStringAppendElement(dsPtr, port); + TcpHostPortList(interp, dsPtr, peername, size); if (len) { return TCL_OK; } @@ -680,16 +779,16 @@ TcpGetOptionProc( if (len) { if (interp) { - Tcl_AppendResult(interp, "can't get peername: ", - Tcl_PosixError(interp), NULL); + Tcl_SetObjResult(interp, Tcl_ObjPrintf( + "can't get peername: %s", + Tcl_PosixError(interp))); } return TCL_ERROR; } } } - if ((len == 0) || - ((len > 1) && (optionName[1] == 's') && + if ((len == 0) || ((len > 1) && (optionName[1] == 's') && (strncmp(optionName, "-sockname", len) == 0))) { TcpFdList *fds; address sockname; @@ -700,43 +799,11 @@ TcpGetOptionProc( Tcl_DStringAppendElement(dsPtr, "-sockname"); Tcl_DStringStartSublist(dsPtr); } - for (fds = statePtr->fds; fds != NULL; fds = fds->next) { + for (fds = &statePtr->fds; fds != NULL; fds = fds->next) { size = sizeof(sockname); if (getsockname(fds->fd, &(sockname.sa), &size) >= 0) { - int flags = reverseDNS; - found = 1; - getnameinfo(&sockname.sa, size, host, sizeof(host), NULL, 0, - NI_NUMERICHOST); - Tcl_DStringAppendElement(dsPtr, host); - - /* - * We don't want to resolve INADDR_ANY and sin6addr_any; they - * can sometimes cause problems (and never have a name). - */ - - flags |= NI_NUMERICSERV; - if (sockname.sa.sa_family == AF_INET) { - if (sockname.sa4.sin_addr.s_addr == INADDR_ANY) { - flags |= NI_NUMERICHOST; - } -#ifndef NEED_FAKE_RFC2553 - } else if (sockname.sa.sa_family == AF_INET6) { - if ((IN6_ARE_ADDR_EQUAL(&sockname.sa6.sin6_addr, - &in6addr_any)) - || (IN6_IS_ADDR_V4MAPPED(&sockname.sa6.sin6_addr) && - sockname.sa6.sin6_addr.s6_addr[12] == 0 && - sockname.sa6.sin6_addr.s6_addr[13] == 0 && - sockname.sa6.sin6_addr.s6_addr[14] == 0 && - sockname.sa6.sin6_addr.s6_addr[15] == 0)) { - flags |= NI_NUMERICHOST; - } -#endif - } - getnameinfo(&sockname.sa, size, host, sizeof(host), port, - sizeof(port), flags); - Tcl_DStringAppendElement(dsPtr, host); - Tcl_DStringAppendElement(dsPtr, port); + TcpHostPortList(interp, dsPtr, sockname, size); } } if (found) { @@ -746,8 +813,8 @@ TcpGetOptionProc( Tcl_DStringEndSublist(dsPtr); } else { if (interp) { - Tcl_AppendResult(interp, "can't get sockname: ", - Tcl_PosixError(interp), NULL); + Tcl_SetObjResult(interp, Tcl_ObjPrintf( + "can't get sockname: %s", Tcl_PosixError(interp))); } return TCL_ERROR; } @@ -784,26 +851,26 @@ TcpWatchProc( * TCL_READABLE, TCL_WRITABLE and * TCL_EXCEPTION. */ { - TcpState *statePtr = (TcpState *) instanceData; + TcpState *statePtr = instanceData; - /* - * Make sure we don't mess with server sockets since they will never be - * readable or writable at the Tcl level. This keeps Tcl scripts from - * interfering with the -accept behavior. - */ - - if (!statePtr->acceptProc) { - TcpFdList *fds; - - for (fds = statePtr->fds; fds != NULL; fds = fds->next) { - if (mask) { - Tcl_CreateFileHandler(fds->fd, mask, - (Tcl_FileProc *) Tcl_NotifyChannel, - (ClientData) statePtr->channel); - } else { - Tcl_DeleteFileHandler(fds->fd); - } - } + if (statePtr->acceptProc != NULL) { + /* + * Make sure we don't mess with server sockets since they will never + * be readable or writable at the Tcl level. This keeps Tcl scripts + * from interfering with the -accept behavior (bug #3394732). + */ + return; + } + + if (statePtr->flags & TCP_ASYNC_CONNECT) { + /* Async sockets use a FileHandler internally while connecting, so we + * need to cache this request until the connection has succeeded. */ + statePtr->filehandlers = mask; + } else if (mask) { + Tcl_CreateFileHandler(statePtr->fds.fd, mask, + (Tcl_FileProc *) Tcl_NotifyChannel, statePtr->channel); + } else { + Tcl_DeleteFileHandler(statePtr->fds.fd); } } @@ -832,61 +899,83 @@ TcpGetHandleProc( int direction, /* Not used. */ ClientData *handlePtr) /* Where to store the handle. */ { - TcpState *statePtr = (TcpState *) instanceData; + TcpState *statePtr = instanceData; - *handlePtr = INT2PTR(statePtr->fds->fd); + *handlePtr = INT2PTR(statePtr->fds.fd); return TCL_OK; } /* *---------------------------------------------------------------------- * - * CreateSocket -- + * TcpAsyncCallback -- + * + * Called by the event handler that CreateClientSocket sets up + * internally for [socket -async] to get notified when the + * asyncronous connection attempt has succeeded or failed. + * + *---------------------------------------------------------------------- + */ +static void +TcpAsyncCallback( + ClientData clientData, /* The socket state. */ + int mask) /* Events of interest; an OR-ed combination of + * TCL_READABLE, TCL_WRITABLE and + * TCL_EXCEPTION. */ +{ + CreateClientSocket(NULL, clientData); +} + +/* + *---------------------------------------------------------------------- + * + * CreateClientSocket -- * - * This function opens a new socket in client or server mode and - * initializes the TcpState structure. + * This function opens a new socket in client mode. * * Results: - * Returns a new TcpState, or NULL with an error in the interp's result, - * if interp is not NULL. + * TCL_OK, if the socket was successfully connected or an asynchronous + * connection is in progress. If an error occurs, TCL_ERROR is returned + * and an error message is left in interp. * * Side effects: * Opens a socket. * + * Remarks: + * A single host name may resolve to more than one IP address, e.g. for + * an IPv4/IPv6 dual stack host. For handling asyncronously connecting + * sockets in the background for such hosts, this function can act as a + * coroutine. On the first call, it sets up the control variables for the + * two nested loops over the local and remote addresses. Once the first + * connection attempt is in progress, it sets up itself as a writable + * event handler for that socket, and returns. When the callback occurs, + * control is transferred to the "reenter" label, right after the initial + * return and the loops resume as if they had never been interrupted. + * For syncronously connecting sockets, the loops work the usual way. + * *---------------------------------------------------------------------- */ -static TcpState * +static int CreateClientSocket( 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. - * NULL implies INADDR_ANY/in6addr_any */ - int myport, /* Optional client-side port */ - int async) /* If nonzero and creating a client socket, - * attempt to do an async connect. Otherwise - * do a synchronous connect or bind. */ + TcpState *state) { - int status = -1, connected = 0, sock = -1; - struct addrinfo *addrlist = NULL, *addrPtr; - /* Socket address */ - struct addrinfo *myaddrlist = NULL, *myaddrPtr; - /* Socket address for client */ - TcpState *statePtr; - const char *errorMsg = NULL; + socklen_t optlen; + int async_callback = (state->addr != NULL); + int status; + int async = state->flags & TCP_ASYNC_CONNECT; - if (!TclCreateSocketAddress(interp, &addrlist, host, port, 0, &errorMsg)) { - goto error; - } - if (!TclCreateSocketAddress(interp, &myaddrlist, myaddr, myport, 1, &errorMsg)) { - goto error; + if (async_callback) { + goto reenter; } - for (addrPtr = addrlist; addrPtr != NULL; - addrPtr = addrPtr->ai_next) { - for (myaddrPtr = myaddrlist; myaddrPtr != NULL; - myaddrPtr = myaddrPtr->ai_next) { + for (state->addr = state->addrlist; state->addr != NULL; + state->addr = state->addr->ai_next) { + status = -1; + + for (state->myaddr = state->myaddrlist; state->myaddr != NULL; + state->myaddr = state->myaddr->ai_next) { int reuseaddr; /* @@ -894,12 +983,22 @@ CreateClientSocket( * different families. */ - if (myaddrPtr->ai_family != addrPtr->ai_family) { + if (state->myaddr->ai_family != state->addr->ai_family) { continue; } - sock = socket(addrPtr->ai_family, SOCK_STREAM, 0); - if (sock < 0) { + /* + * Close the socket if it is still open from the last unsuccessful + * iteration. + */ + + if (state->fds.fd >= 0) { + close(state->fds.fd); + state->fds.fd = -1; + } + + state->fds.fd = socket(state->addr->ai_family, SOCK_STREAM, 0); + if (state->fds.fd < 0) { continue; } @@ -908,27 +1007,29 @@ CreateClientSocket( * inherited by child processes. */ - fcntl(sock, F_SETFD, FD_CLOEXEC); + fcntl(state->fds.fd, F_SETFD, FD_CLOEXEC); /* * Set kernel space buffering */ - TclSockMinimumBuffers(INT2PTR(sock), SOCKET_BUFSIZE); + TclSockMinimumBuffers(INT2PTR(state->fds.fd), SOCKET_BUFSIZE); if (async) { - status = TclUnixSetBlockingMode(sock, TCL_MODE_NONBLOCKING); + status = TclUnixSetBlockingMode(state->fds.fd, + TCL_MODE_NONBLOCKING); if (status < 0) { - goto looperror; + continue; } } reuseaddr = 1; - (void) setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, + (void) setsockopt(state->fds.fd, SOL_SOCKET, SO_REUSEADDR, (char *) &reuseaddr, sizeof(reuseaddr)); - status = bind(sock, myaddrPtr->ai_addr, myaddrPtr->ai_addrlen); + status = bind(state->fds.fd, state->myaddr->ai_addr, + state->myaddr->ai_addrlen); if (status < 0) { - goto looperror; + continue; } /* @@ -938,70 +1039,75 @@ CreateClientSocket( * in being informed when the connect completes. */ - status = connect(sock, addrPtr->ai_addr, addrPtr->ai_addrlen); + status = connect(state->fds.fd, state->addr->ai_addr, + state->addr->ai_addrlen); if (status < 0 && errno == EINPROGRESS) { - status = 0; - } + Tcl_CreateFileHandler(state->fds.fd, + TCL_WRITABLE|TCL_EXCEPTION, TcpAsyncCallback, state); + return TCL_OK; + + reenter: + Tcl_DeleteFileHandler(state->fds.fd); + + /* + * Read the error state from the socket to see if the async + * connection has succeeded or failed. As this clears the + * error condition, we cache the status in the socket state + * struct for later retrieval by [fconfigure -error]. + */ + + optlen = sizeof(int); + + if (state->status == 0) { + getsockopt(state->fds.fd, SOL_SOCKET, SO_ERROR, + (char *) &status, &optlen); + state->status = status; + } else { + status = state->status; + state->status = 0; + } + } if (status == 0) { - connected = 1; - break; - } - looperror: - if (sock != -1) { - close(sock); - sock = -1; + goto out; } } - if (connected) { - break; - } - status = -1; - if (sock >= 0) { - close(sock); - sock = -1; - } } - if (async) { - /* - * Restore blocking mode. - */ - status = TclUnixSetBlockingMode(sock, TCL_MODE_BLOCKING); - } +out: -error: - if (addrlist) { - freeaddrinfo(addrlist); - } - if (myaddrlist) { - freeaddrinfo(myaddrlist); - } - - if (status < 0) { - if (interp != NULL) { - Tcl_AppendResult(interp, "couldn't open socket: ", - Tcl_PosixError(interp), NULL); - if (errorMsg != NULL) { - Tcl_AppendResult(interp, " (", errorMsg, ")", NULL); - } - } - if (sock != -1) { - close(sock); - } - return NULL; - } + CLEAR_BITS(state->flags, TCP_ASYNC_CONNECT); + if (async_callback) { + /* + * An asynchonous connection has finally succeeded or failed. + */ - /* - * Allocate a new TcpState for this socket. - */ + TcpWatchProc(state, state->filehandlers); + TclUnixSetBlockingMode(state->fds.fd, state->cachedBlocking); - statePtr = ckalloc(sizeof(TcpState)); - statePtr->flags = async ? TCP_ASYNC_CONNECT : 0; - statePtr->fds = ckalloc(sizeof(TcpFdList)); - memset(statePtr->fds, (int) 0, sizeof(TcpFdList)); - statePtr->fds->fd = sock; + /* + * We need to forward the writable event that brought us here, bcasue + * upon reading of getsockopt(SO_ERROR), at least some OSes clear the + * writable state from the socket, and so a subsequent select() on + * behalf of a script level [fileevent] would not fire. It doesn't + * hurt that this is also called in the successful case and will save + * the event mechanism one roundtrip through select(). + */ + + Tcl_NotifyChannel(state->channel, TCL_WRITABLE); + } else if (status != 0) { + /* + * Failure for either a synchronous connection, or an async one that + * failed before it could enter background mode, e.g. because an + * invalid -myaddr was given. + */ - return statePtr; + if (interp != NULL) { + Tcl_SetObjResult(interp, Tcl_ObjPrintf( + "couldn't open socket: %s", Tcl_PosixError(interp))); + } + return TCL_ERROR; + } + return TCL_OK; } /* @@ -1032,31 +1138,57 @@ Tcl_OpenTcpClient( * connect. Otherwise we do a blocking * connect. */ { - TcpState *statePtr; - char channelName[16 + TCL_INTEGER_SPACE]; + TcpState *state; + const char *errorMsg = NULL; + struct addrinfo *addrlist = NULL, *myaddrlist = NULL; + char channelName[SOCK_CHAN_LENGTH]; /* - * Create a new client socket and wrap it in a channel. + * Do the name lookups for the local and remote addresses. */ - statePtr = CreateClientSocket(interp, port, host, myaddr, myport, async); - if (statePtr == NULL) { - return NULL; + if (!TclCreateSocketAddress(interp, &addrlist, host, port, 0, &errorMsg) + || !TclCreateSocketAddress(interp, &myaddrlist, myaddr, myport, 1, + &errorMsg)) { + if (addrlist != NULL) { + freeaddrinfo(addrlist); + } + if (interp != NULL) { + Tcl_SetObjResult(interp, Tcl_ObjPrintf( + "couldn't open socket: %s", errorMsg)); + } + return NULL; } - statePtr->acceptProc = NULL; - statePtr->acceptProcData = NULL; + /* + * Allocate a new TcpState for this socket. + */ + state = ckalloc(sizeof(TcpState)); + memset(state, 0, sizeof(TcpState)); + state->flags = async ? TCP_ASYNC_CONNECT : 0; + state->cachedBlocking = TCL_MODE_BLOCKING; + state->addrlist = addrlist; + state->myaddrlist = myaddrlist; + state->fds.fd = -1; - sprintf(channelName, "sock%d", statePtr->fds->fd); + /* + * Create a new client socket and wrap it in a channel. + */ + if (CreateClientSocket(interp, state) != TCL_OK) { + TcpCloseProc(state, NULL); + return NULL; + } - statePtr->channel = Tcl_CreateChannel(&tcpChannelType, channelName, - statePtr, (TCL_READABLE | TCL_WRITABLE)); - if (Tcl_SetChannelOption(interp, statePtr->channel, "-translation", + sprintf(channelName, SOCK_TEMPLATE, (long) state); + + state->channel = Tcl_CreateChannel(&tcpChannelType, channelName, state, + (TCL_READABLE | TCL_WRITABLE)); + if (Tcl_SetChannelOption(interp, state->channel, "-translation", "auto crlf") == TCL_ERROR) { - Tcl_Close(NULL, statePtr->channel); + Tcl_Close(NULL, state->channel); return NULL; } - return statePtr->channel; + return state->channel; } /* @@ -1079,7 +1211,7 @@ Tcl_Channel Tcl_MakeTcpClientChannel( ClientData sock) /* The socket to wrap up into a channel. */ { - return TclpMakeTcpClientChannelMode(sock, (TCL_READABLE | TCL_WRITABLE)); + return (Tcl_Channel) TclpMakeTcpClientChannelMode(sock, (TCL_READABLE | TCL_WRITABLE)); } /* @@ -1099,24 +1231,21 @@ Tcl_MakeTcpClientChannel( *---------------------------------------------------------------------- */ -Tcl_Channel +void * TclpMakeTcpClientChannelMode( - ClientData sock, /* The socket to wrap up into a channel. */ + void *sock, /* The socket to wrap up into a channel. */ int mode) /* ORed combination of TCL_READABLE and * TCL_WRITABLE to indicate file mode. */ { TcpState *statePtr; - char channelName[16 + TCL_INTEGER_SPACE]; + char channelName[SOCK_CHAN_LENGTH]; statePtr = ckalloc(sizeof(TcpState)); - statePtr->fds = ckalloc(sizeof(TcpFdList)); - memset(statePtr->fds, (int) 0, sizeof(TcpFdList)); - statePtr->fds->fd = PTR2INT(sock); + memset(statePtr, 0, sizeof(TcpState)); + statePtr->fds.fd = PTR2INT(sock); statePtr->flags = 0; - statePtr->acceptProc = NULL; - statePtr->acceptProcData = NULL; - sprintf(channelName, "sock%d", statePtr->fds->fd); + sprintf(channelName, SOCK_TEMPLATE, (long)statePtr); statePtr->channel = Tcl_CreateChannel(&tcpChannelType, channelName, statePtr, mode); @@ -1158,17 +1287,31 @@ Tcl_OpenTcpServer( int status = 0, sock = -1, reuseaddr = 1, chosenport = 0; struct addrinfo *addrlist = NULL, *addrPtr; /* socket address */ TcpState *statePtr = NULL; - char channelName[16 + TCL_INTEGER_SPACE]; + char channelName[SOCK_CHAN_LENGTH]; const char *errorMsg = NULL; TcpFdList *fds = NULL, *newfds; + /* + * Try to record and return the most meaningful error message, i.e. the + * one from the first socket that went the farthest before it failed. + */ + + enum { LOOKUP, SOCKET, BIND, LISTEN } howfar = LOOKUP; + int my_errno = 0; + if (!TclCreateSocketAddress(interp, &addrlist, myHost, port, 1, &errorMsg)) { + my_errno = errno; goto error; } for (addrPtr = addrlist; addrPtr != NULL; addrPtr = addrPtr->ai_next) { - sock = socket(addrPtr->ai_family, SOCK_STREAM, 0); + sock = socket(addrPtr->ai_family, addrPtr->ai_socktype, + addrPtr->ai_protocol); if (sock == -1) { + if (howfar < SOCKET) { + howfar = SOCKET; + my_errno = errno; + } continue; } @@ -1214,11 +1357,16 @@ Tcl_OpenTcpServer( (void) setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, &v6only, sizeof(v6only)); } -#endif +#endif /* IPV6_V6ONLY */ status = bind(sock, addrPtr->ai_addr, addrPtr->ai_addrlen); if (status == -1) { + if (howfar < BIND) { + howfar = BIND; + my_errno = errno; + } close(sock); + sock = -1; continue; } if (port == 0 && chosenport == 0) { @@ -1236,22 +1384,28 @@ Tcl_OpenTcpServer( } status = listen(sock, SOMAXCONN); if (status < 0) { + if (howfar < LISTEN) { + howfar = LISTEN; + my_errno = errno; + } close(sock); + sock = -1; continue; } - newfds = ckalloc(sizeof(TcpFdList)); - memset(newfds, (int) 0, sizeof(TcpFdList)); if (statePtr == NULL) { /* * Allocate a new TcpState for this socket. */ statePtr = ckalloc(sizeof(TcpState)); - statePtr->fds = newfds; + memset(statePtr, 0, sizeof(TcpState)); statePtr->acceptProc = acceptProc; statePtr->acceptProcData = acceptProcData; - sprintf(channelName, "sock%d", sock); + sprintf(channelName, SOCK_TEMPLATE, (long) statePtr); + newfds = &statePtr->fds; } else { + newfds = ckalloc(sizeof(TcpFdList)); + memset(newfds, (int) 0, sizeof(TcpFdList)); fds->next = newfds; } newfds->fd = sock; @@ -1276,11 +1430,15 @@ Tcl_OpenTcpServer( return statePtr->channel; } if (interp != NULL) { - Tcl_AppendResult(interp, "couldn't open socket: ", - Tcl_PosixError(interp), NULL); - if (errorMsg != NULL) { - Tcl_AppendResult(interp, " (", errorMsg, ")", NULL); + Tcl_Obj *errorObj = Tcl_NewStringObj("couldn't open socket: ", -1); + + if (errorMsg == NULL) { + errno = my_errno; + Tcl_AppendToObj(errorObj, Tcl_PosixError(interp), -1); + } else { + Tcl_AppendToObj(errorObj, errorMsg, -1); } + Tcl_SetObjResult(interp, errorObj); } if (sock != -1) { close(sock); @@ -1315,11 +1473,11 @@ TcpAccept( TcpState *newSockState; /* State for new socket. */ address addr; /* The remote address */ socklen_t len; /* For accept interface */ - char channelName[16 + TCL_INTEGER_SPACE]; + char channelName[SOCK_CHAN_LENGTH]; char host[NI_MAXHOST], port[NI_MAXSERV]; len = sizeof(addr); - newsock = accept(fds->fd, &(addr.sa), &len); + newsock = accept(fds->fd, &addr.sa, &len); if (newsock < 0) { return; } @@ -1332,15 +1490,11 @@ TcpAccept( (void) fcntl(newsock, F_SETFD, FD_CLOEXEC); newSockState = ckalloc(sizeof(TcpState)); - + memset(newSockState, 0, sizeof(TcpState)); newSockState->flags = 0; - newSockState->fds = ckalloc(sizeof(TcpFdList)); - memset(newSockState->fds, (int) 0, sizeof(TcpFdList)); - newSockState->fds->fd = newsock; - newSockState->acceptProc = NULL; - newSockState->acceptProcData = NULL; + newSockState->fds.fd = newsock; - sprintf(channelName, "sock%d", newsock); + sprintf(channelName, SOCK_TEMPLATE, (long) newSockState); newSockState->channel = Tcl_CreateChannel(&tcpChannelType, channelName, newSockState, (TCL_READABLE | TCL_WRITABLE)); @@ -1348,7 +1502,7 @@ TcpAccept( "auto crlf"); if (fds->statePtr->acceptProc != NULL) { - getnameinfo(&(addr.sa), len, host, sizeof(host), port, sizeof(port), + getnameinfo(&addr.sa, len, host, sizeof(host), port, sizeof(port), NI_NUMERICHOST|NI_NUMERICSERV); fds->statePtr->acceptProc(fds->statePtr->acceptProcData, newSockState->channel, host, atoi(port)); |