-/*
+/*
* os_unix.c --
*
* Description of file.
* 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.
+ * 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
*/
#ifndef lint
-static const char rcsid[] = "$Id: os_unix.c,v 1.1 1997/09/16 15:36:33 stanleyg Exp $";
+static const char rcsid[] = "$Id: os_unix.c,v 1.12 1999/08/05 21:25:55 roberts Exp $";
#endif /* not lint */
-#include "fcgimisc.h"
-#include "fcgiapp.h"
-#include "fcgiappmisc.h"
-#include "fastcgi.h"
+#include "fcgi_config.h"
-#include <stdio.h>
-#ifdef HAVE_UNISTD_H
-#include <unistd.h>
-#endif
+#include <arpa/inet.h>
#include <assert.h>
-#include <stdlib.h>
-#include <string.h>
-#include <memory.h> /* for memchr() */
#include <errno.h>
-#include <stdarg.h>
+#include <fcntl.h> /* for fcntl */
#include <math.h>
-#ifdef HAVE_SYS_SOCKET_H
-#include <sys/socket.h> /* for getpeername */
-#endif
+#include <memory.h> /* for memchr() */
+#include <netinet/tcp.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/time.h>
+#include <sys/types.h>
#include <sys/un.h>
-#include <fcntl.h> /* for fcntl */
+
#ifdef HAVE_NETDB_H
#include <netdb.h>
#endif
-#include <sys/time.h>
-#include <sys/types.h>
#ifdef HAVE_NETINET_IN_H
#include <netinet/in.h>
#endif
-#include <arpa/inet.h>
+#ifdef HAVE_SYS_SOCKET_H
+#include <sys/socket.h> /* for getpeername */
+#endif
+
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+
+#include "fastcgi.h"
+#include "fcgiapp.h"
+#include "fcgiappmisc.h"
+#include "fcgimisc.h"
#include "fcgios.h"
-#ifndef _CLIENTDATA
-# if defined(__STDC__) || defined(__cplusplus)
- typedef void *ClientData;
-# else
- typedef int *ClientData;
-# endif /* __STDC__ */
-#define _CLIENTDATA
+#ifndef FALSE
+#define FALSE 0
+#endif
+
+#ifndef TRUE
+#define TRUE 1
#endif
/*
static int asyncIoTableSize = 16;
static AioInfo *asyncIoTable = NULL;
-#define STDIN_FILENO 0
-#define STDOUT_FILENO 1
-#define STDERR_FILENO 2
-#ifndef FALSE
-#define FALSE 0
-#endif
-
-#ifndef TRUE
-#define TRUE 1
-#endif
-
-static int isFastCGI = FALSE;
static int libInitialized = FALSE;
static fd_set readFdSet;
static int numWrPosted = 0;
static int volatile maxFd = -1;
-/*
- * fcgiSocket will hold the socket file descriptor if the call to
- * accept below results in a connection being accepted. This socket
- * will be used by FCGX_Accept and then set back to -1.
- */
-static int fcgiSocket = -1;
-union u_sockaddr {
- struct sockaddr_un un;
- struct sockaddr_in in;
-} static fcgiSa;
-static int fcgiClilen;
\f
/*
*--------------------------------------------------------------
{
if(libInitialized)
return 0;
-
- asyncIoTable = malloc(asyncIoTableSize * sizeof(AioInfo));
+
+ asyncIoTable = (AioInfo *)malloc(asyncIoTableSize * sizeof(AioInfo));
if(asyncIoTable == NULL) {
errno = ENOMEM;
return -1;
{
if(!libInitialized)
return;
-
+
free(asyncIoTable);
asyncIoTable = NULL;
libInitialized = FALSE;
*----------------------------------------------------------------------
*/
-static int OS_BuildSockAddrUn(char *bindPath,
+static int OS_BuildSockAddrUn(const char *bindPath,
struct sockaddr_un *servAddrPtr,
int *servAddrLen)
{
*
*----------------------------------------------------------------------
*/
-int OS_CreateLocalIpcFd(char *bindPath)
+int OS_CreateLocalIpcFd(const char *bindPath, int backlog)
{
int listenSock, servLen;
union SockAddrUnion sa;
}
}
if(bind(listenSock, (struct sockaddr *) &sa.unixVariant, servLen) < 0
- || listen(listenSock, 5) < 0) {
+ || listen(listenSock, backlog) < 0) {
perror("bind/listen");
exit(errno);
}
exit(1000);
}
sa.inetVariant.sin_family = AF_INET;
- memcpy((caddr_t)&sa.inetVariant.sin_addr, hp->h_addr, hp->h_length);
+ memcpy(&sa.inetVariant.sin_addr, hp->h_addr, hp->h_length);
sa.inetVariant.sin_port = htons(port);
servLen = sizeof(sa.inetVariant);
resultSock = socket(AF_INET, SOCK_STREAM, 0);
return -1;
}
}
-
+
\f
/*
*--------------------------------------------------------------
*
*--------------------------------------------------------------
*/
-int OS_AsyncReadStdin(void *buf, int len, OS_AsyncProc procPtr,
+int OS_AsyncReadStdin(void *buf, int len, OS_AsyncProc procPtr,
ClientData clientData)
{
int index = AIO_RD_IX(STDIN_FILENO);
static void GrowAsyncTable(void)
{
int oldTableSize = asyncIoTableSize;
-
+
asyncIoTableSize = asyncIoTableSize * 2;
- asyncIoTable = realloc(asyncIoTable, asyncIoTableSize * sizeof(AioInfo));
+ asyncIoTable = (AioInfo *)realloc(asyncIoTable, asyncIoTableSize * sizeof(AioInfo));
if(asyncIoTable == NULL) {
errno = ENOMEM;
exit(errno);
OS_AsyncProc procPtr, ClientData clientData)
{
int index = AIO_RD_IX(fd);
-
+
ASSERT(asyncIoTable != NULL);
if(fd > maxFd)
*
*--------------------------------------------------------------
*/
-int OS_AsyncWrite(int fd, int offset, void *buf, int len,
+int OS_AsyncWrite(int fd, int offset, void *buf, int len,
OS_AsyncProc procPtr, ClientData clientData)
{
int index = AIO_WR_IX(fd);
int OS_Close(int fd)
{
int index = AIO_RD_IX(fd);
-
+
FD_CLR(fd, &readFdSet);
FD_CLR(fd, &readFdSetPost);
if(asyncIoTable[index].inUse != 0) {
asyncIoTable[index].inUse = 0;
}
-
+
FD_CLR(fd, &writeFdSet);
FD_CLR(fd, &writeFdSetPost);
index = AIO_WR_IX(fd);
asyncIoTable[AIO_RD_IX(fd)].inUse = 0;
FD_CLR(fd, &readFdSet);
}
-
+
return shutdown(fd, 0);
}
FD_SET(fd, &writeFdSetCpy);
}
}
-
+
/*
* If there were no completed events from a prior call, see if there's
* any work to do.
if(numRdPosted == 0 && numWrPosted == 0)
return 0;
-
+
for(fd = 0; fd <= maxFd; fd++) {
/*
* Do reads and dispatch callback.
*/
- if(FD_ISSET(fd, &readFdSetPost)
+ if(FD_ISSET(fd, &readFdSetPost)
&& asyncIoTable[AIO_RD_IX(fd)].inUse) {
numRdPosted--;
FD_CLR(fd, &readFdSetPost);
aioPtr = &asyncIoTable[AIO_RD_IX(fd)];
-
+
len = read(aioPtr->fd, aioPtr->buf, aioPtr->len);
procPtr = aioPtr->procPtr;
numWrPosted--;
FD_CLR(fd, &writeFdSetPost);
aioPtr = &asyncIoTable[AIO_WR_IX(fd)];
-
+
len = write(aioPtr->fd, aioPtr->buf, aioPtr->len);
procPtr = aioPtr->procPtr;
*
*----------------------------------------------------------------------
*/
-static int ClientAddrOK(struct sockaddr_in *saPtr, char *clientList)
+static int ClientAddrOK(struct sockaddr_in *saPtr, const char *clientList)
{
int result = FALSE;
char *clientListCopy, *cur, *next;
}
strLen = strlen(clientList);
- clientListCopy = malloc(strLen + 1);
+ clientListCopy = (char *)malloc(strLen + 1);
assert(newString != NULL);
memcpy(newString, clientList, strLen);
newString[strLen] = '\000';
-
+
for(cur = clientListCopy; cur != NULL; cur = next) {
next = strchr(cur, ',');
if(next != NULL) {
*
*----------------------------------------------------------------------
*/
-static int AcquireLock(void)
+static int AcquireLock(int sock, int fail_on_intr)
{
#ifdef USE_LOCKING
- struct flock lock;
- lock.l_type = F_WRLCK;
- lock.l_start = 0;
- lock.l_whence = SEEK_SET;
- lock.l_len = 0;
+ do {
+ struct flock lock;
+ lock.l_type = F_WRLCK;
+ lock.l_start = 0;
+ lock.l_whence = SEEK_SET;
+ lock.l_len = 0;
- if(fcntl(FCGI_LISTENSOCK_FILENO, F_SETLKW, &lock) < 0) {
- return -1;
- }
-#endif /* USE_LOCKING */
+ if (fcntl(sock, F_SETLKW, &lock) != -1)
+ return 0;
+ } while (errno == EINTR && !fail_on_intr);
+
+ return -1;
+
+#else
return 0;
+#endif
}
\f
/*
*
*----------------------------------------------------------------------
*/
-static int ReleaseLock(void)
+static int ReleaseLock(int sock)
{
#ifdef USE_LOCKING
- struct flock lock;
- lock.l_type = F_UNLCK;
- lock.l_start = 0;
- lock.l_whence = SEEK_SET;
- lock.l_len = 0;
+ do {
+ struct flock lock;
+ lock.l_type = F_UNLCK;
+ lock.l_start = 0;
+ lock.l_whence = SEEK_SET;
+ lock.l_len = 0;
- if(fcntl(FCGI_LISTENSOCK_FILENO, F_SETLK, &lock) < 0) {
- return -1;
- }
-#endif /* USE_LOCKING */
+ if (fcntl(sock, F_SETLK, &lock) != -1)
+ return 0;
+ } while (errno == EINTR);
+
+ return -1;
+
+#else
return 0;
+#endif
}
\f
+/**********************************************************************
+ * Determine if the errno resulting from a failed accept() warrants a
+ * retry or exit(). Based on Apache's http_main.c accept() handling
+ * and Stevens' Unix Network Programming Vol 1, 2nd Ed, para. 15.6.
+ */
+static int is_reasonable_accept_errno (const int error)
+{
+ switch (error) {
+#ifdef EPROTO
+ /* EPROTO on certain older kernels really means ECONNABORTED, so
+ * we need to ignore it for them. See discussion in new-httpd
+ * archives nh.9701 search for EPROTO. Also see nh.9603, search
+ * for EPROTO: There is potentially a bug in Solaris 2.x x<6, and
+ * other boxes that implement tcp sockets in userland (i.e. on top of
+ * STREAMS). On these systems, EPROTO can actually result in a fatal
+ * loop. See PR#981 for example. It's hard to handle both uses of
+ * EPROTO. */
+ case EPROTO:
+#endif
+#ifdef ECONNABORTED
+ case ECONNABORTED:
+#endif
+ /* Linux generates the rest of these, other tcp stacks (i.e.
+ * bsd) tend to hide them behind getsockopt() interfaces. They
+ * occur when the net goes sour or the client disconnects after the
+ * three-way handshake has been done in the kernel but before
+ * userland has picked up the socket. */
+#ifdef ECONNRESET
+ case ECONNRESET:
+#endif
+#ifdef ETIMEDOUT
+ case ETIMEDOUT:
+#endif
+#ifdef EHOSTUNREACH
+ case EHOSTUNREACH:
+#endif
+#ifdef ENETUNREACH
+ case ENETUNREACH:
+#endif
+ return 1;
+
+ default:
+ return 0;
+ }
+}
+
+/**********************************************************************
+ * This works around a problem on Linux 2.0.x and SCO Unixware (maybe
+ * others?). When a connect() is made to a Unix Domain socket, but its
+ * not accept()ed before the web server gets impatient and close()s, an
+ * accept() results in a valid file descriptor, but no data to read.
+ * This causes a block on the first read() - which never returns!
+ *
+ * Another approach to this is to write() to the socket to provoke a
+ * SIGPIPE, but this is a pain because of the FastCGI protocol, the fact
+ * that whatever is written has to be universally ignored by all FastCGI
+ * web servers, and a SIGPIPE handler has to be installed which returns
+ * (or SIGPIPE is ignored).
+ *
+ * READABLE_UNIX_FD_DROP_DEAD_TIMEVAL = 2,0 by default.
+ *
+ * Making it shorter is probably safe, but I'll leave that to you. Making
+ * it 0,0 doesn't work reliably. The shorter you can reliably make it,
+ * the faster your application will be able to recover (waiting 2 seconds
+ * may _cause_ the problem when there is a very high demand). At any rate,
+ * this is better than perma-blocking.
+ */
+static int is_af_unix_keeper(const int fd)
+{
+ struct timeval tval = { READABLE_UNIX_FD_DROP_DEAD_TIMEVAL };
+ fd_set read_fds;
+
+ FD_ZERO(&read_fds);
+ FD_SET(fd, &read_fds);
+
+ return select(fd + 1, &read_fds, NULL, NULL, &tval) >= 0 && FD_ISSET(fd, &read_fds);
+}
+
/*
*----------------------------------------------------------------------
*
- * OS_FcgiIpcAccept --
+ * OS_Accept --
*
* Accepts a new FastCGI connection. This routine knows whether
* we're dealing with TCP based sockets or NT Named Pipes for IPC.
*
*----------------------------------------------------------------------
*/
-int OS_FcgiIpcAccept(char *clientAddrList)
+int OS_Accept(int listen_sock, int fail_on_intr, const char *clientAddrList)
{
int socket;
- union u_sockaddr {
+ union {
struct sockaddr_un un;
- struct sockaddr_in in;
+ struct sockaddr_in in;
} sa;
- int clilen = sizeof(sa);
-
- for(;;) {
- if(AcquireLock() < 0) {
- return -1;
- }
- /*
- * If there was a connection accepted from a prior FCGX_IsCGI
- * test, use it.
- */
- if(fcgiSocket != -1) {
- socket = fcgiSocket;
- memcpy(&sa, &fcgiSa, fcgiClilen);
- clilen = fcgiClilen;
- /*
- * Clear out the fcgiSocket as we will not be needing
- * it later.
- */
- fcgiSocket = -1;
- } else {
+
+ for (;;) {
+ if (AcquireLock(listen_sock, fail_on_intr))
+ return -1;
+
+ for (;;) {
do {
- socket = accept(FCGI_LISTENSOCK_FILENO,
- (struct sockaddr *) &sa.un,
- &clilen);
- } while ((socket < 0) && (errno==EINTR));
- }
- if(ReleaseLock() < 0) {
- return -1;
- }
- if(socket < 0) {
- return -1;
- }
- /*
- * If the new connection uses TCP/IP, check the IP address;
- * if the address isn't valid, close the connection and
- * try again.
- */
- if(sa.in.sin_family == AF_INET
- && !ClientAddrOK(&sa.in, clientAddrList)) {
- close(socket);
- socket = -1;
- } else {
- return socket;
- }
- }
+#ifdef HAVE_SOCKLEN
+ socklen_t len = sizeof(sa);
+#else
+ int len = sizeof(sa);
+#endif
+ socket = accept(listen_sock, (struct sockaddr *)&sa, &len);
+ } while (socket < 0 && errno == EINTR && !fail_on_intr);
+
+ if (socket < 0) {
+ if (!is_reasonable_accept_errno(errno)) {
+ int errnoSave = errno;
+ ReleaseLock(listen_sock);
+ errno = errnoSave;
+ return (-1);
+ }
+ errno = 0;
+ }
+ else { /* socket >= 0 */
+ int set = 1;
+
+ if (sa.in.sin_family != AF_INET)
+ break;
+
+#ifdef TCP_NODELAY
+ /* No replies to outgoing data, so disable Nagle */
+ setsockopt(socket, IPPROTO_TCP, TCP_NODELAY, (char *)&set, sizeof(set));
+#endif
+
+ /* Check that the client IP address is approved */
+ if (ClientAddrOK(&sa.in, clientAddrList))
+ break;
+
+ close(socket);
+ } /* socket >= 0 */
+ } /* for(;;) */
+
+ if (ReleaseLock(listen_sock))
+ return (-1);
+
+ if (sa.in.sin_family != AF_UNIX || is_af_unix_keeper(socket))
+ break;
+
+ close(socket);
+ } /* while(1) - lock */
+
+ return (socket);
}
\f
/*
*
*----------------------------------------------------------------------
*/
-int OS_IsFcgi()
+int OS_IsFcgi(int sock)
{
- int flags, flags1 ;
-
- fcgiClilen = sizeof(fcgiSa);
-
- /*
- * Put the file descriptor into non-blocking mode.
- */
- flags = fcntl(FCGI_LISTENSOCK_FILENO, F_GETFL, 0);
- flags |= O_NONBLOCK;
- if( (fcntl(FCGI_LISTENSOCK_FILENO, F_SETFL, flags)) == -1 ) {
- /*
- * XXX: The reason for the assert is that this call is not
- * supposed to return an error unless the
- * FCGI_LISTENSOCK_FILENO is closed. If it is closed
- * then we have an unexpected error which should cause
- * the assert to pop. The same is true for the following
- * asserts in this function.
- */
- assert(errno == 0);
- }
-
- /*
- * Perform an accept() on the file descriptor. If this is not a
- * listener socket we will get an error. Typically this will be
- * ENOTSOCK but it differs from platform to platform.
- */
- fcgiSocket = accept(FCGI_LISTENSOCK_FILENO, (struct sockaddr *) &fcgiSa.un,
- &fcgiClilen);
- if(fcgiSocket >= 0) {
- /*
- * This is a FastCGI listener socket because we accepted a
- * connection. fcgiSocket will be tested in FCGX_Accept before
- * performing another accept so that we don't lose this state.
- *
- * The new connection is put into blocking mode as we're not
- * doing asynchronous I/O.
- */
- flags1 = fcntl(fcgiSocket, F_GETFL, 0);
- flags1 &= ~(O_NONBLOCK);
- if( (fcntl(fcgiSocket, F_SETFL, flags1)) == -1 ) {
- assert(errno == 0);
- }
- isFastCGI = TRUE;
- } else {
- /*
- * If errno == EWOULDBLOCK then this is a valid FastCGI listener
- * socket without any connection pending at this time.
- *
- * NOTE: hp-ux can also return EAGAIN for listener sockets in
- * non-blocking mode when no connections are present.
- */
-#if (EAGAIN != EWOULDBLOCK)
- if((errno == EWOULDBLOCK) || (errno == EAGAIN)) {
+ int isFastCGI = FALSE;
+ union {
+ struct sockaddr_in in;
+ struct sockaddr_un un;
+ } sa;
+#ifdef HAVE_SOCKLEN
+ socklen_t len = sizeof(sa);
#else
- if(errno == EWOULDBLOCK) {
+ int len = sizeof(sa);
#endif
- isFastCGI = TRUE;
- } else {
- isFastCGI = FALSE;
- }
- }
- /*
- * Put the file descriptor back in blocking mode.
- */
- flags &= ~(O_NONBLOCK);
- if( (fcntl(FCGI_LISTENSOCK_FILENO, F_SETFL, flags)) == -1 ) {
- assert(errno == 0);
+ if (getpeername(sock, (struct sockaddr *)&sa, &len) != 0 && errno == ENOTCONN) {
+ return TRUE;
+ }
+ else {
+ return FALSE;
}
- return isFastCGI;
}
\f
/*