diff options
| -rw-r--r-- | ChangeLog | 8 | ||||
| -rw-r--r-- | tests/socket.test | 22 | ||||
| -rw-r--r-- | unix/tclUnixSock.c | 231 | 
3 files changed, 163 insertions, 98 deletions
@@ -1,3 +1,11 @@ +2011-05-27  Reinhard Max  <max@suse.de> + +	* unix/tclUnixSock.c: Fix [socket -async], so that all addresses +	returned by getaddrinfo() are tried, not just the first one. This +	requires the event loop to be running while the async connection +	is in progress. ***POTENTIAL INCOMPATIBILITY*** +	* tests/socket.test: Add a test for the above. +  2011-05-25  Don Porter  <dgp@users.sourceforge.net>  	* library/msgcat/msgcat.tcl:	Bump to msgcat 1.4.4. diff --git a/tests/socket.test b/tests/socket.test index 83bad09..1bb9b79 100644 --- a/tests/socket.test +++ b/tests/socket.test @@ -1699,6 +1699,28 @@ if {$remoteProcChan ne ""} {  catch {close $commandSocket}  catch {close $remoteProcChan}  } +unset ::tcl::unsupported::socketAF +test socket-14.0 {async when server only listens on one address family} \ +    -constraints [list socket supported_any] \ +    -setup { +        proc accept {s a p} { +            global x +            puts $s bye +            close $s +            set x ok +        } +        set server [socket -server accept -myaddr 127.0.0.1 0] +        set port [lindex [fconfigure $server -sockname] 2] +    } -body { +        set client [socket -async localhost $port] +        # fileevent $client readable [list set x [fconfigure $client -error]] +        after 1000 {set x [fconfigure $client -error]} +        vwait x +        set x +    } -cleanup { +        close $server +        close $client +    } -result ok  ::tcltest::cleanupTests  flush stdout  return diff --git a/unix/tclUnixSock.c b/unix/tclUnixSock.c index cb72759..f6a1bf2 100644 --- a/unix/tclUnixSock.c +++ b/unix/tclUnixSock.c @@ -49,9 +49,22 @@ struct TcpState {      TcpFdList *fds;		/* The file descriptors of the sockets. */      int flags;			/* ORed combination of the bitfields defined  				 * below. */ -    Tcl_TcpAcceptProc *acceptProc; +    union { +        struct { +            /* Only needed for server sockets */ +            Tcl_TcpAcceptProc *acceptProc;  				/* Proc to call on accept. */ -    ClientData acceptProcData;	/* The data for the accept proc. */ +            ClientData acceptProcData; +				/* The data for the accept proc. */ +        }; +        struct { +            /* Only needed for client sockets */ +            struct addrinfo *addrlist; +            struct addrinfo *myaddrlist; +            struct addrinfo *addr; +            struct addrinfo *myaddr; +        }; +    };  };  /* @@ -89,9 +102,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, @@ -829,17 +841,27 @@ TcpGetHandleProc(      return TCL_OK;  } +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); +} +  /*   *----------------------------------------------------------------------   * - * CreateSocket -- + * 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. @@ -847,37 +869,22 @@ TcpGetHandleProc(   *----------------------------------------------------------------------   */ -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; +    int status = -1, connected = 0; +    int async = state->flags & TCP_ASYNC_CONNECT; -    if (!TclCreateSocketAddress(interp, &addrlist, host, port, 0, &errorMsg)) { -	goto error; +    if (state->addr != NULL) { +        goto coro_continue;      } -    if (!TclCreateSocketAddress(interp, &myaddrlist, myaddr, myport, 1, &errorMsg)) { -	goto error; -    } - -    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) { +        for (state->myaddr = state->myaddrlist; state->myaddr != NULL; +             state->myaddr = state->myaddr->ai_next) {              int reuseaddr;  	    /* @@ -885,12 +892,12 @@ 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) { +	    state->fds->fd = socket(state->addr->ai_family, SOCK_STREAM, 0); +	    if (state->fds->fd < 0) {  		continue;  	    } @@ -899,25 +906,26 @@ 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;  		}  	    }              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;              } @@ -929,27 +937,39 @@ 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, +                                      TcpAsyncCallback, state); +                // fprintf(stderr, "here: %d \n", state->fds->fd); +                return TCL_OK; +            coro_continue: +                do { +                    socklen_t optlen = sizeof(int); +                    Tcl_DeleteFileHandler(state->fds->fd); +                    getsockopt(state->fds->fd, SOL_SOCKET, SO_ERROR, +                               (char *)&status, &optlen);              +                    // fprintf(stderr, "there: %d \n", state->fds->fd); +                } while (0); +            }  	    if (status == 0) {  		connected = 1;  		break;  	    }  	looperror: -	    if (sock != -1) { -		close(sock); -		sock = -1; +	    if (state->fds->fd != -1) { +		close(state->fds->fd); +		state->fds->fd = -1;  	    }  	}  	if (connected) {  	    break;  	}   	status = -1; -	if (sock >= 0) { -	    close(sock); -	    sock = -1; +	if (state->fds->fd >= 0) { +	    close(state->fds->fd); +	    state->fds->fd = -1;  	}      }      if (async) { @@ -957,42 +977,25 @@ CreateClientSocket(  	 * Restore blocking mode.  	 */ -	status = TclUnixSetBlockingMode(sock, TCL_MODE_BLOCKING); +	status = TclUnixSetBlockingMode(state->fds->fd, TCL_MODE_BLOCKING);      } -error: -    if (addrlist) { -	freeaddrinfo(addrlist); -    } -    if (myaddrlist) { -	freeaddrinfo(myaddrlist); -    } -     -    if (status < 0) { -	if (interp != NULL) { +    freeaddrinfo(state->addrlist); +    freeaddrinfo(state->myaddrlist); + +    if (status < 0 && !async) { +        if (interp != NULL) {  	    Tcl_AppendResult(interp, "couldn't open socket: ", -		    Tcl_PosixError(interp), NULL); -	    if (errorMsg != NULL) { -		Tcl_AppendResult(interp, " (", errorMsg, ")", NULL); -	    } +                             Tcl_PosixError(interp), NULL);  	} -	if (sock != -1) { -	    close(sock); +	if (state->fds->fd != -1) { +	    close(state->fds->fd);  	} -	return NULL; +        ckfree(state->fds); +        ckfree(state); +	return TCL_ERROR;      } - -    /* -     * Allocate a new TcpState for this socket. -     */ - -    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; - -    return statePtr; +    return TCL_OK;  }  /* @@ -1023,31 +1026,63 @@ 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, *myaddrlist; +    char channelName[4+16+1];	/* "sock" + up to 16 hex chars + \0 */ +      /* -     * 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)) { +	goto error; +    } +    if (!TclCreateSocketAddress(interp, &myaddrlist, myaddr, myport, 1, +                                &errorMsg)) { +        freeaddrinfo(addrlist); +        goto error;      } -    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->addrlist = addrlist; +    state->myaddrlist = myaddrlist; +    state->fds = ckalloc(sizeof(TcpFdList)); +    memset(state->fds, (int) 0, sizeof(TcpFdList)); +    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) { +        goto error; +    } -    statePtr->channel = Tcl_CreateChannel(&tcpChannelType, channelName, -	    statePtr, (TCL_READABLE | TCL_WRITABLE)); -    if (Tcl_SetChannelOption(interp, statePtr->channel, "-translation", +    sprintf(channelName, "sock%lx", (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; + +error: +    if (interp != NULL) { +        Tcl_AppendResult(interp, "couldn't open socket: ", +                         Tcl_PosixError(interp), NULL); +        if (errorMsg != NULL) { +            Tcl_AppendResult(interp, " (", errorMsg, ")", NULL); +        } +    } +    return NULL;  }  /*  | 
