-/*
+/*
* 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.4 1998/12/09 03:25:43 roberts Exp $";
+static const char rcsid[] = "$Id: os_unix.c,v 1.10 1999/07/28 00:20:22 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;
{
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;
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;
}
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) {
lock.l_whence = SEEK_SET;
lock.l_len = 0;
- if(fcntl(FCGI_LISTENSOCK_FILENO,
+ if(fcntl(FCGI_LISTENSOCK_FILENO,
blocking ? F_SETLKW : F_SETLK, &lock) < 0) {
if (errno != EINTR)
return -1;
}
\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);
+}
+
/*
*----------------------------------------------------------------------
*
int OS_FcgiIpcAccept(char *clientAddrList)
{
int socket;
- union u_sockaddr {
+ union {
struct sockaddr_un un;
struct sockaddr_in in;
} sa;
- int clilen;
-
- if (AcquireLock(TRUE) < 0) {
- return (-1);
- }
- for (;;) {
- do {
- clilen = sizeof(sa);
- socket = accept(FCGI_LISTENSOCK_FILENO,
- (struct sockaddr *) &sa.un,
- &clilen);
- } while ((socket < 0) && (errno == EINTR));
-
- if (socket >= 0) {
- /*
- * If the new connection uses TCP/IP, check the client 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);
- continue;
- }
- break;
- }
-
- /* Based on Apache's (v1.3.1) http_main.c accept() handling and
- * Stevens' Unix Network Programming Vol 1, 2nd Ed, para. 15.6
- */
- switch (errno) {
-#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:
+#if defined __linux__
+ socklen_t len;
+#else
+ int len;
#endif
- break; /* switch(errno) */
- default: {
+ while (1) {
+ if (AcquireLock(TRUE) < 0)
+ return (-1);
+
+ while (1) {
+ do {
+ len = sizeof(sa);
+ socket = accept(FCGI_LISTENSOCK_FILENO, (struct sockaddr *) &sa.un, &len);
+ } while (socket < 0 && errno == EINTR);
+
+ if (socket < 0) {
+ if (!is_reasonable_accept_errno(errno)) {
int errnoSave = errno;
+
ReleaseLock();
errno = errnoSave;
+ return (-1);
}
- return (-1);
- } /* switch(errno) */
- } /* for(;;) */
+ errno = 0;
+ }
+ else {
+ 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);
+ }
+ } /* while(1) - accept */
+
+ if (ReleaseLock() < 0)
+ return (-1);
+
+ if (sa.in.sin_family != AF_UNIX || is_af_unix_keeper(socket))
+ break;
+
+ close(socket);
+ } /* while(1) - lock */
- if (ReleaseLock() < 0) {
- return (-1);
- }
return (socket);
}
\f
struct sockaddr_in in;
struct sockaddr_un un;
} sa;
+#if defined __linux__
+ socklen_t len = sizeof(sa);
+#else
int len = sizeof(sa);
+#endif
- if (getpeername(FCGI_LISTENSOCK_FILENO, (struct sockaddr *)&sa, &len) != 0
+ if (getpeername(FCGI_LISTENSOCK_FILENO, (struct sockaddr *)&sa, &len) != 0
&& errno == ENOTCONN)
isFastCGI = TRUE;
else
isFastCGI = FALSE;
-
+
return (isFastCGI);
}
\f