Check unix domain socket functionality
[catagits/fcgi2.git] / libfcgi / os_win32.c
index 7f1f1c8..cb3550f 100755 (executable)
@@ -1,23 +1,19 @@
 /*
  * os_win32.c --
  *
- *
- *  Copyright (c) 1995 Open Market, Inc.
- *  All rights reserved.
- *
- *  This file contains proprietary and confidential information and
- *  remains the unpublished property of Open Market, Inc. Use,
- *  disclosure, or reproduction is prohibited except as permitted by
- *  express written license agreement with Open Market, Inc.
- *
  *  Bill Snapper
  *  snapper@openmarket.com
  *
  * (Special thanks to Karen and Bill.  They made my job much easier and
  *  significantly more enjoyable.)
+ *
+ * Copyright (c) 1996 Open Market, Inc.
+ *
+ * See the file "LICENSE" for information on usage and redistribution
+ * of this file, and for a DISCLAIMER OF ALL WARRANTIES.
  */
 #ifndef lint
-static const char rcsid[] = "$Id: os_win32.c,v 1.19 2001/06/22 03:00:18 robs Exp $";
+static const char rcsid[] = "$Id: os_win32.c,v 1.35 2004/01/31 17:47:07 robs Exp $";
 #endif /* not lint */
 
 #define WIN32_LEAN_AND_MEAN 
@@ -27,10 +23,13 @@ static const char rcsid[] = "$Id: os_win32.c,v 1.19 2001/06/22 03:00:18 robs Exp
 #include <assert.h>
 #include <stdio.h>
 #include <sys/timeb.h>
+#include <process.h>
+#include <signal.h>
 
 #define DLLAPI  __declspec(dllexport)
-#include "fcgios.h"
+
 #include "fcgimisc.h"
+#include "fcgios.h"
 
 #define WIN32_OPEN_MAX 128 /* XXX: Small hack */
 
@@ -51,9 +50,11 @@ static HANDLE hStdinThread = INVALID_HANDLE_VALUE;
 static HANDLE stdioHandles[3] = {INVALID_HANDLE_VALUE, INVALID_HANDLE_VALUE,
                                 INVALID_HANDLE_VALUE};
 
+// This is a nail for listening to more than one port..
 static HANDLE acceptMutex = INVALID_HANDLE_VALUE;
 
 static BOOLEAN shutdownPending = FALSE;
+static BOOLEAN shutdownNow = FALSE;
 
 /*
  * An enumeration of the file types
@@ -117,12 +118,11 @@ typedef struct OVERLAPPED_REQUEST *POVERLAPPED_REQUEST;
 
 static const char *bindPathPrefix = "\\\\.\\pipe\\FastCGI\\";
 
-static enum FILE_TYPE listenType = FD_UNUSED;
+static FILE_TYPE listenType = FD_UNUSED;
 
 // XXX This should be a DESCRIPTOR
 static HANDLE hListen = INVALID_HANDLE_VALUE;
 
-static OVERLAPPED listenOverlapped;
 static BOOLEAN libInitialized = FALSE;
 
 /*
@@ -220,8 +220,8 @@ static int Win32NewDescriptor(FILE_TYPE type, int fd, int desiredFd)
  *
  *--------------------------------------------------------------
  */
-static void StdinThread(LPDWORD startup){
-
+static void StdinThread(void * startup) 
+{
     int doIo = TRUE;
     unsigned long fd;
     unsigned long bytesRead;
@@ -261,27 +261,35 @@ static void StdinThread(LPDWORD startup){
     ExitThread(0);
 }
 
-static DWORD WINAPI ShutdownRequestThread(LPVOID arg)
+void OS_ShutdownPending(void)
+{
+    shutdownPending = TRUE;
+}
+
+static void ShutdownRequestThread(void * arg)
 {
     HANDLE shutdownEvent = (HANDLE) arg;
     
-    if (WaitForSingleObject(shutdownEvent, INFINITE) == WAIT_FAILED)
-    {
-        // Assuming it will happen again, all we can do is exit the thread
-        return 1;
-    }
-    else
+    WaitForSingleObject(shutdownEvent, INFINITE);
+
+    shutdownPending = TRUE;
+
+    // emulate the unix behaviour
+    raise(SIGTERM);
+
+    if (listenType == FD_PIPE_SYNC)
     {
-        // "Simple reads and writes to properly-aligned 32-bit variables are atomic"
-        shutdownPending = TRUE;
-        
-        // Before an accept() is entered the shutdownPending flag is checked.
-        // If set, OS_Accept() will return -1.  If not, it waits
-        // on a connection request for one second, checks the flag, & repeats.
-        // Only one process/thread is allowed to do this at time by
-        // wrapping the accept() with mutex.
-        return 0;
+        // Its a hassle to get ConnectNamedPipe to return early,
+        // so just wack the whole process - yes, this will toast
+        // any requests in progress, but at least its a clean 
+        // shutdown (its better than TerminateProcess())
+        exit(0);
     }
+       
+    // FD_SOCKET_SYNC: When in Accept(), select() is used to poll
+    // the shutdownPending flag - yeah this isn't pretty either
+    // but its only one process doing it if an Accept mutex is used.
+    // This at least buys no toasted requests.
 }
 
 /*
@@ -305,7 +313,6 @@ int OS_LibInit(int stdioFds[3])
     WSADATA wsaData;
     int err;
     int fakeFd;
-    DWORD threadId;
     char *cLenPtr = NULL;
     char *val = NULL;
         
@@ -348,23 +355,22 @@ int OS_LibInit(int stdioFds[3])
     {
         HANDLE shutdownEvent = (HANDLE) atoi(val);
 
-        if (! CreateThread(NULL, 0, ShutdownRequestThread, 
-                           shutdownEvent, 0, NULL))
+        if (_beginthread(ShutdownRequestThread, 0, shutdownEvent) == -1)
         {
             return -1;
         }
     }
 
-    /*
-     * If an accept mutex is in the env, save it and remove it.
-     */
-    val = getenv(MUTEX_VARNAME);
-    if (val != NULL) 
+    if (acceptMutex == INVALID_HANDLE_VALUE)
     {
-        acceptMutex = (HANDLE) atoi(val);
+        /* If an accept mutex is in the env, use it */
+        val = getenv(MUTEX_VARNAME);
+        if (val != NULL) 
+        {
+            acceptMutex = (HANDLE) atoi(val);
+        }
     }
 
-
     /*
      * Determine if this library is being used to listen for FastCGI
      * connections.  This is communicated by STDIN containing a
@@ -411,7 +417,6 @@ int OS_LibInit(int stdioFds[3])
         if (SetNamedPipeHandleState(hListen, &pipeMode, NULL, NULL)) 
         {
             listenType = FD_PIPE_SYNC;
-            listenOverlapped.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
         } 
         else 
         {
@@ -484,10 +489,8 @@ int OS_LibInit(int stdioFds[3])
      */
     if((cLenPtr = getenv("CONTENT_LENGTH")) != NULL &&
        atoi(cLenPtr) > 0) {
-        hStdinThread = CreateThread(NULL, 8192,
-                                   (LPTHREAD_START_ROUTINE)&StdinThread,
-                                   NULL, 0, &threadId);
-       if (hStdinThread == NULL) {
+        hStdinThread = (HANDLE) _beginthread(StdinThread, 0, NULL);
+       if (hStdinThread == (HANDLE) -1) {
            printf("<H2>OS_LibInit Failed to create STDIN thread!  ERROR: %d</H2>\r\n\r\n",
                   GetLastError());
            return -1;
@@ -680,26 +683,14 @@ int OS_CreateLocalIpcFd(const char *bindPath, int backlog)
 {
     int pseudoFd = -1;
     short port = getPort(bindPath);
-    HANDLE mutex = CreateMutex(NULL, FALSE, NULL);
-    char * mutexEnvString;
-
-    if (mutex == NULL)
-    {
-        return -1;
-    }
 
-    if (! SetHandleInformation(mutex, HANDLE_FLAG_INHERIT, TRUE))
+    if (acceptMutex == INVALID_HANDLE_VALUE)
     {
-        return -1;
+        acceptMutex = CreateMutex(NULL, FALSE, NULL);
+        if (acceptMutex == NULL) return -2;
+        if (! SetHandleInformation(acceptMutex, HANDLE_FLAG_INHERIT, TRUE)) return -3;
     }
 
-    // This is a nail for listening to more than one port..
-    // This should really be handled by the caller.
-
-    mutexEnvString = malloc(strlen(MUTEX_VARNAME) + 7);
-    sprintf(mutexEnvString, MUTEX_VARNAME "=%d", (int) mutex);
-    putenv(mutexEnvString);
-
     // There's nothing to be gained (at the moment) by a shutdown Event    
 
     if (port && *bindPath != ':' && strncmp(bindPath, LOCALHOST, strlen(LOCALHOST)))
@@ -727,13 +718,17 @@ int OS_CreateLocalIpcFd(const char *bindPath, int backlog)
         listenSock = socket(AF_INET, SOCK_STREAM, 0);
         if (listenSock == INVALID_SOCKET) 
         {
-               return -1;
+               return -4;
            }
 
-           if (! bind(listenSock, (struct sockaddr *) &sockAddr, sockLen)
-               || ! listen(listenSock, backlog)) 
+           if (bind(listenSock, (struct sockaddr *) &sockAddr, sockLen)  )
         {
-               return -1;
+               return -12;
+           }
+
+           if (listen(listenSock, backlog)) 
+        {
+               return -5;
            }
 
         pseudoFd = Win32NewDescriptor(listenType, listenSock, -1);
@@ -741,7 +736,7 @@ int OS_CreateLocalIpcFd(const char *bindPath, int backlog)
         if (pseudoFd == -1) 
         {
             closesocket(listenSock);
-            return -1;
+            return -6;
         }
 
         hListen = (HANDLE) listenSock;        
@@ -753,14 +748,14 @@ int OS_CreateLocalIpcFd(const char *bindPath, int backlog)
         
         if (! pipePath) 
         {
-            return -1;
+            return -7;
         }
 
         strcpy(pipePath, bindPathPrefix);
         strcat(pipePath, bindPath);
 
         hListenPipe = CreateNamedPipe(pipePath,
-                       PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED,
+                       PIPE_ACCESS_DUPLEX,
                        PIPE_TYPE_BYTE | PIPE_WAIT | PIPE_READMODE_BYTE,
                        PIPE_UNLIMITED_INSTANCES,
                        4096, 4096, 0, NULL);
@@ -769,12 +764,12 @@ int OS_CreateLocalIpcFd(const char *bindPath, int backlog)
 
         if (hListenPipe == INVALID_HANDLE_VALUE)
         {
-            return -1;
+            return -8;
         }
 
         if (! SetHandleInformation(hListenPipe, HANDLE_FLAG_INHERIT, TRUE))
         {
-            return -1;
+            return -9;
         }
 
         pseudoFd = Win32NewDescriptor(listenType, (int) hListenPipe, -1);
@@ -782,7 +777,7 @@ int OS_CreateLocalIpcFd(const char *bindPath, int backlog)
         if (pseudoFd == -1) 
         {
             CloseHandle(hListenPipe);
-            return -1;
+            return -10;
         }
 
         hListen = (HANDLE) hListenPipe;
@@ -941,6 +936,8 @@ int OS_Read(int fd, char * buf, size_t len)
 
     ASSERT((fd >= 0) && (fd < WIN32_OPEN_MAX));
 
+    if (shutdownNow) return -1;
+
     switch (fdTable[fd].type) 
     {
        case FD_FILE_SYNC:
@@ -1002,6 +999,8 @@ int OS_Write(int fd, char * buf, size_t len)
 
     ASSERT(fd >= 0 && fd < WIN32_OPEN_MAX);
 
+    if (shutdownNow) return -1;
+
     switch (fdTable[fd].type) 
     {
        case FD_FILE_SYNC:
@@ -1353,7 +1352,7 @@ int OS_AsyncWrite(int fd, int offset, void *buf, int len,
  *
  *--------------------------------------------------------------
  */
-int OS_Close(int fd)
+int OS_Close(int fd, int shutdown_ok)
 {
     int ret = 0;
 
@@ -1368,24 +1367,53 @@ int OS_Close(int fd)
        case FD_PIPE_ASYNC:
        case FD_FILE_SYNC:
        case FD_FILE_ASYNC:
-           break;
+           
+        break;
 
-        case FD_SOCKET_SYNC:
+    case FD_SOCKET_SYNC:
        case FD_SOCKET_ASYNC:
-           /*
-            * Closing a socket that has an async read outstanding causes a
-            * tcp reset and possible data loss.  The shutdown call seems to
-            * prevent this.
-            */
-           shutdown(fdTable[fd].fid.sock, 2);
-           /*
-            * closesocket returns: 0 success, SOCKET_ERROR failure
-            */
-           if (closesocket(fdTable[fd].fid.sock) == SOCKET_ERROR)
-               ret = -1;
-           break;
+
+        /*
+         * shutdown() the send side and then read() from client until EOF
+         * or a timeout expires.  This is done to minimize the potential
+         * that a TCP RST will be sent by our TCP stack in response to 
+         * receipt of additional data from the client.  The RST would
+         * cause the client to discard potentially useful response data.
+         */
+
+        if (shutdown_ok)
+        {
+            if (shutdown(fdTable[fd].fid.sock, SD_SEND) == 0)
+            {
+                struct timeval tv;
+                fd_set rfds;
+                int sock = fdTable[fd].fid.sock;
+                int rv;
+                char trash[1024];
+   
+                FD_ZERO(&rfds);
+
+                do 
+                {
+#pragma warning( disable : 4127 ) 
+                   FD_SET((unsigned) sock, &rfds);
+#pragma warning( default : 4127 )
+                    
+                   tv.tv_sec = 2;
+                   tv.tv_usec = 0;
+                   rv = select(sock + 1, &rfds, NULL, NULL, &tv);
+                }
+                while (rv > 0 && recv(sock, trash, sizeof(trash), 0) > 0);
+            }
+        }
+        
+        closesocket(fdTable[fd].fid.sock);
+
+        break;
+
        default:
-           return -1;          /* fake failure */
+
+           ret = -1;           /* fake failure */
     }
 
     Win32FreeDescriptor(fd);
@@ -1484,55 +1512,58 @@ int OS_DoIo(struct timeval *tmo)
     return 0;
 }
 
-
-static int CALLBACK isAddrOK(LPWSABUF  lpCallerId,
-                             LPWSABUF  dc0,
-                             LPQOS     dc1,
-                             LPQOS     dc2,
-                             LPWSABUF  dc3,
-                             LPWSABUF  dc4,
-                             GROUP     *dc5,
-                             DWORD     dwCallbackData)
+static int isAddrOK(struct sockaddr_in * inet_sockaddr, const char * okAddrs)
 {
-    const char *okAddrs = (char *) dwCallbackData;
-    struct sockaddr *sockaddr = (struct sockaddr *) lpCallerId->buf;
+    static const char *token = " ,;:\t";
+    char *ipaddr;
+    char *p;
 
-    // Touch the args to avoid warnings
-    dc0 = NULL; dc1 = NULL; dc2 = NULL; dc3 = NULL; dc4 = NULL; dc5 = NULL;
+    if (okAddrs == NULL) return TRUE;
+
+    ipaddr = inet_ntoa(inet_sockaddr->sin_addr);
+    p = strstr(okAddrs, ipaddr);
 
-    if (okAddrs == NULL || sockaddr->sa_family != AF_INET)
+    if (p == NULL) return FALSE;
+
+    if (p == okAddrs)
     {
-        return TRUE;
+        p += strlen(ipaddr);
+        return (strchr(token, *p) != NULL);
     }
-    else
-    {
-        static const char *token = " ,;:\t";
-        struct sockaddr_in * inet_sockaddr = (struct sockaddr_in *) sockaddr;
-        char *ipaddr = inet_ntoa(inet_sockaddr->sin_addr);
-        char *p = strstr(okAddrs, ipaddr);
 
-        if (p == NULL)
-        {
-            return FALSE;
-        }
-        else if (p == okAddrs)
-        {
-            p += strlen(ipaddr);
-            return (strchr(token, *p) != NULL);
-        }
-        else if (strchr(token, *--p))
-        {
-            p += strlen(ipaddr) + 1;
-            return (strchr(token, *p) != NULL);
-        }
-        else
-        {
-            return FALSE;
-        }
+    if (strchr(token, *--p) != NULL)
+    {
+        p += strlen(ipaddr) + 1;
+        return (strchr(token, *p) != NULL);
     }
+
+    return FALSE;
 }
 
-static printLastError(const char * text)
+#ifndef NO_WSAACEPT
+static int CALLBACK isAddrOKCallback(LPWSABUF  lpCallerId,
+                                     LPWSABUF  dc0,
+                                     LPQOS     dc1,
+                                     LPQOS     dc2,
+                                     LPWSABUF  dc3,
+                                     LPWSABUF  dc4,
+                                     GROUP     *dc5,
+                                     DWORD     data)
+{
+    struct sockaddr_in *sockaddr = (struct sockaddr_in *) lpCallerId->buf;
+
+    // Touch the args to avoid warnings
+    dc0 = NULL; dc1 = NULL; dc2 = NULL; dc3 = NULL; dc4 = NULL; dc5 = NULL;
+
+    if ((void *) data == NULL) return CF_ACCEPT;
+
+    if (sockaddr->sin_family != AF_INET) return CF_ACCEPT;
+
+    return isAddrOK(sockaddr, (const char *) data) ? CF_ACCEPT : CF_REJECT;
+}
+#endif
+
+static void printLastError(const char * text)
 {
     LPVOID buf;
 
@@ -1556,7 +1587,7 @@ static int acceptNamedPipe()
 {
     int ipcFd = -1;
 
-    if (! ConnectNamedPipe(hListen, &listenOverlapped))
+    if (! ConnectNamedPipe(hListen, NULL))
     {
         switch (GetLastError())
         {
@@ -1569,19 +1600,9 @@ static int acceptNamedPipe()
         
             case ERROR_IO_PENDING:
 
-                // Wait for a connection to complete.
-
-                while (WaitForSingleObject(listenOverlapped.hEvent, 
-                                           ACCEPT_TIMEOUT) == WAIT_TIMEOUT) 
-                {
-                    if (shutdownPending) 
-                    {
-                        OS_LibShutdown();
-                        return -1;
-                    }            
-                }
-
-                break;
+                // The NamedPipe was opened with an Overlapped structure
+                // and there is a pending io operation.  mod_fastcgi 
+                // did this in 2.2.12 (fcgi_pm.c v1.52).
 
             case ERROR_PIPE_LISTENING:
 
@@ -1609,39 +1630,75 @@ static int acceptNamedPipe()
 
 static int acceptSocket(const char *webServerAddrs)
 {
-    struct sockaddr sockaddr;
-    int sockaddrLen = sizeof(sockaddr);
-    fd_set readfds;
-    const struct timeval timeout = {1, 0};
     SOCKET hSock;
     int ipcFd = -1;
-    FD_ZERO(&readfds);
-    FD_SET((unsigned int) hListen, &readfds);
-    
-    while (select(0, &readfds, NULL, NULL, &timeout) == 0)
+
+    for (;;)
     {
-        if (shutdownPending) 
+        struct sockaddr sockaddr;
+        int sockaddrLen = sizeof(sockaddr);
+
+        for (;;)
         {
-            OS_LibShutdown();
-            return -1;
+            const struct timeval timeout = {1, 0};
+            fd_set readfds;
+
+            FD_ZERO(&readfds);
+
+#pragma warning( disable : 4127 ) 
+            FD_SET((unsigned int) hListen, &readfds);
+#pragma warning( default : 4127 ) 
+
+            if (select(0, &readfds, NULL, NULL, &timeout) == 0)
+            {
+                if (shutdownPending) 
+                {
+                    OS_LibShutdown();
+                    return -1;
+                }
+            }
+            else 
+            {
+                break;
+            }
         }
-    }
-    
-    hSock = (webServerAddrs == NULL)
-        ? accept((SOCKET) hListen, 
-                          &sockaddr, 
-                          &sockaddrLen)
-        : WSAAccept((unsigned int) hListen,                    
-                                   &sockaddr,  
-                                   &sockaddrLen,               
-                                   isAddrOK,  
-                           (DWORD) webServerAddrs);
     
+#if NO_WSAACEPT
+        hSock = accept((SOCKET) hListen, &sockaddr, &sockaddrLen);
+
+        if (hSock == INVALID_SOCKET)
+        {
+            break;
+        }
+
+        if (isAddrOK((struct sockaddr_in *) &sockaddr, webServerAddrs))
+        {
+            break;
+        }
+
+        closesocket(hSock);
+#else
+        hSock = WSAAccept((unsigned int) hListen,                    
+                          &sockaddr,  
+                          &sockaddrLen,               
+                          isAddrOKCallback,  
+                          (DWORD) webServerAddrs);
+
+        if (hSock != INVALID_SOCKET)
+        {
+            break;
+        }
+        
+        if (WSAGetLastError() != WSAECONNREFUSED)
+        {
+            break;
+        }
+#endif
+    }
 
     if (hSock == INVALID_SOCKET) 
     {
-        // Can I use FormatMessage()?
+        /* Use FormatMessage() */
         fprintf(stderr, "accept()/WSAAccept() failed: %d", WSAGetLastError());
         return -1;
     }
@@ -1740,10 +1797,9 @@ int OS_Accept(int listen_sock, int fail_on_intr, const char *webServerAddrs)
  *
  *----------------------------------------------------------------------
  */
-int OS_IpcClose(int ipcFd)
+int OS_IpcClose(int ipcFd, int shutdown)
 {
-    if (ipcFd == -1)
-        return 0;
+    if (ipcFd == -1) return 0;
 
     /*
      * Catch it if fd is a bogus value
@@ -1751,33 +1807,32 @@ int OS_IpcClose(int ipcFd)
     ASSERT((ipcFd >= 0) && (ipcFd < WIN32_OPEN_MAX));
     ASSERT(fdTable[ipcFd].type != FD_UNUSED);
 
-    switch(listenType) {
-
+    switch (listenType) 
+    {
     case FD_PIPE_SYNC:
-       /*
-        * Make sure that the client (ie. a Web Server in this case) has
-        * read all data from the pipe before we disconnect.
-        */
-       if(!FlushFileBuffers(fdTable[ipcFd].fid.fileHandle))
-           return -1;
-       if(DisconnectNamedPipe(fdTable[ipcFd].fid.fileHandle)) {
-           OS_Close(ipcFd);
-           return 0;
-       } else {
-           return -1;
-       }
-       break;
+           /*
+            * Make sure that the client (ie. a Web Server in this case) has
+            * read all data from the pipe before we disconnect.
+            */
+           if (! FlushFileBuffers(fdTable[ipcFd].fid.fileHandle)) return -1;
+
+           if (! DisconnectNamedPipe(fdTable[ipcFd].fid.fileHandle)) return -1;
+
+        /* fall through */
 
     case FD_SOCKET_SYNC:
-       OS_Close(ipcFd);
-        return 0;
-       break;
+
+           OS_Close(ipcFd, shutdown);
+           break;
 
     case FD_UNUSED:
     default:
-       exit(106);
-       break;
+
+           exit(106);
+           break;
     }
+
+    return 0; 
 }
 
 /*