#ifndef lint
static
const
char
rcsid[] =
"$Id: os_unix.c,v 1.38 2003/06/22 00:16:43 robs Exp $"
;
#endif /* not lint */
#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> /* for fcntl */
#include <math.h>
#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/un.h>
#include <signal.h>
#ifdef HAVE_NETDB_H
#include <netdb.h>
#endif
#ifdef HAVE_SYS_SOCKET_H
#include <sys/socket.h> /* for getpeername */
#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(TRUE);
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 /* 4.3BSD Reno and later: BSDI, DEC */
if
(bindPathLen >=
sizeof
(servAddrPtr->sun_path)) {
return
-1;
}
#else /* 4.3 BSD Tahoe: Solaris, HPUX, DEC, ... */
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 /* 4.3BSD Reno and later: BSDI, DEC */
*servAddrLen =
sizeof
(servAddrPtr->sun_len)
+
sizeof
(servAddrPtr->sun_family)
+ bindPathLen + 1;
servAddrPtr->sun_len = *servAddrLen;
#else /* 4.3 BSD Tahoe: Solaris, HPUX, DEC, ... */
*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];
if
(
strlen
(bindPath) >= MAXPATHLEN) {
fprintf
(stderr,
"Listening socket path is longer than %d bytes -- exiting!\n"
,
MAXPATHLEN);
exit
(1);
}
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;
if
(
strlen
(bindPath) >= MAXPATHLEN) {
fprintf
(stderr,
"Listening socket path is too long\n"
);
exit
(1000);
}
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,
int
shutdown_ok)
{
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_ok)
{
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,
int
shutdown)
{
return
OS_Close(ipcFd, shutdown);
}
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
);
}
}