diff --git a/TODO.win32 b/TODO.win32 index 09438698ab..b883a8c19c 100644 --- a/TODO.win32 +++ b/TODO.win32 @@ -10,7 +10,8 @@ - Child processes Should not be too hard using CreatePipe, CreateProcessW and GetExitCodeProcess. - Hooking up the child to a socket is tricky but it can be done (http://www.spinellis.gr/sw/unix/socketpipe/socketpipe-win.c) + Hooking up a child process to a file handle can be done; hooking up to a normal socket won't work; + we'd need some sort of pump() mechanism. Waiting for the child to exit is tricky, probably would require a wait thread to wait for the child, then ev_async notify. How can we distinguish between the exit code and exception number after calling GetExitCodeProcess? diff --git a/src/node.cc b/src/node.cc index 3c81bc9a86..d6a1745036 100644 --- a/src/node.cc +++ b/src/node.cc @@ -17,7 +17,8 @@ #include "platform.h" #ifdef __MINGW32__ -# include "platform_win32.h" /* winapi_perror() */ +# include /* winapi_perror() */ +# include /* wsa_init() */ #else // __POSIX__ # include /* dlopen(), dlsym() */ # include /* getpwnam() */ @@ -96,10 +97,6 @@ static ev_async eio_want_poll_notifier; static ev_async eio_done_poll_notifier; static ev_idle eio_poller; -#ifdef __MINGW32__ -WSAData winsockData; -#endif - // Buffer for getpwnam_r(), getgrpam_r() and other misc callers; keep this // scoped at file-level rather than method-level to avoid excess stack usage. static char getbuf[PATH_MAX + 1]; @@ -1952,11 +1949,8 @@ int Start(int argc, char *argv[]) { #endif // __POSIX__ #ifdef __MINGW32__ - // On windows, to use winsock it must be initialized - WORD winsockVersion = MAKEWORD(2, 2); - if (WSAStartup(winsockVersion, &winsockData)) { - winapi_perror("WSAStartup"); - } + // Initialize winsock and soem related caches + wsa_init(); #endif // __MINGW32__ // Initialize the default ev loop. diff --git a/src/node_net.cc b/src/node_net.cc index 8ba1b7185b..8b9b061bfe 100644 --- a/src/node_net.cc +++ b/src/node_net.cc @@ -716,11 +716,8 @@ static Handle Read(const Arguments& args) { return ThrowException(ErrnoException(errno, "read")); } #else // __MINGW32__ - /* - * read() _should_ work for sockets in mingw, but always gives EINVAL; - * someone should really file a bug about it. - * We'll use recv() for sockets however, it's faster as well. - */ + // read() doesn't work for overlapped sockets (the only usable + // type of sockets) so recv() is used here. ssize_t bytes_read = recv(_get_osfhandle(fd), (char*)buffer_data + off, len, 0); if (bytes_read < 0) { @@ -938,11 +935,8 @@ static Handle Write(const Arguments& args) { return ThrowException(ErrnoException(errno, "write")); } #else // __MINGW32__ - /* - * write() _should_ work for sockets in mingw, but always gives EINVAL; - * someone should really file a bug about it. - * We'll use send() for sockets however, it's faster as well. - */ + // write() doesn't work for overlapped sockets (the only usable + // type of sockets) so send() is used. ssize_t written = send(_get_osfhandle(fd), buffer_data + off, len, 0); if (written < 0) { diff --git a/src/platform_win32.cc b/src/platform_win32.cc index c3831c1c4a..3e7029e294 100644 --- a/src/platform_win32.cc +++ b/src/platform_win32.cc @@ -7,6 +7,7 @@ #include // getpagesize #include +#include "platform_win32_winsock.cc" namespace node { diff --git a/src/platform_win32_winsock.cc b/src/platform_win32_winsock.cc new file mode 100644 index 0000000000..6c858e70e1 --- /dev/null +++ b/src/platform_win32_winsock.cc @@ -0,0 +1,331 @@ +/* + * This file contains all winsock-related stuff. + * Socketpair() for winsock is implemented here. + * There are also functions to create a non-overlapped socket (which windows normally doesn't do) + * and to create a socketpair that has one synchronous and one async socket. + * Synchronous sockets are required because async sockets can't be used by child processes. + */ + + +#include +#include +#include +#include +#include +#include + + +namespace node { + + +/* + * Winsock version data goes here + */ +static WSAData winsock_info; + + +/* + * Cache for WSAPROTOCOL_INFOW structures for protocols used in node + * [0] TCP/IP + * [1] UDP/IP + * [2] TCP/IPv6 + * [3] UDP/IPv6 + */ +static WSAPROTOCOL_INFOW proto_info_cache[4]; + + +/* + * Does the about the same as perror(), but for winsock errors + */ +void wsa_perror(const char *prefix) { + DWORD errorno = WSAGetLastError(); + char *errmsg; + + FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, errorno, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR)&errmsg, 0, NULL); + + // FormatMessage messages include a newline character + + if (prefix) { + fprintf(stderr, "%s: %s", prefix, errmsg); + } else { + fputs(errmsg, stderr); + } +} + + +/* + * Retrieves a pointer to a WSAPROTOCOL_INFOW structure + * related to a certain winsock protocol from the cache + */ +inline static WSAPROTOCOL_INFOW *wsa_get_cached_proto_info(int af, int type, int proto) { + assert(proto == IPPROTO_IP + || proto == IPPROTO_TCP + || proto == IPPROTO_UDP); + + switch (af) { + case AF_INET: + switch (type) { + case SOCK_STREAM: + return &proto_info_cache[0]; + case SOCK_DGRAM: + return &proto_info_cache[1]; + } + break; + + case AF_INET6: + switch (type) { + case SOCK_STREAM: + return &proto_info_cache[2]; + case SOCK_DGRAM: + return &proto_info_cache[3]; + } + break; + } + + WSASetLastError(WSAEPROTONOSUPPORT); + return NULL; +} + + +/* + * Creates a synchronous, non-overlapped socket. + * (The sockets that are created with socket() or accept() are always in overlapped mode.) + * Doubles the winsock api, e.g. returns a SOCKET handle, not an FD + */ +SOCKET wsa_sync_socket(int af, int type, int proto) { + WSAPROTOCOL_INFOW *protoInfo = wsa_get_cached_proto_info(af, type, proto); + if (protoInfo == NULL) + return INVALID_SOCKET; + + return WSASocketW(af, type, proto, protoInfo, 0, 0); +} + + +/* + * Create a socketpair using the protocol specified + * This function uses winsock semantics, it returns SOCKET handles, not FDs + * Currently supports TCP/IPv4 socket pairs only + */ +int wsa_socketpair(int af, int type, int proto, SOCKET sock[2]) { + assert(af == AF_INET + && type == SOCK_STREAM + && (proto == IPPROTO_IP || proto == IPPROTO_TCP)); + + SOCKET listen_sock; + SOCKADDR_IN addr1; + SOCKADDR_IN addr2; + int addr1_len = sizeof (addr1); + int addr2_len = sizeof (addr2); + sock[1] = INVALID_SOCKET; + sock[2] = INVALID_SOCKET; + + if ((listen_sock = socket(af, type, proto)) == INVALID_SOCKET) + goto error; + + memset((void*)&addr1, 0, sizeof(addr1)); + addr1.sin_family = af; + addr1.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + addr1.sin_port = 0; + + if (bind(listen_sock, (SOCKADDR*)&addr1, addr1_len) == SOCKET_ERROR) + goto error; + + if (getsockname(listen_sock, (SOCKADDR*)&addr1, &addr1_len) == SOCKET_ERROR) + goto error; + + if (listen(listen_sock, 1)) + goto error; + + if ((sock[0] = socket(af, type, proto)) == INVALID_SOCKET) + goto error; + + if (connect(sock[0], (SOCKADDR*)&addr1, addr1_len)) + goto error; + + if ((sock[1] = accept(listen_sock, 0, 0)) == INVALID_SOCKET) + goto error; + + if (getpeername(sock[0], (SOCKADDR*)&addr1, &addr1_len) == INVALID_SOCKET) + goto error; + + if (getsockname(sock[1], (SOCKADDR*)&addr2, &addr2_len) == INVALID_SOCKET) + goto error; + + if (addr1_len != addr2_len + || addr1.sin_addr.s_addr != addr2.sin_addr.s_addr + || addr1.sin_port != addr2.sin_port) + goto error; + + closesocket(listen_sock); + + return 0; + +error: + int error = WSAGetLastError(); + + if (listen_sock != INVALID_SOCKET) + closesocket(listen_sock); + + if (sock[0] != INVALID_SOCKET) + closesocket(sock[0]); + + if (sock[1] != INVALID_SOCKET) + closesocket(sock[1]); + + WSASetLastError(error); + + return SOCKET_ERROR; +} + + +/* + * Create a sync-async socketpair using the protocol specified, + * returning a synchronous socket and an asynchronous socket. + * Upon completion asyncSocket is opened with the WSA_FLAG_OVERLAPPED flag set, + * syncSocket won't have it set. + * Currently supports TCP/IPv4 socket pairs only + */ +int wsa_sync_async_socketpair(int af, int type, int proto, SOCKET *syncSocket, SOCKET *asyncSocket) { + assert(af == AF_INET + && type == SOCK_STREAM + && (proto == IPPROTO_IP || proto == IPPROTO_TCP)); + + SOCKET listen_sock; + SOCKET sock1 = INVALID_SOCKET; + SOCKET sock2 = INVALID_SOCKET; + SOCKADDR_IN addr1; + SOCKADDR_IN addr2; + int addr1_len = sizeof (addr1); + int addr2_len = sizeof (addr2); + + if ((listen_sock = socket(af, type, proto)) == INVALID_SOCKET) + goto error; + + memset((void*)&addr1, 0, sizeof(addr1)); + addr1.sin_family = af; + addr1.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + addr1.sin_port = 0; + + if (bind(listen_sock, (SOCKADDR*)&addr1, addr1_len) == SOCKET_ERROR) + goto error; + + if (getsockname(listen_sock, (SOCKADDR*)&addr1, &addr1_len) == SOCKET_ERROR) + goto error; + + if (listen(listen_sock, 1)) + goto error; + + if ((sock1 = wsa_sync_socket(af, type, proto)) == INVALID_SOCKET) + goto error; + + if (connect(sock1, (SOCKADDR*)&addr1, addr1_len)) + goto error; + + if ((sock2 = accept(listen_sock, 0, 0)) == INVALID_SOCKET) + goto error; + + if (getpeername(sock1, (SOCKADDR*)&addr1, &addr1_len) == INVALID_SOCKET) + goto error; + + if (getsockname(sock2, (SOCKADDR*)&addr2, &addr2_len) == INVALID_SOCKET) + goto error; + + if (addr1_len != addr2_len + || addr1.sin_addr.s_addr != addr2.sin_addr.s_addr + || addr1.sin_port != addr2.sin_port) + goto error; + + closesocket(listen_sock); + + *syncSocket = sock1; + *asyncSocket = sock2; + + return 0; + +error: + int error = WSAGetLastError(); + + if (listen_sock != INVALID_SOCKET) + closesocket(listen_sock); + + if (sock1 != INVALID_SOCKET) + closesocket(sock1); + + if (sock2 != INVALID_SOCKET) + closesocket(sock2); + + WSASetLastError(error); + + return SOCKET_ERROR; +} + + +/* + * Retrieves a WSAPROTOCOL_INFOW structure for a certain protocol + */ +static void wsa_get_proto_info(int af, int type, int proto, WSAPROTOCOL_INFOW *target) { + WSAPROTOCOL_INFOW *info_buffer = NULL; + unsigned long info_buffer_length = 0; + int protocol_count, i, error; + + if (WSCEnumProtocols(NULL, NULL, &info_buffer_length, &error) != SOCKET_ERROR) { + error = WSAEOPNOTSUPP; + goto error; + } + + info_buffer = (WSAPROTOCOL_INFOW *)malloc(info_buffer_length); + + if ((protocol_count = WSCEnumProtocols(NULL, info_buffer, &info_buffer_length, &error)) == SOCKET_ERROR) + goto error; + + for (i = 0; i < protocol_count; i++) { + if (af == info_buffer[i].iAddressFamily + && type == info_buffer[i].iSocketType + && proto == info_buffer[i].iProtocol + && info_buffer[i].dwServiceFlags1 & XP1_IFS_HANDLES) { + memcpy(target, (WSAPROTOCOL_INFOW*)&info_buffer[i], sizeof(WSAPROTOCOL_INFOW)); + free(info_buffer); + return; + } + } + + error = WSAEPROTONOSUPPORT; + +error: + WSASetLastError(error); + wsa_perror("Error obtaining winsock protocol information"); + + if (info_buffer != NULL) { + free(info_buffer); + } +} + + +/* + * Initializes (fills) the WSAPROTOCOL_INFOW structure cache + */ +static void wsa_init_proto_info_cache() { + WSAPROTOCOL_INFOW *cache = (WSAPROTOCOL_INFOW*)&proto_info_cache; + + wsa_get_proto_info(AF_INET, SOCK_STREAM, IPPROTO_TCP, &proto_info_cache[0]); + wsa_get_proto_info(AF_INET, SOCK_DGRAM, IPPROTO_UDP, &proto_info_cache[1]); + wsa_get_proto_info(AF_INET6, SOCK_STREAM, IPPROTO_TCP, &proto_info_cache[2]); + wsa_get_proto_info(AF_INET6, SOCK_DGRAM, IPPROTO_UDP, &proto_info_cache[3]); +} + + +/* + * Initializes winsock and winsock-related stuff + */ +void wsa_init() { + WORD version = MAKEWORD(2, 2); + if (WSAStartup(version, &winsock_info)) { + wsa_perror("WSAStartup"); + } + + wsa_init_proto_info_cache(); +} + + +} // namespace node diff --git a/src/platform_win32_winsock.h b/src/platform_win32_winsock.h new file mode 100644 index 0000000000..126ee0dd4d --- /dev/null +++ b/src/platform_win32_winsock.h @@ -0,0 +1,21 @@ +#ifndef NODE_PLATFORM_WIN32_WINSOCK_H_ +#define NODE_PLATFORM_WIN32_WINSOCK_H_ + +#include +#include + +namespace node { + + +void wsa_init(); + +void wsa_perror(const char* prefix = ""); + +SOCKET wsa_sync_socket(int af, int type, int proto); +int wsa_socketpair(int af, int type, int proto, SOCKET sock[2]); +int wsa_sync_async_socketpair(int af, int type, int proto, SOCKET *syncSocket, SOCKET *asyncSocket); + + +} // namespace node + +#endif // NODE_PLATFORM_WIN32_WINSOCK_H_