#ifndef lint
static const char rcsid[] = "$Id: os_unix.c,v 1.37 2002/03/05 19:14:49 robs Exp $";
#endif
#include "fcgi_config.h"
#include <sys/types.h>
#ifdef HAVE_NETINET_IN_H
#include <netinet/in.h>
#endif
#include <arpa/inet.h>
#include <assert.h>
#include <errno.h>
#include <fcntl.h>
#include <math.h>
#include <memory.h>
#include <netinet/tcp.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/time.h>
#include <sys/un.h>
#include <signal.h>
#ifdef HAVE_NETDB_H
#include <netdb.h>
#endif
#ifdef HAVE_SYS_SOCKET_H
#include <sys/socket.h>
#endif
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#include "fastcgi.h"
#include "fcgimisc.h"
#include "fcgios.h"
#ifndef INADDR_NONE
#define INADDR_NONE ((unsigned long) -1)
#endif
typedef struct {
OS_AsyncProc procPtr;
ClientData clientData;
int fd;
int len;
int offset;
void *buf;
int inUse;
} AioInfo;
#define AIO_RD_IX(fd) (fd * 2)
#define AIO_WR_IX(fd) ((fd * 2) + 1)
static int asyncIoInUse = FALSE;
static int asyncIoTableSize = 16;
static AioInfo *asyncIoTable = NULL;
static int libInitialized = FALSE;
static fd_set readFdSet;
static fd_set writeFdSet;
static fd_set readFdSetPost;
static int numRdPosted = 0;
static fd_set writeFdSetPost;
static int numWrPosted = 0;
static int volatile maxFd = -1;
static int shutdownPending = FALSE;
static int shutdownNow = FALSE;
void OS_ShutdownPending()
{
shutdownPending = TRUE;
}
static void OS_Sigusr1Handler(int signo)
{
OS_ShutdownPending();
}
static void OS_SigpipeHandler(int signo)
{
;
}
static void installSignalHandler(int signo, const struct sigaction * act, int force)
{
struct sigaction sa;
sigaction(signo, NULL, &sa);
if (force || sa.sa_handler == SIG_DFL)
{
sigaction(signo, act, NULL);
}
}
static void OS_InstallSignalHandlers(int force)
{
struct sigaction sa;
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0;
sa.sa_handler = OS_SigpipeHandler;
installSignalHandler(SIGPIPE, &sa, force);
sa.sa_handler = OS_Sigusr1Handler;
installSignalHandler(SIGUSR1, &sa, force);
}
int OS_LibInit(int stdioFds[3])
{
if(libInitialized)
return 0;
asyncIoTable = (AioInfo *)malloc(asyncIoTableSize * sizeof(AioInfo));
if(asyncIoTable == NULL) {
errno = ENOMEM;
return -1;
}
memset((char *) asyncIoTable, 0,
asyncIoTableSize * sizeof(AioInfo));
FD_ZERO(&readFdSet);
FD_ZERO(&writeFdSet);
FD_ZERO(&readFdSetPost);
FD_ZERO(&writeFdSetPost);
OS_InstallSignalHandlers(FALSE);
libInitialized = TRUE;
return 0;
}
void OS_LibShutdown()
{
if(!libInitialized)
return;
free(asyncIoTable);
asyncIoTable = NULL;
libInitialized = FALSE;
return;
}
static int OS_BuildSockAddrUn(const char *bindPath,
struct sockaddr_un *servAddrPtr,
int *servAddrLen)
{
int bindPathLen = strlen(bindPath);
#ifdef HAVE_SOCKADDR_UN_SUN_LEN
if(bindPathLen >= sizeof(servAddrPtr->sun_path)) {
return -1;
}
#else
if(bindPathLen > sizeof(servAddrPtr->sun_path)) {
return -1;
}
#endif
memset((char *) servAddrPtr, 0, sizeof(*servAddrPtr));
servAddrPtr->sun_family = AF_UNIX;
memcpy(servAddrPtr->sun_path, bindPath, bindPathLen);
#ifdef HAVE_SOCKADDR_UN_SUN_LEN
*servAddrLen = sizeof(servAddrPtr->sun_len)
+ sizeof(servAddrPtr->sun_family)
+ bindPathLen + 1;
servAddrPtr->sun_len = *servAddrLen;
#else
*servAddrLen = sizeof(servAddrPtr->sun_family) + bindPathLen;
#endif
return 0;
}
union SockAddrUnion {
struct sockaddr_un unixVariant;
struct sockaddr_in inetVariant;
};
int OS_CreateLocalIpcFd(const char *bindPath, int backlog)
{
int listenSock, servLen;
union SockAddrUnion sa;
int tcp = FALSE;
unsigned long tcp_ia = 0;
char *tp;
short port = 0;
char host[MAXPATHLEN];
strcpy(host, bindPath);
if((tp = strchr(host, ':')) != 0) {
*tp++ = 0;
if((port = atoi(tp)) == 0) {
*--tp = ':';
} else {
tcp = TRUE;
}
}
if(tcp) {
if (!*host || !strcmp(host,"*")) {
tcp_ia = htonl(INADDR_ANY);
} else {
tcp_ia = inet_addr(host);
if (tcp_ia == INADDR_NONE) {
struct hostent * hep;
hep = gethostbyname(host);
if ((!hep) || (hep->h_addrtype != AF_INET || !hep->h_addr_list[0])) {
fprintf(stderr, "Cannot resolve host name %s -- exiting!\n", host);
exit(1);
}
if (hep->h_addr_list[1]) {
fprintf(stderr, "Host %s has multiple addresses ---\n", host);
fprintf(stderr, "you must choose one explicitly!!!\n");
exit(1);
}
tcp_ia = ((struct in_addr *) (hep->h_addr))->s_addr;
}
}
}
if(tcp) {
listenSock = socket(AF_INET, SOCK_STREAM, 0);
if(listenSock >= 0) {
int flag = 1;
if(setsockopt(listenSock, SOL_SOCKET, SO_REUSEADDR,
(char *) &flag, sizeof(flag)) < 0) {
fprintf(stderr, "Can't set SO_REUSEADDR.\n");
exit(1001);
}
}
} else {
listenSock = socket(AF_UNIX, SOCK_STREAM, 0);
}
if(listenSock < 0) {
return -1;
}
if(tcp) {
memset((char *) &sa.inetVariant, 0, sizeof(sa.inetVariant));
sa.inetVariant.sin_family = AF_INET;
sa.inetVariant.sin_addr.s_addr = tcp_ia;
sa.inetVariant.sin_port = htons(port);
servLen = sizeof(sa.inetVariant);
} else {
unlink(bindPath);
if(OS_BuildSockAddrUn(bindPath, &sa.unixVariant, &servLen)) {
fprintf(stderr, "Listening socket's path name is too long.\n");
exit(1000);
}
}
if(bind(listenSock, (struct sockaddr *) &sa.unixVariant, servLen) < 0
|| listen(listenSock, backlog) < 0) {
perror("bind/listen");
exit(errno);
}
return listenSock;
}
int OS_FcgiConnect(char *bindPath)
{
union SockAddrUnion sa;
int servLen, resultSock;
int connectStatus;
char *tp;
char host[MAXPATHLEN];
short port = 0;
int tcp = FALSE;
strcpy(host, bindPath);
if((tp = strchr(host, ':')) != 0) {
*tp++ = 0;
if((port = atoi(tp)) == 0) {
*--tp = ':';
} else {
tcp = TRUE;
}
}
if(tcp == TRUE) {
struct hostent *hp;
if((hp = gethostbyname((*host ? host : "localhost"))) == NULL) {
fprintf(stderr, "Unknown host: %s\n", bindPath);
exit(1000);
}
sa.inetVariant.sin_family = AF_INET;
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);
} else {
if(OS_BuildSockAddrUn(bindPath, &sa.unixVariant, &servLen)) {
fprintf(stderr, "Listening socket's path name is too long.\n");
exit(1000);
}
resultSock = socket(AF_UNIX, SOCK_STREAM, 0);
}
ASSERT(resultSock >= 0);
connectStatus = connect(resultSock, (struct sockaddr *) &sa.unixVariant,
servLen);
if(connectStatus >= 0) {
return resultSock;
} else {
close(resultSock);
return -1;
}
}
int OS_Read(int fd, char * buf, size_t len)
{
if (shutdownNow) return -1;
return(read(fd, buf, len));
}
int OS_Write(int fd, char * buf, size_t len)
{
if (shutdownNow) return -1;
return(write(fd, buf, len));
}
int OS_SpawnChild(char *appPath, int listenFd)
{
int forkResult;
forkResult = fork();
if(forkResult < 0) {
exit(errno);
}
if(forkResult == 0) {
close(STDIN_FILENO);
if(listenFd != FCGI_LISTENSOCK_FILENO) {
dup2(listenFd, FCGI_LISTENSOCK_FILENO);
close(listenFd);
}
close(STDOUT_FILENO);
close(STDERR_FILENO);
execl(appPath, appPath, NULL);
exit(errno);
}
return 0;
}
int OS_AsyncReadStdin(void *buf, int len, OS_AsyncProc procPtr,
ClientData clientData)
{
int index = AIO_RD_IX(STDIN_FILENO);
asyncIoInUse = TRUE;
ASSERT(asyncIoTable[index].inUse == 0);
asyncIoTable[index].procPtr = procPtr;
asyncIoTable[index].clientData = clientData;
asyncIoTable[index].fd = STDIN_FILENO;
asyncIoTable[index].len = len;
asyncIoTable[index].offset = 0;
asyncIoTable[index].buf = buf;
asyncIoTable[index].inUse = 1;
FD_SET(STDIN_FILENO, &readFdSet);
if(STDIN_FILENO > maxFd)
maxFd = STDIN_FILENO;
return 0;
}
static void GrowAsyncTable(void)
{
int oldTableSize = asyncIoTableSize;
asyncIoTableSize = asyncIoTableSize * 2;
asyncIoTable = (AioInfo *)realloc(asyncIoTable, asyncIoTableSize * sizeof(AioInfo));
if(asyncIoTable == NULL) {
errno = ENOMEM;
exit(errno);
}
memset((char *) &asyncIoTable[oldTableSize], 0,
oldTableSize * sizeof(AioInfo));
}
int OS_AsyncRead(int fd, int offset, void *buf, int len,
OS_AsyncProc procPtr, ClientData clientData)
{
int index = AIO_RD_IX(fd);
ASSERT(asyncIoTable != NULL);
asyncIoInUse = TRUE;
if(fd > maxFd)
maxFd = fd;
while (index >= asyncIoTableSize) {
GrowAsyncTable();
}
ASSERT(asyncIoTable[index].inUse == 0);
asyncIoTable[index].procPtr = procPtr;
asyncIoTable[index].clientData = clientData;
asyncIoTable[index].fd = fd;
asyncIoTable[index].len = len;
asyncIoTable[index].offset = offset;
asyncIoTable[index].buf = buf;
asyncIoTable[index].inUse = 1;
FD_SET(fd, &readFdSet);
return 0;
}
int OS_AsyncWrite(int fd, int offset, void *buf, int len,
OS_AsyncProc procPtr, ClientData clientData)
{
int index = AIO_WR_IX(fd);
asyncIoInUse = TRUE;
if(fd > maxFd)
maxFd = fd;
while (index >= asyncIoTableSize) {
GrowAsyncTable();
}
ASSERT(asyncIoTable[index].inUse == 0);
asyncIoTable[index].procPtr = procPtr;
asyncIoTable[index].clientData = clientData;
asyncIoTable[index].fd = fd;
asyncIoTable[index].len = len;
asyncIoTable[index].offset = offset;
asyncIoTable[index].buf = buf;
asyncIoTable[index].inUse = 1;
FD_SET(fd, &writeFdSet);
return 0;
}
int OS_Close(int fd)
{
if (fd == -1)
return 0;
if (asyncIoInUse) {
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);
if (asyncIoTable[index].inUse != 0) {
asyncIoTable[index].inUse = 0;
}
if (maxFd == fd) {
maxFd--;
}
}
if (shutdown(fd, 1) == 0)
{
struct timeval tv;
fd_set rfds;
int rv;
char trash[1024];
FD_ZERO(&rfds);
do
{
FD_SET(fd, &rfds);
tv.tv_sec = 2;
tv.tv_usec = 0;
rv = select(fd + 1, &rfds, NULL, NULL, &tv);
}
while (rv > 0 && read(fd, trash, sizeof(trash)) > 0);
}
return close(fd);
}
int OS_CloseRead(int fd)
{
if(asyncIoTable[AIO_RD_IX(fd)].inUse != 0) {
asyncIoTable[AIO_RD_IX(fd)].inUse = 0;
FD_CLR(fd, &readFdSet);
}
return shutdown(fd, 0);
}
int OS_DoIo(struct timeval *tmo)
{
int fd, len, selectStatus;
OS_AsyncProc procPtr;
ClientData clientData;
AioInfo *aioPtr;
fd_set readFdSetCpy;
fd_set writeFdSetCpy;
asyncIoInUse = TRUE;
FD_ZERO(&readFdSetCpy);
FD_ZERO(&writeFdSetCpy);
for(fd = 0; fd <= maxFd; fd++) {
if(FD_ISSET(fd, &readFdSet)) {
FD_SET(fd, &readFdSetCpy);
}
if(FD_ISSET(fd, &writeFdSet)) {
FD_SET(fd, &writeFdSetCpy);
}
}
if(numRdPosted == 0 && numWrPosted == 0) {
selectStatus = select((maxFd+1), &readFdSetCpy, &writeFdSetCpy,
NULL, tmo);
if(selectStatus < 0) {
exit(errno);
}
for(fd = 0; fd <= maxFd; fd++) {
if(FD_ISSET(fd, &readFdSetCpy)) {
numRdPosted++;
FD_SET(fd, &readFdSetPost);
FD_CLR(fd, &readFdSet);
}
if(FD_ISSET(fd, &writeFdSetCpy)) {
numWrPosted++;
FD_SET(fd, &writeFdSetPost);
FD_CLR(fd, &writeFdSet);
}
}
}
if(numRdPosted == 0 && numWrPosted == 0)
return 0;
for(fd = 0; fd <= maxFd; fd++) {
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;
aioPtr->procPtr = NULL;
clientData = aioPtr->clientData;
aioPtr->inUse = 0;
(*procPtr)(clientData, len);
}
if(FD_ISSET(fd, &writeFdSetPost) &&
asyncIoTable[AIO_WR_IX(fd)].inUse) {
numWrPosted--;
FD_CLR(fd, &writeFdSetPost);
aioPtr = &asyncIoTable[AIO_WR_IX(fd)];
len = write(aioPtr->fd, aioPtr->buf, aioPtr->len);
procPtr = aioPtr->procPtr;
aioPtr->procPtr = NULL;
clientData = aioPtr->clientData;
aioPtr->inUse = 0;
(*procPtr)(clientData, len);
}
}
return 0;
}
static char * str_dup(const char * str)
{
char * sdup = (char *) malloc(strlen(str) + 1);
if (sdup)
strcpy(sdup, str);
return sdup;
}
static int ClientAddrOK(struct sockaddr_in *saPtr, const char *clientList)
{
int result = FALSE;
char *clientListCopy, *cur, *next;
if (clientList == NULL || *clientList == '\0') {
return TRUE;
}
clientListCopy = str_dup(clientList);
for (cur = clientListCopy; cur != NULL; cur = next) {
next = strchr(cur, ',');
if (next != NULL) {
*next++ = '\0';
}
if (inet_addr(cur) == saPtr->sin_addr.s_addr) {
result = TRUE;
break;
}
}
free(clientListCopy);
return result;
}
static int AcquireLock(int sock, int fail_on_intr)
{
#ifdef USE_LOCKING
do {
struct flock lock;
lock.l_type = F_WRLCK;
lock.l_start = 0;
lock.l_whence = SEEK_SET;
lock.l_len = 0;
if (fcntl(sock, F_SETLKW, &lock) != -1)
return 0;
} while (errno == EINTR
&& ! fail_on_intr
&& ! shutdownPending);
return -1;
#else
return 0;
#endif
}
static int ReleaseLock(int sock)
{
#ifdef USE_LOCKING
do {
struct flock lock;
lock.l_type = F_UNLCK;
lock.l_start = 0;
lock.l_whence = SEEK_SET;
lock.l_len = 0;
if (fcntl(sock, F_SETLK, &lock) != -1)
return 0;
} while (errno == EINTR);
return -1;
#else
return 0;
#endif
}
static int is_reasonable_accept_errno (const int error)
{
switch (error) {
#ifdef EPROTO
case EPROTO:
#endif
#ifdef ECONNABORTED
case ECONNABORTED:
#endif
#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;
}
}
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_Accept(int listen_sock, int fail_on_intr, const char *webServerAddrs)
{
int socket = -1;
union {
struct sockaddr_un un;
struct sockaddr_in in;
} sa;
for (;;) {
if (AcquireLock(listen_sock, fail_on_intr))
return -1;
for (;;) {
do {
#ifdef HAVE_SOCKLEN
socklen_t len = sizeof(sa);
#else
int len = sizeof(sa);
#endif
if (shutdownPending) break;
socket = accept(listen_sock, (struct sockaddr *)&sa, &len);
} while (socket < 0
&& errno == EINTR
&& ! fail_on_intr
&& ! shutdownPending);
if (socket < 0) {
if (shutdownPending || ! is_reasonable_accept_errno(errno)) {
int errnoSave = errno;
ReleaseLock(listen_sock);
if (! shutdownPending) {
errno = errnoSave;
}
return (-1);
}
errno = 0;
}
else {
int set = 1;
if (sa.in.sin_family != AF_INET)
break;
#ifdef TCP_NODELAY
setsockopt(socket, IPPROTO_TCP, TCP_NODELAY, (char *)&set, sizeof(set));
#endif
if (ClientAddrOK(&sa.in, webServerAddrs))
break;
close(socket);
}
}
if (ReleaseLock(listen_sock))
return (-1);
if (sa.in.sin_family != AF_UNIX || is_af_unix_keeper(socket))
break;
close(socket);
}
return (socket);
}
int OS_IpcClose(int ipcFd)
{
return OS_Close(ipcFd);
}
int OS_IsFcgi(int sock)
{
union {
struct sockaddr_in in;
struct sockaddr_un un;
} sa;
#ifdef HAVE_SOCKLEN
socklen_t len = sizeof(sa);
#else
int len = sizeof(sa);
#endif
errno = 0;
if (getpeername(sock, (struct sockaddr *)&sa, &len) != 0 && errno == ENOTCONN) {
return TRUE;
}
else {
return FALSE;
}
}
void OS_SetFlags(int fd, int flags)
{
int val;
if((val = fcntl(fd, F_GETFL, 0)) < 0) {
exit(errno);
}
val |= flags;
if(fcntl(fd, F_SETFL, val) < 0) {
exit(errno);
}
}