Permalink
Switch branches/tags
Nothing to show
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
executable file 1888 lines (1627 sloc) 47.1 KB
/*
* 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.)
*/
#ifndef lint
static const char rcsid[] = "$Id: os_win32.c,v 1.33 2002/03/05 18:15:15 robs Exp $";
#endif /* not lint */
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <winsock2.h>
#include <stdlib.h>
#include <assert.h>
#include <stdio.h>
#include <sys/timeb.h>
#include <process.h>
#define DLLAPI __declspec(dllexport)
#include "fcgimisc.h"
#include "fcgios.h"
#define WIN32_OPEN_MAX 128 /* XXX: Small hack */
/*
* millisecs to wait for a client connection before checking the
* shutdown flag (then go back to waiting for a connection, etc).
*/
#define ACCEPT_TIMEOUT 1000
#define MUTEX_VARNAME "_FCGI_MUTEX_"
#define SHUTDOWN_EVENT_NAME "_FCGI_SHUTDOWN_EVENT_"
#define LOCALHOST "localhost"
static HANDLE hIoCompPort = INVALID_HANDLE_VALUE;
static HANDLE hStdinCompPort = INVALID_HANDLE_VALUE;
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
* supported by the FD_TABLE structure.
*
* XXX: Not all currently supported. This allows for future
* functionality.
*/
typedef enum {
FD_UNUSED,
FD_FILE_SYNC,
FD_FILE_ASYNC,
FD_SOCKET_SYNC,
FD_SOCKET_ASYNC,
FD_PIPE_SYNC,
FD_PIPE_ASYNC
} FILE_TYPE;
typedef union {
HANDLE fileHandle;
SOCKET sock;
unsigned int value;
} DESCRIPTOR;
/*
* Structure used to map file handle and socket handle
* values into values that can be used to create unix-like
* select bitmaps, read/write for both sockets/files.
*/
struct FD_TABLE {
DESCRIPTOR fid;
FILE_TYPE type;
char *path;
DWORD Errno;
unsigned long instance;
int status;
int offset; /* only valid for async file writes */
LPDWORD offsetHighPtr; /* pointers to offset high and low words */
LPDWORD offsetLowPtr; /* only valid for async file writes (logs) */
HANDLE hMapMutex; /* mutex handle for multi-proc offset update */
LPVOID ovList; /* List of associated OVERLAPPED_REQUESTs */
};
/*
* XXX Note there is no dyanmic sizing of this table, so if the
* number of open file descriptors exceeds WIN32_OPEN_MAX the
* app will blow up.
*/
static struct FD_TABLE fdTable[WIN32_OPEN_MAX];
static CRITICAL_SECTION fdTableCritical;
struct OVERLAPPED_REQUEST {
OVERLAPPED overlapped;
unsigned long instance; /* file instance (won't match after a close) */
OS_AsyncProc procPtr; /* callback routine */
ClientData clientData; /* callback argument */
ClientData clientData1; /* additional clientData */
};
typedef struct OVERLAPPED_REQUEST *POVERLAPPED_REQUEST;
static const char *bindPathPrefix = "\\\\.\\pipe\\FastCGI\\";
static FILE_TYPE listenType = FD_UNUSED;
// XXX This should be a DESCRIPTOR
static HANDLE hListen = INVALID_HANDLE_VALUE;
static BOOLEAN libInitialized = FALSE;
/*
*--------------------------------------------------------------
*
* Win32NewDescriptor --
*
* Set up for I/O descriptor masquerading.
*
* Results:
* Returns "fake id" which masquerades as a UNIX-style "small
* non-negative integer" file/socket descriptor.
* Win32_* routine below will "do the right thing" based on the
* descriptor's actual type. -1 indicates failure.
*
* Side effects:
* Entry in fdTable is reserved to represent the socket/file.
*
*--------------------------------------------------------------
*/
static int Win32NewDescriptor(FILE_TYPE type, int fd, int desiredFd)
{
int index = -1;
EnterCriticalSection(&fdTableCritical);
/*
* If desiredFd is set, try to get this entry (this is used for
* mapping stdio handles). Otherwise try to get the fd entry.
* If this is not available, find a the first empty slot. .
*/
if (desiredFd >= 0 && desiredFd < WIN32_OPEN_MAX)
{
if (fdTable[desiredFd].type == FD_UNUSED)
{
index = desiredFd;
}
}
else if (fd > 0)
{
if (fd < WIN32_OPEN_MAX && fdTable[fd].type == FD_UNUSED)
{
index = fd;
}
else
{
int i;
for (i = 1; i < WIN32_OPEN_MAX; ++i)
{
if (fdTable[i].type == FD_UNUSED)
{
index = i;
break;
}
}
}
}
if (index != -1)
{
fdTable[index].fid.value = fd;
fdTable[index].type = type;
fdTable[index].path = NULL;
fdTable[index].Errno = NO_ERROR;
fdTable[index].status = 0;
fdTable[index].offset = -1;
fdTable[index].offsetHighPtr = fdTable[index].offsetLowPtr = NULL;
fdTable[index].hMapMutex = NULL;
fdTable[index].ovList = NULL;
}
LeaveCriticalSection(&fdTableCritical);
return index;
}
/*
*--------------------------------------------------------------
*
* StdinThread--
*
* This thread performs I/O on stadard input. It is needed
* because you can't guarantee that all applications will
* create standard input with sufficient access to perform
* asynchronous I/O. Since we don't want to block the app
* reading from stdin we make it look like it's using I/O
* completion ports to perform async I/O.
*
* Results:
* Data is read from stdin and posted to the io completion
* port.
*
* Side effects:
* None.
*
*--------------------------------------------------------------
*/
static void StdinThread(void * startup)
{
int doIo = TRUE;
unsigned long fd;
unsigned long bytesRead;
POVERLAPPED_REQUEST pOv;
// Touch the arg to prevent warning
startup = NULL;
while(doIo) {
/*
* Block until a request to read from stdin comes in or a
* request to terminate the thread arrives (fd = -1).
*/
if (!GetQueuedCompletionStatus(hStdinCompPort, &bytesRead, &fd,
(LPOVERLAPPED *)&pOv, (DWORD)-1) && !pOv) {
doIo = 0;
break;
}
ASSERT((fd == STDIN_FILENO) || (fd == -1));
if(fd == -1) {
doIo = 0;
break;
}
ASSERT(pOv->clientData1 != NULL);
if(ReadFile(stdioHandles[STDIN_FILENO], pOv->clientData1, bytesRead,
&bytesRead, NULL)) {
PostQueuedCompletionStatus(hIoCompPort, bytesRead,
STDIN_FILENO, (LPOVERLAPPED)pOv);
} else {
doIo = 0;
break;
}
}
ExitThread(0);
}
void OS_ShutdownPending(void)
{
shutdownPending = TRUE;
}
static void ShutdownRequestThread(void * arg)
{
HANDLE shutdownEvent = (HANDLE) arg;
WaitForSingleObject(shutdownEvent, INFINITE);
shutdownPending = TRUE;
if (listenType == FD_PIPE_SYNC)
{
// 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.
}
/*
*--------------------------------------------------------------
*
* OS_LibInit --
*
* Set up the OS library for use.
*
* Results:
* Returns 0 if success, -1 if not.
*
* Side effects:
* Sockets initialized, pseudo file descriptors setup, etc.
*
*--------------------------------------------------------------
*/
int OS_LibInit(int stdioFds[3])
{
WORD wVersion;
WSADATA wsaData;
int err;
int fakeFd;
char *cLenPtr = NULL;
char *val = NULL;
if(libInitialized)
return 0;
InitializeCriticalSection(&fdTableCritical);
/*
* Initialize windows sockets library.
*/
wVersion = MAKEWORD(2,0);
err = WSAStartup( wVersion, &wsaData );
if (err) {
fprintf(stderr, "Error starting Windows Sockets. Error: %d",
WSAGetLastError());
exit(111);
}
/*
* Create the I/O completion port to be used for our I/O queue.
*/
if (hIoCompPort == INVALID_HANDLE_VALUE) {
hIoCompPort = CreateIoCompletionPort (INVALID_HANDLE_VALUE, NULL,
0, 1);
if(hIoCompPort == INVALID_HANDLE_VALUE) {
printf("<H2>OS_LibInit Failed CreateIoCompletionPort! ERROR: %d</H2>\r\n\r\n",
GetLastError());
return -1;
}
}
/*
* If a shutdown event is in the env, save it (I don't see any to
* remove it from the environment out from under the application).
* Spawn a thread to wait on the shutdown request.
*/
val = getenv(SHUTDOWN_EVENT_NAME);
if (val != NULL)
{
HANDLE shutdownEvent = (HANDLE) atoi(val);
if (_beginthread(ShutdownRequestThread, 0, shutdownEvent) == -1)
{
return -1;
}
}
if (acceptMutex == INVALID_HANDLE_VALUE)
{
/* 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
* valid handle to a listener object. In this case, both the
* "stdout" and "stderr" handles will be INVALID (ie. closed) by
* the starting process.
*
* The trick is determining if this is a pipe or a socket...
*
* XXX: Add the async accept test to determine socket or handle to a
* pipe!!!
*/
if((GetStdHandle(STD_OUTPUT_HANDLE) == INVALID_HANDLE_VALUE) &&
(GetStdHandle(STD_ERROR_HANDLE) == INVALID_HANDLE_VALUE) &&
(GetStdHandle(STD_INPUT_HANDLE) != INVALID_HANDLE_VALUE) )
{
DWORD pipeMode = PIPE_READMODE_BYTE | PIPE_WAIT;
HANDLE oldStdIn = GetStdHandle(STD_INPUT_HANDLE);
// Move the handle to a "low" number
if (! DuplicateHandle(GetCurrentProcess(), oldStdIn,
GetCurrentProcess(), &hListen,
0, TRUE, DUPLICATE_SAME_ACCESS))
{
return -1;
}
if (! SetStdHandle(STD_INPUT_HANDLE, hListen))
{
return -1;
}
CloseHandle(oldStdIn);
/*
* Set the pipe handle state so that it operates in wait mode.
*
* NOTE: The listenFd is not mapped to a pseudo file descriptor
* as all work done on it is contained to the OS library.
*
* XXX: Initial assumption is that SetNamedPipeHandleState will
* fail if this is an IP socket...
*/
if (SetNamedPipeHandleState(hListen, &pipeMode, NULL, NULL))
{
listenType = FD_PIPE_SYNC;
}
else
{
listenType = FD_SOCKET_SYNC;
}
}
/*
* If there are no stdioFds passed in, we're done.
*/
if(stdioFds == NULL) {
libInitialized = 1;
return 0;
}
/*
* Setup standard input asynchronous I/O. There is actually a separate
* thread spawned for this purpose. The reason for this is that some
* web servers use anonymous pipes for the connection between itself
* and a CGI application. Anonymous pipes can't perform asynchronous
* I/O or use I/O completion ports. Therefore in order to present a
* consistent I/O dispatch model to an application we emulate I/O
* completion port behavior by having the standard input thread posting
* messages to the hIoCompPort which look like a complete overlapped
* I/O structure. This keeps the event dispatching simple from the
* application perspective.
*/
stdioHandles[STDIN_FILENO] = GetStdHandle(STD_INPUT_HANDLE);
if(!SetHandleInformation(stdioHandles[STDIN_FILENO],
HANDLE_FLAG_INHERIT, 0)) {
/*
* XXX: Causes error when run from command line. Check KB
err = GetLastError();
DebugBreak();
exit(99);
*/
}
if ((fakeFd = Win32NewDescriptor(FD_PIPE_SYNC,
(int)stdioHandles[STDIN_FILENO],
STDIN_FILENO)) == -1) {
return -1;
} else {
/*
* Set stdin equal to our pseudo FD and create the I/O completion
* port to be used for async I/O.
*/
stdioFds[STDIN_FILENO] = fakeFd;
}
/*
* Create the I/O completion port to be used for communicating with
* the thread doing I/O on standard in. This port will carry read
* and possibly thread termination requests to the StdinThread.
*/
if (hStdinCompPort == INVALID_HANDLE_VALUE) {
hStdinCompPort = CreateIoCompletionPort (INVALID_HANDLE_VALUE, NULL,
0, 1);
if(hStdinCompPort == INVALID_HANDLE_VALUE) {
printf("<H2>OS_LibInit Failed CreateIoCompletionPort: STDIN! ERROR: %d</H2>\r\n\r\n",
GetLastError());
return -1;
}
}
/*
* Create the thread that will read stdin if the CONTENT_LENGTH
* is non-zero.
*/
if((cLenPtr = getenv("CONTENT_LENGTH")) != NULL &&
atoi(cLenPtr) > 0) {
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;
}
}
/*
* STDOUT will be used synchronously.
*
* XXX: May want to convert this so that it could be used for OVERLAPPED
* I/O later. If so, model it after the Stdin I/O as stdout is
* also incapable of async I/O on some servers.
*/
stdioHandles[STDOUT_FILENO] = GetStdHandle(STD_OUTPUT_HANDLE);
if(!SetHandleInformation(stdioHandles[STDOUT_FILENO],
HANDLE_FLAG_INHERIT, FALSE)) {
DebugBreak();
exit(99);
}
if ((fakeFd = Win32NewDescriptor(FD_PIPE_SYNC,
(int)stdioHandles[STDOUT_FILENO],
STDOUT_FILENO)) == -1) {
return -1;
} else {
/*
* Set stdout equal to our pseudo FD
*/
stdioFds[STDOUT_FILENO] = fakeFd;
}
stdioHandles[STDERR_FILENO] = GetStdHandle(STD_ERROR_HANDLE);
if(!SetHandleInformation(stdioHandles[STDERR_FILENO],
HANDLE_FLAG_INHERIT, FALSE)) {
DebugBreak();
exit(99);
}
if ((fakeFd = Win32NewDescriptor(FD_PIPE_SYNC,
(int)stdioHandles[STDERR_FILENO],
STDERR_FILENO)) == -1) {
return -1;
} else {
/*
* Set stderr equal to our pseudo FD
*/
stdioFds[STDERR_FILENO] = fakeFd;
}
return 0;
}
/*
*--------------------------------------------------------------
*
* OS_LibShutdown --
*
* Shutdown the OS library.
*
* Results:
* None.
*
* Side effects:
* Memory freed, handles closed.
*
*--------------------------------------------------------------
*/
void OS_LibShutdown()
{
if (hIoCompPort != INVALID_HANDLE_VALUE)
{
CloseHandle(hIoCompPort);
hIoCompPort = INVALID_HANDLE_VALUE;
}
if (hStdinCompPort != INVALID_HANDLE_VALUE)
{
CloseHandle(hStdinCompPort);
hStdinCompPort = INVALID_HANDLE_VALUE;
}
if (acceptMutex != INVALID_HANDLE_VALUE)
{
ReleaseMutex(acceptMutex);
}
DisconnectNamedPipe(hListen);
CancelIo(hListen);
WSACleanup();
}
/*
*--------------------------------------------------------------
*
* Win32FreeDescriptor --
*
* Free I/O descriptor entry in fdTable.
*
* Results:
* Frees I/O descriptor entry in fdTable.
*
* Side effects:
* None.
*
*--------------------------------------------------------------
*/
static void Win32FreeDescriptor(int fd)
{
/* Catch it if fd is a bogus value */
ASSERT((fd >= 0) && (fd < WIN32_OPEN_MAX));
EnterCriticalSection(&fdTableCritical);
if (fdTable[fd].type != FD_UNUSED)
{
switch (fdTable[fd].type)
{
case FD_FILE_SYNC:
case FD_FILE_ASYNC:
/* Free file path string */
ASSERT(fdTable[fd].path != NULL);
free(fdTable[fd].path);
fdTable[fd].path = NULL;
break;
default:
break;
}
ASSERT(fdTable[fd].path == NULL);
fdTable[fd].type = FD_UNUSED;
fdTable[fd].path = NULL;
fdTable[fd].Errno = NO_ERROR;
fdTable[fd].offsetHighPtr = fdTable[fd].offsetLowPtr = NULL;
if (fdTable[fd].hMapMutex != NULL)
{
CloseHandle(fdTable[fd].hMapMutex);
fdTable[fd].hMapMutex = NULL;
}
}
LeaveCriticalSection(&fdTableCritical);
return;
}
static short getPort(const char * bindPath)
{
short port = 0;
char * p = strchr(bindPath, ':');
if (p && *++p)
{
char buf[6];
strncpy(buf, p, 6);
buf[5] = '\0';
port = (short) atoi(buf);
}
return port;
}
/*
* OS_CreateLocalIpcFd --
*
* This procedure is responsible for creating the listener pipe
* on Windows NT for local process communication. It will create a
* named pipe and return a file descriptor to it to the caller.
*
* Results:
* Listener pipe created. This call returns either a valid
* pseudo file descriptor or -1 on error.
*
* Side effects:
* Listener pipe and IPC address are stored in the FCGI info
* structure.
* 'errno' will set on errors (-1 is returned).
*
*----------------------------------------------------------------------
*/
int OS_CreateLocalIpcFd(const char *bindPath, int backlog)
{
int pseudoFd = -1;
short port = getPort(bindPath);
if (acceptMutex == INVALID_HANDLE_VALUE)
{
acceptMutex = CreateMutex(NULL, FALSE, NULL);
if (acceptMutex == NULL) return -2;
if (! SetHandleInformation(acceptMutex, HANDLE_FLAG_INHERIT, TRUE)) return -3;
}
// There's nothing to be gained (at the moment) by a shutdown Event
if (port && *bindPath != ':' && strncmp(bindPath, LOCALHOST, strlen(LOCALHOST)))
{
fprintf(stderr, "To start a service on a TCP port can not "
"specify a host name.\n"
"You should either use \"localhost:<port>\" or "
" just use \":<port>.\"\n");
exit(1);
}
listenType = (port) ? FD_SOCKET_SYNC : FD_PIPE_ASYNC;
if (port)
{
SOCKET listenSock;
struct sockaddr_in sockAddr;
int sockLen = sizeof(sockAddr);
memset(&sockAddr, 0, sizeof(sockAddr));
sockAddr.sin_family = AF_INET;
sockAddr.sin_addr.s_addr = htonl(INADDR_ANY);
sockAddr.sin_port = htons(port);
listenSock = socket(AF_INET, SOCK_STREAM, 0);
if (listenSock == INVALID_SOCKET)
{
return -4;
}
if (bind(listenSock, (struct sockaddr *) &sockAddr, sockLen) )
{
return -12;
}
if (listen(listenSock, backlog))
{
return -5;
}
pseudoFd = Win32NewDescriptor(listenType, listenSock, -1);
if (pseudoFd == -1)
{
closesocket(listenSock);
return -6;
}
hListen = (HANDLE) listenSock;
}
else
{
HANDLE hListenPipe = INVALID_HANDLE_VALUE;
char *pipePath = malloc(strlen(bindPathPrefix) + strlen(bindPath) + 1);
if (! pipePath)
{
return -7;
}
strcpy(pipePath, bindPathPrefix);
strcat(pipePath, bindPath);
hListenPipe = CreateNamedPipe(pipePath,
PIPE_ACCESS_DUPLEX,
PIPE_TYPE_BYTE | PIPE_WAIT | PIPE_READMODE_BYTE,
PIPE_UNLIMITED_INSTANCES,
4096, 4096, 0, NULL);
free(pipePath);
if (hListenPipe == INVALID_HANDLE_VALUE)
{
return -8;
}
if (! SetHandleInformation(hListenPipe, HANDLE_FLAG_INHERIT, TRUE))
{
return -9;
}
pseudoFd = Win32NewDescriptor(listenType, (int) hListenPipe, -1);
if (pseudoFd == -1)
{
CloseHandle(hListenPipe);
return -10;
}
hListen = (HANDLE) hListenPipe;
}
return pseudoFd;
}
/*
*----------------------------------------------------------------------
*
* OS_FcgiConnect --
*
* Create the pipe pathname connect to the remote application if
* possible.
*
* Results:
* -1 if fail or a valid handle if connection succeeds.
*
* Side effects:
* Remote connection established.
*
*----------------------------------------------------------------------
*/
int OS_FcgiConnect(char *bindPath)
{
short port = getPort(bindPath);
int pseudoFd = -1;
if (port)
{
struct hostent *hp;
char *host = NULL;
struct sockaddr_in sockAddr;
int sockLen = sizeof(sockAddr);
SOCKET sock;
if (*bindPath != ':')
{
char * p = strchr(bindPath, ':');
int len = p - bindPath + 1;
host = malloc(len);
strncpy(host, bindPath, len);
host[len] = '\0';
}
hp = gethostbyname(host ? host : LOCALHOST);
if (host)
{
free(host);
}
if (hp == NULL)
{
fprintf(stderr, "Unknown host: %s\n", bindPath);
return -1;
}
memset(&sockAddr, 0, sizeof(sockAddr));
sockAddr.sin_family = AF_INET;
memcpy(&sockAddr.sin_addr, hp->h_addr, hp->h_length);
sockAddr.sin_port = htons(port);
sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock == INVALID_SOCKET)
{
return -1;
}
if (! connect(sock, (struct sockaddr *) &sockAddr, sockLen))
{
closesocket(sock);
return -1;
}
pseudoFd = Win32NewDescriptor(FD_SOCKET_SYNC, sock, -1);
if (pseudoFd == -1)
{
closesocket(sock);
return -1;
}
}
else
{
char *pipePath = malloc(strlen(bindPathPrefix) + strlen(bindPath) + 1);
HANDLE hPipe;
if (! pipePath)
{
return -1;
}
strcpy(pipePath, bindPathPrefix);
strcat(pipePath, bindPath);
hPipe = CreateFile(pipePath,
GENERIC_WRITE | GENERIC_READ,
FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL,
OPEN_EXISTING,
FILE_FLAG_OVERLAPPED,
NULL);
free(pipePath);
if( hPipe == INVALID_HANDLE_VALUE)
{
return -1;
}
pseudoFd = Win32NewDescriptor(FD_PIPE_ASYNC, (int) hPipe, -1);
if (pseudoFd == -1)
{
CloseHandle(hPipe);
return -1;
}
/*
* Set stdin equal to our pseudo FD and create the I/O completion
* port to be used for async I/O.
*/
if (! CreateIoCompletionPort(hPipe, hIoCompPort, pseudoFd, 1))
{
Win32FreeDescriptor(pseudoFd);
CloseHandle(hPipe);
return -1;
}
}
return pseudoFd;
}
/*
*--------------------------------------------------------------
*
* OS_Read --
*
* Pass through to the appropriate NT read function.
*
* Results:
* Returns number of byes read. Mimics unix read:.
* n bytes read, 0 or -1 failure: errno contains actual error
*
* Side effects:
* None.
*
*--------------------------------------------------------------
*/
int OS_Read(int fd, char * buf, size_t len)
{
DWORD bytesRead;
int ret = -1;
ASSERT((fd >= 0) && (fd < WIN32_OPEN_MAX));
if (shutdownNow) return -1;
switch (fdTable[fd].type)
{
case FD_FILE_SYNC:
case FD_FILE_ASYNC:
case FD_PIPE_SYNC:
case FD_PIPE_ASYNC:
if (ReadFile(fdTable[fd].fid.fileHandle, buf, len, &bytesRead, NULL))
{
ret = bytesRead;
}
else
{
fdTable[fd].Errno = GetLastError();
}
break;
case FD_SOCKET_SYNC:
case FD_SOCKET_ASYNC:
ret = recv(fdTable[fd].fid.sock, buf, len, 0);
if (ret == SOCKET_ERROR)
{
fdTable[fd].Errno = WSAGetLastError();
ret = -1;
}
break;
default:
ASSERT(0);
}
return ret;
}
/*
*--------------------------------------------------------------
*
* OS_Write --
*
* Perform a synchronous OS write.
*
* Results:
* Returns number of bytes written. Mimics unix write:
* n bytes written, 0 or -1 failure (??? couldn't find man page).
*
* Side effects:
* none.
*
*--------------------------------------------------------------
*/
int OS_Write(int fd, char * buf, size_t len)
{
DWORD bytesWritten;
int ret = -1;
ASSERT(fd >= 0 && fd < WIN32_OPEN_MAX);
if (shutdownNow) return -1;
switch (fdTable[fd].type)
{
case FD_FILE_SYNC:
case FD_FILE_ASYNC:
case FD_PIPE_SYNC:
case FD_PIPE_ASYNC:
if (WriteFile(fdTable[fd].fid.fileHandle, buf, len, &bytesWritten, NULL))
{
ret = bytesWritten;
}
else
{
fdTable[fd].Errno = GetLastError();
}
break;
case FD_SOCKET_SYNC:
case FD_SOCKET_ASYNC:
ret = send(fdTable[fd].fid.sock, buf, len, 0);
if (ret == SOCKET_ERROR)
{
fdTable[fd].Errno = WSAGetLastError();
ret = -1;
}
break;
default:
ASSERT(0);
}
return ret;
}
/*
*----------------------------------------------------------------------
*
* OS_SpawnChild --
*
* Spawns a new server listener process, and stores the information
* relating to the child in the supplied record. A wait handler is
* registered on the child's completion. This involves creating
* a process on NT and preparing a command line with the required
* state (currently a -childproc flag and the server socket to use
* for accepting connections).
*
* Results:
* 0 if success, -1 if error.
*
* Side effects:
* Child process spawned.
*
*----------------------------------------------------------------------
*/
int OS_SpawnChild(char *execPath, int listenFd)
{
STARTUPINFO StartupInfo;
PROCESS_INFORMATION pInfo;
BOOL success;
memset((void *)&StartupInfo, 0, sizeof(STARTUPINFO));
StartupInfo.cb = sizeof (STARTUPINFO);
StartupInfo.lpReserved = NULL;
StartupInfo.lpReserved2 = NULL;
StartupInfo.cbReserved2 = 0;
StartupInfo.lpDesktop = NULL;
/*
* FastCGI on NT will set the listener pipe HANDLE in the stdin of
* the new process. The fact that there is a stdin and NULL handles
* for stdout and stderr tells the FastCGI process that this is a
* FastCGI process and not a CGI process.
*/
StartupInfo.dwFlags = STARTF_USESTDHANDLES;
/*
* XXX: Do I have to dup the handle before spawning the process or is
* it sufficient to use the handle as it's reference counted
* by NT anyway?
*/
StartupInfo.hStdInput = fdTable[listenFd].fid.fileHandle;
StartupInfo.hStdOutput = INVALID_HANDLE_VALUE;
StartupInfo.hStdError = INVALID_HANDLE_VALUE;
/*
* Make the listener socket inheritable.
*/
success = SetHandleInformation(StartupInfo.hStdInput, HANDLE_FLAG_INHERIT,
TRUE);
if(!success) {
exit(99);
}
/*
* XXX: Might want to apply some specific security attributes to the
* processes.
*/
success = CreateProcess(execPath, /* LPCSTR address of module name */
NULL, /* LPCSTR address of command line */
NULL, /* Process security attributes */
NULL, /* Thread security attributes */
TRUE, /* Inheritable Handes inherited. */
0, /* DWORD creation flags */
NULL, /* Use parent environment block */
NULL, /* Address of current directory name */
&StartupInfo, /* Address of STARTUPINFO */
&pInfo); /* Address of PROCESS_INFORMATION */
if(success) {
return 0;
} else {
return -1;
}
}
/*
*--------------------------------------------------------------
*
* OS_AsyncReadStdin --
*
* This initiates an asynchronous read on the standard
* input handle. This handle is not guaranteed to be
* capable of performing asynchronous I/O so we send a
* message to the StdinThread to do the synchronous read.
*
* Results:
* -1 if error, 0 otherwise.
*
* Side effects:
* Asynchronous message is queued to the StdinThread and an
* overlapped structure is allocated/initialized.
*
*--------------------------------------------------------------
*/
int OS_AsyncReadStdin(void *buf, int len, OS_AsyncProc procPtr,
ClientData clientData)
{
POVERLAPPED_REQUEST pOv;
ASSERT(fdTable[STDIN_FILENO].type != FD_UNUSED);
pOv = (POVERLAPPED_REQUEST)malloc(sizeof(struct OVERLAPPED_REQUEST));
ASSERT(pOv);
memset((void *)pOv, 0, sizeof(struct OVERLAPPED_REQUEST));
pOv->clientData1 = (ClientData)buf;
pOv->instance = fdTable[STDIN_FILENO].instance;
pOv->procPtr = procPtr;
pOv->clientData = clientData;
PostQueuedCompletionStatus(hStdinCompPort, len, STDIN_FILENO,
(LPOVERLAPPED)pOv);
return 0;
}
/*
*--------------------------------------------------------------
*
* OS_AsyncRead --
*
* This initiates an asynchronous read on the file
* handle which may be a socket or named pipe.
*
* We also must save the ProcPtr and ClientData, so later
* when the io completes, we know who to call.
*
* We don't look at any results here (the ReadFile may
* return data if it is cached) but do all completion
* processing in OS_Select when we get the io completion
* port done notifications. Then we call the callback.
*
* Results:
* -1 if error, 0 otherwise.
*
* Side effects:
* Asynchronous I/O operation is queued for completion.
*
*--------------------------------------------------------------
*/
int OS_AsyncRead(int fd, int offset, void *buf, int len,
OS_AsyncProc procPtr, ClientData clientData)
{
DWORD bytesRead;
POVERLAPPED_REQUEST pOv;
/*
* Catch any bogus fd values
*/
ASSERT((fd >= 0) && (fd < WIN32_OPEN_MAX));
/*
* Confirm that this is an async fd
*/
ASSERT(fdTable[fd].type != FD_UNUSED);
ASSERT(fdTable[fd].type != FD_FILE_SYNC);
ASSERT(fdTable[fd].type != FD_PIPE_SYNC);
ASSERT(fdTable[fd].type != FD_SOCKET_SYNC);
pOv = (POVERLAPPED_REQUEST)malloc(sizeof(struct OVERLAPPED_REQUEST));
ASSERT(pOv);
memset((void *)pOv, 0, sizeof(struct OVERLAPPED_REQUEST));
/*
* Only file offsets should be non-zero, but make sure.
*/
if (fdTable[fd].type == FD_FILE_ASYNC)
if (fdTable[fd].offset >= 0)
pOv->overlapped.Offset = fdTable[fd].offset;
else
pOv->overlapped.Offset = offset;
pOv->instance = fdTable[fd].instance;
pOv->procPtr = procPtr;
pOv->clientData = clientData;
bytesRead = fd;
/*
* ReadFile returns: TRUE success, FALSE failure
*/
if (!ReadFile(fdTable[fd].fid.fileHandle, buf, len, &bytesRead,
(LPOVERLAPPED)pOv)) {
fdTable[fd].Errno = GetLastError();
if(fdTable[fd].Errno == ERROR_NO_DATA ||
fdTable[fd].Errno == ERROR_PIPE_NOT_CONNECTED) {
PostQueuedCompletionStatus(hIoCompPort, 0, fd, (LPOVERLAPPED)pOv);
return 0;
}
if(fdTable[fd].Errno != ERROR_IO_PENDING) {
PostQueuedCompletionStatus(hIoCompPort, 0, fd, (LPOVERLAPPED)pOv);
return -1;
}
fdTable[fd].Errno = 0;
}
return 0;
}
/*
*--------------------------------------------------------------
*
* OS_AsyncWrite --
*
* This initiates an asynchronous write on the "fake" file
* descriptor (which may be a file, socket, or named pipe).
* We also must save the ProcPtr and ClientData, so later
* when the io completes, we know who to call.
*
* We don't look at any results here (the WriteFile generally
* completes immediately) but do all completion processing
* in OS_DoIo when we get the io completion port done
* notifications. Then we call the callback.
*
* Results:
* -1 if error, 0 otherwise.
*
* Side effects:
* Asynchronous I/O operation is queued for completion.
*
*--------------------------------------------------------------
*/
int OS_AsyncWrite(int fd, int offset, void *buf, int len,
OS_AsyncProc procPtr, ClientData clientData)
{
DWORD bytesWritten;
POVERLAPPED_REQUEST pOv;
/*
* Catch any bogus fd values
*/
ASSERT((fd >= 0) && (fd < WIN32_OPEN_MAX));
/*
* Confirm that this is an async fd
*/
ASSERT(fdTable[fd].type != FD_UNUSED);
ASSERT(fdTable[fd].type != FD_FILE_SYNC);
ASSERT(fdTable[fd].type != FD_PIPE_SYNC);
ASSERT(fdTable[fd].type != FD_SOCKET_SYNC);
pOv = (POVERLAPPED_REQUEST)malloc(sizeof(struct OVERLAPPED_REQUEST));
ASSERT(pOv);
memset((void *)pOv, 0, sizeof(struct OVERLAPPED_REQUEST));
/*
* Only file offsets should be non-zero, but make sure.
*/
if (fdTable[fd].type == FD_FILE_ASYNC)
/*
* Only file opened via OS_AsyncWrite with
* O_APPEND will have an offset != -1.
*/
if (fdTable[fd].offset >= 0)
/*
* If the descriptor has a memory mapped file
* handle, take the offsets from there.
*/
if (fdTable[fd].hMapMutex != NULL) {
/*
* Wait infinitely; this *should* not cause problems.
*/
WaitForSingleObject(fdTable[fd].hMapMutex, INFINITE);
/*
* Retrieve the shared offset values.
*/
pOv->overlapped.OffsetHigh = *(fdTable[fd].offsetHighPtr);
pOv->overlapped.Offset = *(fdTable[fd].offsetLowPtr);
/*
* Update the shared offset values for the next write
*/
*(fdTable[fd].offsetHighPtr) += 0; /* XXX How do I handle overflow */
*(fdTable[fd].offsetLowPtr) += len;
ReleaseMutex(fdTable[fd].hMapMutex);
} else
pOv->overlapped.Offset = fdTable[fd].offset;
else
pOv->overlapped.Offset = offset;
pOv->instance = fdTable[fd].instance;
pOv->procPtr = procPtr;
pOv->clientData = clientData;
bytesWritten = fd;
/*
* WriteFile returns: TRUE success, FALSE failure
*/
if (!WriteFile(fdTable[fd].fid.fileHandle, buf, len, &bytesWritten,
(LPOVERLAPPED)pOv)) {
fdTable[fd].Errno = GetLastError();
if(fdTable[fd].Errno != ERROR_IO_PENDING) {
PostQueuedCompletionStatus(hIoCompPort, 0, fd, (LPOVERLAPPED)pOv);
return -1;
}
fdTable[fd].Errno = 0;
}
if (fdTable[fd].offset >= 0)
fdTable[fd].offset += len;
return 0;
}
/*
*--------------------------------------------------------------
*
* OS_Close --
*
* Closes the descriptor with routine appropriate for
* descriptor's type.
*
* Results:
* Socket or file is closed. Return values mimic Unix close:
* 0 success, -1 failure
*
* Side effects:
* Entry in fdTable is marked as free.
*
*--------------------------------------------------------------
*/
int OS_Close(int fd)
{
int ret = 0;
/*
* Catch it if fd is a bogus value
*/
ASSERT((fd >= 0) && (fd < WIN32_OPEN_MAX));
ASSERT(fdTable[fd].type != FD_UNUSED);
switch (fdTable[fd].type) {
case FD_PIPE_SYNC:
case FD_PIPE_ASYNC:
case FD_FILE_SYNC:
case FD_FILE_ASYNC:
break;
case FD_SOCKET_SYNC:
case FD_SOCKET_ASYNC:
/*
* 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(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
{
FD_SET(sock, &rfds);
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:
ret = -1; /* fake failure */
}
Win32FreeDescriptor(fd);
return ret;
}
/*
*--------------------------------------------------------------
*
* OS_CloseRead --
*
* Cancel outstanding asynchronous reads and prevent subsequent
* reads from completing.
*
* Results:
* Socket or file is shutdown. Return values mimic Unix shutdown:
* 0 success, -1 failure
*
*--------------------------------------------------------------
*/
int OS_CloseRead(int fd)
{
int ret = 0;
/*
* Catch it if fd is a bogus value
*/
ASSERT((fd >= 0) && (fd < WIN32_OPEN_MAX));
ASSERT(fdTable[fd].type == FD_SOCKET_ASYNC
|| fdTable[fd].type == FD_SOCKET_SYNC);
if (shutdown(fdTable[fd].fid.sock,0) == SOCKET_ERROR)
ret = -1;
return ret;
}
/*
*--------------------------------------------------------------
*
* OS_DoIo --
*
* This function was formerly OS_Select. It's purpose is
* to pull I/O completion events off the queue and dispatch
* them to the appropriate place.
*
* Results:
* Returns 0.
*
* Side effects:
* Handlers are called.
*
*--------------------------------------------------------------
*/
int OS_DoIo(struct timeval *tmo)
{
unsigned long fd;
unsigned long bytes;
POVERLAPPED_REQUEST pOv;
struct timeb tb;
int ms;
int ms_last;
int err;
/* XXX
* We can loop in here, but not too long, as wait handlers
* must run.
* For cgi stdin, apparently select returns when io completion
* ports don't, so don't wait the full timeout.
*/
if(tmo)
ms = (tmo->tv_sec*1000 + tmo->tv_usec/1000) / 2;
else
ms = 1000;
ftime(&tb);
ms_last = tb.time*1000 + tb.millitm;
while (ms >= 0) {
if(tmo && (ms = tmo->tv_sec*1000 + tmo->tv_usec/1000)> 100)
ms = 100;
if (!GetQueuedCompletionStatus(hIoCompPort, &bytes, &fd,
(LPOVERLAPPED *)&pOv, ms) && !pOv) {
err = WSAGetLastError();
return 0; /* timeout */
}
ASSERT((fd >= 0) && (fd < WIN32_OPEN_MAX));
/* call callback if descriptor still valid */
ASSERT(pOv);
if(pOv->instance == fdTable[fd].instance)
(*pOv->procPtr)(pOv->clientData, bytes);
free(pOv);
ftime(&tb);
ms -= (tb.time*1000 + tb.millitm - ms_last);
ms_last = tb.time*1000 + tb.millitm;
}
return 0;
}
static int isAddrOK(struct sockaddr_in * inet_sockaddr, const char * okAddrs)
{
static const char *token = " ,;:\t";
char *ipaddr;
char *p;
if (okAddrs == NULL) return TRUE;
ipaddr = inet_ntoa(inet_sockaddr->sin_addr);
p = strstr(okAddrs, ipaddr);
if (p == NULL) return FALSE;
if (p == okAddrs)
{
p += strlen(ipaddr);
return (strchr(token, *p) != NULL);
}
if (strchr(token, *--p) != NULL)
{
p += strlen(ipaddr) + 1;
return (strchr(token, *p) != NULL);
}
return FALSE;
}
#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;
FormatMessage(
FORMAT_MESSAGE_ALLOCATE_BUFFER |
FORMAT_MESSAGE_FROM_SYSTEM |
FORMAT_MESSAGE_IGNORE_INSERTS,
NULL,
GetLastError(),
0,
(LPTSTR) &buf,
0,
NULL
);
fprintf(stderr, "%s: %s\n", text, (LPCTSTR) buf);
LocalFree(buf);
}
static int acceptNamedPipe()
{
int ipcFd = -1;
if (! ConnectNamedPipe(hListen, NULL))
{
switch (GetLastError())
{
case ERROR_PIPE_CONNECTED:
// A client connected after CreateNamedPipe but
// before ConnectNamedPipe. Its a good connection.
break;
case ERROR_IO_PENDING:
// 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:
// The pipe handle is in nonblocking mode.
case ERROR_NO_DATA:
// The previous client closed its handle (and we failed
// to call DisconnectNamedPipe)
default:
printLastError("unexpected ConnectNamedPipe() error");
}
}
ipcFd = Win32NewDescriptor(FD_PIPE_SYNC, (int) hListen, -1);
if (ipcFd == -1)
{
DisconnectNamedPipe(hListen);
}
return ipcFd;
}
static int acceptSocket(const char *webServerAddrs)
{
SOCKET hSock;
int ipcFd = -1;
for (;;)
{
struct sockaddr sockaddr;
int sockaddrLen = sizeof(sockaddr);
for (;;)
{
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;
}
}
#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)
{
/* Use FormatMessage() */
fprintf(stderr, "accept()/WSAAccept() failed: %d", WSAGetLastError());
return -1;
}
ipcFd = Win32NewDescriptor(FD_SOCKET_SYNC, hSock, -1);
if (ipcFd == -1)
{
closesocket(hSock);
}
return ipcFd;
}
/*
*----------------------------------------------------------------------
*
* OS_Accept --
*
* Accepts a new FastCGI connection. This routine knows whether
* we're dealing with TCP based sockets or NT Named Pipes for IPC.
*
* fail_on_intr is ignored in the Win lib.
*
* Results:
* -1 if the operation fails, otherwise this is a valid IPC fd.
*
*----------------------------------------------------------------------
*/
int OS_Accept(int listen_sock, int fail_on_intr, const char *webServerAddrs)
{
int ipcFd = -1;
// Touch args to prevent warnings
listen_sock = 0; fail_on_intr = 0;
// @todo Muliple listen sockets and sockets other than 0 are not
// supported due to the use of globals.
if (shutdownPending)
{
OS_LibShutdown();
return -1;
}
// The mutex is to keep other processes (and threads, when supported)
// from going into the accept cycle. The accept cycle needs to
// periodically break out to check the state of the shutdown flag
// and there's no point to having more than one thread do that.
if (acceptMutex != INVALID_HANDLE_VALUE)
{
if (WaitForSingleObject(acceptMutex, INFINITE) == WAIT_FAILED)
{
printLastError("WaitForSingleObject() failed");
return -1;
}
}
if (shutdownPending)
{
OS_LibShutdown();
}
else if (listenType == FD_PIPE_SYNC)
{
ipcFd = acceptNamedPipe();
}
else if (listenType == FD_SOCKET_SYNC)
{
ipcFd = acceptSocket(webServerAddrs);
}
else
{
fprintf(stderr, "unknown listenType (%d)\n", listenType);
}
if (acceptMutex != INVALID_HANDLE_VALUE)
{
ReleaseMutex(acceptMutex);
}
return ipcFd;
}
/*
*----------------------------------------------------------------------
*
* OS_IpcClose
*
* OS IPC routine to close an IPC connection.
*
* Results:
*
*
* Side effects:
* IPC connection is closed.
*
*----------------------------------------------------------------------
*/
int OS_IpcClose(int ipcFd)
{
if (ipcFd == -1) return 0;
/*
* Catch it if fd is a bogus value
*/
ASSERT((ipcFd >= 0) && (ipcFd < WIN32_OPEN_MAX));
ASSERT(fdTable[ipcFd].type != FD_UNUSED);
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)) return -1;
/* fall through */
case FD_SOCKET_SYNC:
OS_Close(ipcFd);
break;
case FD_UNUSED:
default:
exit(106);
break;
}
return 0;
}
/*
*----------------------------------------------------------------------
*
* OS_IsFcgi --
*
* Determines whether this process is a FastCGI process or not.
*
* Results:
* Returns 1 if FastCGI, 0 if not.
*
* Side effects:
* None.
*
*----------------------------------------------------------------------
*/
int OS_IsFcgi(int sock)
{
// Touch args to prevent warnings
sock = 0;
/* XXX This is broken for sock */
return (listenType != FD_UNUSED);
}
/*
*----------------------------------------------------------------------
*
* OS_SetFlags --
*
* Sets selected flag bits in an open file descriptor. Currently
* this is only to put a SOCKET into non-blocking mode.
*
*----------------------------------------------------------------------
*/
void OS_SetFlags(int fd, int flags)
{
unsigned long pLong = 1L;
int err;
if (fdTable[fd].type == FD_SOCKET_SYNC && flags == O_NONBLOCK) {
if (ioctlsocket(fdTable[fd].fid.sock, FIONBIO, &pLong) ==
SOCKET_ERROR) {
exit(WSAGetLastError());
}
if (!CreateIoCompletionPort((HANDLE)fdTable[fd].fid.sock,
hIoCompPort, fd, 1)) {
err = GetLastError();
exit(err);
}
fdTable[fd].type = FD_SOCKET_ASYNC;
}
return;
}