📄 Viewing: fcgi_pm.c
/*
* $Id: fcgi_pm.c,v 1.93 2004/04/15 02:01:26 robs Exp $
*/
#include "fcgi.h"
#if defined(APACHE2) && !defined(WIN32)
#include <pwd.h>
#include <unistd.h>
#include "unixd.h"
#include "apr_signal.h"
#endif
#ifndef WIN32
#include <utime.h>
#endif
#ifdef _HPUX_SOURCE
#include <unistd.h>
#define seteuid(arg) setresuid(-1, (arg), -1)
#endif
int fcgi_dynamic_total_proc_count = 0; /* number of running apps */
time_t fcgi_dynamic_epoch = 0; /* last time kill_procs was
* invoked by process mgr */
time_t fcgi_dynamic_last_analyzed = 0; /* last time calculation was
* made for the dynamic procs */
static time_t now = 0;
#ifdef WIN32
#ifdef APACHE2
#include "mod_cgi.h"
#endif
#pragma warning ( disable : 4100 4102 )
static BOOL bTimeToDie = FALSE; /* process termination flag */
HANDLE fcgi_event_handles[3];
#ifndef SIGKILL
#define SIGKILL 9
#endif
#endif
#ifndef WIN32
static int seteuid_root(void)
{
int rc = seteuid(getuid());
if (rc) {
ap_log_error(FCGI_LOG_ALERT, fcgi_apache_main_server,
"FastCGI: seteuid(0) failed");
}
return rc;
}
static int seteuid_user(void)
{
int rc = seteuid(ap_user_id);
if (rc) {
ap_log_error(FCGI_LOG_ALERT, fcgi_apache_main_server,
"FastCGI: seteuid(%u) failed", (unsigned)ap_user_id);
}
return rc;
}
#endif
/*
* Signal the process to exit. How (or if) the process responds
* depends on the FastCGI application library (esp. on Win32) and
* possibly application code (signal handlers and whether or not
* SA_RESTART is on). At any rate, we send the signal with the
* hopes that the process will exit on its own. Later, as we
* review the state of application processes, if we see one marked
* for death, but that hasn't died within a specified period of
* time, fcgi_kill() is called again with a KILL)
*/
static void fcgi_kill(ServerProcess *process, int sig)
{
FCGIDBG3("fcgi_kill(%ld, %d)", (long) process->pid, sig);
process->state = FCGI_VICTIM_STATE;
#ifdef WIN32
if (sig == SIGTERM)
{
SetEvent(process->terminationEvent);
}
else if (sig == SIGKILL)
{
TerminateProcess(process->handle, 1);
}
else
{
ap_assert(0);
}
#else /* !WIN32 */
if (fcgi_wrapper)
{
seteuid_root();
}
kill(process->pid, sig);
if (fcgi_wrapper)
{
seteuid_user();
}
#endif /* !WIN32 */
}
/*******************************************************************************
* Send SIGTERM to each process in the server class, remove socket
* file if appropriate. Currently this is only called when the PM is shutting
* down and thus memory isn't freed and sockets and files aren't closed.
*/
static void shutdown_all()
{
fcgi_server *s = fcgi_servers;
while (s)
{
ServerProcess *proc = s->procs;
int i;
int numChildren = (s->directive == APP_CLASS_DYNAMIC)
? dynamicMaxClassProcs
: s->numProcesses;
#ifndef WIN32
if (s->socket_path != NULL && s->directive != APP_CLASS_EXTERNAL)
{
/* Remove the socket file */
if (unlink(s->socket_path) != 0 && errno != ENOENT) {
ap_log_error(FCGI_LOG_ERR, fcgi_apache_main_server,
"FastCGI: unlink() failed to remove socket file \"%s\" for%s server \"%s\"",
s->socket_path,
(s->directive == APP_CLASS_DYNAMIC) ? " (dynamic)" : "", s->fs_path);
}
}
#endif
/* Send TERM to all processes */
for (i = 0; i < numChildren; i++, proc++)
{
if (proc->state == FCGI_RUNNING_STATE)
{
fcgi_kill(proc, SIGTERM);
}
}
s = s->next;
}
#if defined(WIN32) && (WIN32_SHUTDOWN_GRACEFUL_WAIT > 0)
/*
* WIN32 applications may not have support for the shutdown event
* depending on their application library version
*/
Sleep(WIN32_SHUTDOWN_GRACEFUL_WAIT);
s = fcgi_servers;
while (s)
{
ServerProcess *proc = s->procs;
int i;
int numChildren = (s->directive == APP_CLASS_DYNAMIC)
? dynamicMaxClassProcs
: s->numProcesses;
/* Send KILL to all processes */
for (i = 0; i < numChildren; i++, proc++)
{
if (proc->state == FCGI_RUNNING_STATE)
{
fcgi_kill(proc, SIGKILL);
}
}
s = s->next;
}
#endif /* WIN32 */
}
static int init_listen_sock(fcgi_server * fs)
{
ap_assert(fs->directive != APP_CLASS_EXTERNAL);
/* Create the socket */
if ((fs->listenFd = socket(fs->socket_addr->sa_family, SOCK_STREAM, 0)) < 0)
{
#ifdef WIN32
errno = WSAGetLastError(); /* Not sure if this will work as expected */
#endif
ap_log_error(FCGI_LOG_CRIT_ERRNO, fcgi_apache_main_server,
"FastCGI: can't create %sserver \"%s\": socket() failed",
(fs->directive == APP_CLASS_DYNAMIC) ? "(dynamic) " : "",
fs->fs_path);
return -1;
}
#ifndef WIN32
if (fs->socket_addr->sa_family == AF_UNIX)
{
/* Remove any existing socket file.. just in case */
unlink(((struct sockaddr_un *)fs->socket_addr)->sun_path);
}
else
#endif
{
int flag = 1;
setsockopt(fs->listenFd, SOL_SOCKET, SO_REUSEADDR, (char *)&flag, sizeof(flag));
}
/* Bind it to the socket_addr */
if (bind(fs->listenFd, fs->socket_addr, fs->socket_addr_len))
{
char port[11];
#ifdef WIN32
errno = WSAGetLastError();
#endif
ap_snprintf(port, sizeof(port), "port=%d",
((struct sockaddr_in *)fs->socket_addr)->sin_port);
ap_log_error(FCGI_LOG_CRIT_ERRNO, fcgi_apache_main_server,
"FastCGI: can't create %sserver \"%s\": bind() failed [%s]",
(fs->directive == APP_CLASS_DYNAMIC) ? "(dynamic) " : "",
fs->fs_path,
#ifndef WIN32
(fs->socket_addr->sa_family == AF_UNIX) ?
((struct sockaddr_un *)fs->socket_addr)->sun_path :
#endif
port);
}
#ifndef WIN32
/* Twiddle Unix socket permissions */
else if (fs->socket_addr->sa_family == AF_UNIX
&& chmod(((struct sockaddr_un *)fs->socket_addr)->sun_path, S_IRUSR | S_IWUSR))
{
ap_log_error(FCGI_LOG_CRIT, fcgi_apache_main_server,
"FastCGI: can't create %sserver \"%s\": chmod() of socket failed",
(fs->directive == APP_CLASS_DYNAMIC) ? "(dynamic) " : "",
fs->fs_path);
}
#endif
/* Set to listen */
else if (listen(fs->listenFd, fs->listenQueueDepth))
{
#ifdef WIN32
errno = WSAGetLastError();
#endif
ap_log_error(FCGI_LOG_CRIT_ERRNO, fcgi_apache_main_server,
"FastCGI: can't create %sserver \"%s\": listen() failed",
(fs->directive == APP_CLASS_DYNAMIC) ? "(dynamic) " : "",
fs->fs_path);
}
else
{
return 0;
}
#ifdef WIN32
closesocket(fs->listenFd);
#else
close(fs->listenFd);
#endif
fs->listenFd = -1;
return -2;
}
/*
*----------------------------------------------------------------------
*
* pm_main
*
* The FastCGI process manager, which runs as a separate
* process responsible for:
* - Starting all the FastCGI proceses.
* - Restarting any of these processes that die (indicated
* by SIGCHLD).
* - Catching SIGTERM and relaying it to all the FastCGI
* processes before exiting.
*
* Inputs:
* Uses global variable fcgi_servers.
*
* Results:
* Does not return.
*
* Side effects:
* Described above.
*
*----------------------------------------------------------------------
*/
#ifndef WIN32
static int caughtSigTerm = FALSE;
static int caughtSigChld = FALSE;
static int caughtSigAlarm = FALSE;
static void signal_handler(int signo)
{
if ((signo == SIGTERM) || (signo == SIGUSR1) || (signo == SIGHUP)) {
/* SIGUSR1 & SIGHUP are sent by apache to its process group
* when apache get 'em. Apache follows up (1.2.x) with attacks
* on each of its child processes, but we've got the KillMgr
* sitting between us so we never see the KILL. The main loop
* in ProcMgr also checks to see if the KillMgr has terminated,
* and if it has, we handl it as if we should shutdown too. */
caughtSigTerm = TRUE;
} else if(signo == SIGCHLD) {
caughtSigChld = TRUE;
} else if(signo == SIGALRM) {
caughtSigAlarm = TRUE;
}
}
#endif
/*
*----------------------------------------------------------------------
*
* spawn_fs_process --
*
* Fork and exec the specified fcgi process.
*
* Results:
* 0 for successful fork, -1 for failed fork.
*
* In case the child fails before or in the exec, the child
* obtains the error log by calling getErrLog, logs
* the error, and exits with exit status = errno of
* the failed system call.
*
* Side effects:
* Child process created.
*
*----------------------------------------------------------------------
*/
static pid_t spawn_fs_process(fcgi_server *fs, ServerProcess *process)
{
#ifndef WIN32
pid_t child_pid;
int i;
char *dirName;
char *dnEnd, *failedSysCall;
child_pid = fork();
if (child_pid) {
return child_pid;
}
/* We're the child. We're gonna exec() so pools don't matter. */
dnEnd = strrchr(fs->fs_path, '/');
if (dnEnd == NULL) {
dirName = "./";
} else {
dirName = ap_pcalloc(fcgi_config_pool, dnEnd - fs->fs_path + 1);
dirName = memcpy(dirName, fs->fs_path, dnEnd - fs->fs_path);
}
if (chdir(dirName) < 0) {
failedSysCall = "chdir()";
goto FailedSystemCallExit;
}
#ifndef __EMX__
/* OS/2 dosen't support nice() */
if (fs->processPriority != 0) {
if (nice(fs->processPriority) == -1) {
failedSysCall = "nice()";
goto FailedSystemCallExit;
}
}
#endif
/* Open the listenFd on spec'd fd */
if (fs->listenFd != FCGI_LISTENSOCK_FILENO)
dup2(fs->listenFd, FCGI_LISTENSOCK_FILENO);
/* Close all other open fds, except stdout/stderr. Leave these two open so
* FastCGI applications don't have to find and fix ALL 3rd party libs that
* write to stdout/stderr inadvertantly. For now, just leave 'em open to the
* main server error_log - @@@ provide a directive control where this goes.
*/
ap_error_log2stderr(fcgi_apache_main_server);
dup2(2, 1);
for (i = 0; i < FCGI_MAX_FD; i++) {
if (i != FCGI_LISTENSOCK_FILENO && i != 2 && i != 1) {
close(i);
}
}
/* Ignore SIGPIPE by default rather than terminate. The fs SHOULD
* install its own handler. */
signal(SIGPIPE, SIG_IGN);
if (fcgi_wrapper)
{
char *shortName;
/* Relinquish our root real uid powers */
seteuid_root();
setuid(ap_user_id);
/* Apache (2 anyway) doesn't use suexec if there is no user/group in
* effect - this translates to a uid/gid of 0/0 (which should never
* be a valid uid/gid for an suexec invocation so it should be safe */
if (fs->uid == 0 && fs->gid == 0) {
goto NO_SUEXEC;
}
#ifdef NO_SUEXEC_FOR_AP_USER_N_GROUP
/* AP13 does not use suexec if the target uid/gid is the same as the
* server's - AP20 does. I (now) consider the AP2 approach better
* (fcgi_pm.c v1.42 incorporated the 1.3 behaviour, v1.84 reverted it,
* v1.85 added the compile time option to use the old behaviour). */
if (fcgi_user_id == fs->uid && fcgi_group_id == fs->gid) {
goto NO_SUEXEC;
}
#endif
shortName = strrchr(fs->fs_path, '/') + 1;
do {
execle(fcgi_wrapper, fcgi_wrapper, fs->username, fs->group,
shortName, NULL, fs->envp);
} while (errno == EINTR);
}
else
{
NO_SUEXEC:
do {
execle(fs->fs_path, fs->fs_path, NULL, fs->envp);
} while (errno == EINTR);
}
failedSysCall = "execle()";
FailedSystemCallExit:
fprintf(stderr, "FastCGI: can't start server \"%s\" (pid %ld), %s failed: %s\n",
fs->fs_path, (long) getpid(), failedSysCall, strerror(errno));
exit(-1);
/* avoid an irrelevant compiler warning */
return(0);
#else /* WIN32 */
#ifdef APACHE2
/* based on mod_cgi.c:run_cgi_child() */
apr_pool_t * tp;
char * termination_env_string;
HANDLE listen_handle = INVALID_HANDLE_VALUE;
apr_procattr_t * procattr;
apr_proc_t proc = { 0 };
apr_file_t * file;
int i = 0;
cgi_exec_info_t e_info = { 0 };
request_rec r = { 0 };
const char *command;
const char **argv;
int rv;
APR_OPTIONAL_FN_TYPE(ap_cgi_build_command) *cgi_build_command;
cgi_build_command = APR_RETRIEVE_OPTIONAL_FN(ap_cgi_build_command);
if (cgi_build_command == NULL)
{
ap_log_error(FCGI_LOG_CRIT, fcgi_apache_main_server,
"FastCGI: can't exec server \"%s\", mod_cgi isn't loaded",
fs->fs_path);
return 0;
}
if (apr_pool_create(&tp, fcgi_config_pool))
return 0;
process->terminationEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
if (process->terminationEvent == NULL)
goto CLEANUP;
SetHandleInformation(process->terminationEvent, HANDLE_FLAG_INHERIT, TRUE);
termination_env_string = ap_psprintf(tp,
"_FCGI_SHUTDOWN_EVENT_=%ld", process->terminationEvent);
while (fs->envp[i]) i++;
fs->envp[i++] = termination_env_string;
fs->envp[i] = (char *) fs->mutex_env_string;
ap_assert(fs->envp[i + 1] == NULL);
if (fs->socket_path)
{
SECURITY_ATTRIBUTES sa = { 0 };
sa.bInheritHandle = TRUE;
sa.nLength = sizeof(sa);
listen_handle = CreateNamedPipe(fs->socket_path,
PIPE_ACCESS_DUPLEX,
PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT,
PIPE_UNLIMITED_INSTANCES, 4096, 4096, 0, &sa);
if (listen_handle == INVALID_HANDLE_VALUE)
{
ap_log_error(FCGI_LOG_CRIT, fcgi_apache_main_server,
"FastCGI: can't exec server \"%s\", CreateNamedPipe() failed",
fs->fs_path);
goto CLEANUP;
}
}
else
{
listen_handle = (HANDLE) fs->listenFd;
}
r.per_dir_config = fcgi_apache_main_server->lookup_defaults;
r.server = fcgi_apache_main_server;
r.filename = (char *) fs->fs_path;
r.pool = tp;
r.subprocess_env = apr_table_make(tp, 0);
e_info.cmd_type = APR_PROGRAM;
rv = cgi_build_command(&command, &argv, &r, tp, &e_info);
if (rv != APR_SUCCESS)
{
ap_log_error(FCGI_LOG_CRIT, fcgi_apache_main_server,
"FastCGI: don't know how to spawn cmd child process: %s",
fs->fs_path);
goto CLEANUP;
}
if (apr_procattr_create(&procattr, tp))
goto CLEANUP;
if (apr_procattr_dir_set(procattr, ap_make_dirstr_parent(tp, fs->fs_path)))
goto CLEANUP;
if (apr_procattr_cmdtype_set(procattr, e_info.cmd_type))
goto CLEANUP;
if (apr_procattr_detach_set(procattr, 1))
goto CLEANUP;
if (apr_os_file_put(&file, &listen_handle, 0, tp))
goto CLEANUP;
/* procattr is opaque so we have to use this - unfortuantely it dups */
if (apr_procattr_child_in_set(procattr, file, NULL))
goto CLEANUP;
if (apr_proc_create(&proc, command, argv, fs->envp, procattr, tp))
goto CLEANUP;
process->handle = proc.hproc;
CLEANUP:
if (fs->socket_path && listen_handle != INVALID_HANDLE_VALUE)
{
CloseHandle(listen_handle);
}
if (i)
{
fs->envp[i - 1] = NULL;
}
ap_destroy_pool(tp);
return proc.pid;
#else /* WIN32 && !APACHE2 */
/* Adapted from Apache's util_script.c ap_call_exec() */
char *interpreter = NULL;
char *quoted_filename;
char *pCommand;
char *pEnvBlock, *pNext;
int i = 0;
int iEnvBlockLen = 1;
file_type_e fileType;
STARTUPINFO si;
PROCESS_INFORMATION pi;
request_rec r;
pid_t pid = -1;
pool * tp = ap_make_sub_pool(fcgi_config_pool);
HANDLE listen_handle = INVALID_HANDLE_VALUE;
char * termination_env_string = NULL;
process->terminationEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
if (process->terminationEvent == NULL)
{
ap_log_error(FCGI_LOG_CRIT, fcgi_apache_main_server,
"FastCGI: can't create termination event for server \"%s\", "
"CreateEvent() failed", fs->fs_path);
goto CLEANUP;
}
SetHandleInformation(process->terminationEvent, HANDLE_FLAG_INHERIT, TRUE);
termination_env_string = ap_psprintf(tp,
"_FCGI_SHUTDOWN_EVENT_=%ld", process->terminationEvent);
if (fs->socket_path)
{
SECURITY_ATTRIBUTES sa;
sa.lpSecurityDescriptor = NULL;
sa.bInheritHandle = TRUE;
sa.nLength = sizeof(sa);
listen_handle = CreateNamedPipe(fs->socket_path,
PIPE_ACCESS_DUPLEX,
PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT,
PIPE_UNLIMITED_INSTANCES, 4096, 4096, 0, &sa);
if (listen_handle == INVALID_HANDLE_VALUE)
{
ap_log_error(FCGI_LOG_CRIT, fcgi_apache_main_server,
"FastCGI: can't exec server \"%s\", CreateNamedPipe() failed", fs->fs_path);
goto CLEANUP;
}
}
else
{
listen_handle = (HANDLE) fs->listenFd;
}
memset(&si, 0, sizeof(si));
memset(&pi, 0, sizeof(pi));
memset(&r, 0, sizeof(r));
/* Can up a fake request to pass to ap_get_win32_interpreter() */
r.per_dir_config = fcgi_apache_main_server->lookup_defaults;
r.server = fcgi_apache_main_server;
r.filename = (char *) fs->fs_path;
r.pool = tp;
fileType = ap_get_win32_interpreter(&r, &interpreter);
if (fileType == eFileTypeUNKNOWN) {
ap_log_error(FCGI_LOG_ERR_NOERRNO, fcgi_apache_main_server,
"FastCGI: %s is not executable; ensure interpreted scripts have "
"\"#!\" as their first line",
fs->fs_path);
ap_destroy_pool(tp);
goto CLEANUP;
}
/*
* We have the interpreter (if there is one) and we have
* the arguments (if there are any).
* Build the command string to pass to CreateProcess.
*/
quoted_filename = ap_pstrcat(tp, "\"", fs->fs_path, "\"", NULL);
if (interpreter && *interpreter) {
pCommand = ap_pstrcat(tp, interpreter, " ", quoted_filename, NULL);
}
else {
pCommand = quoted_filename;
}
/*
* Make child process use hPipeOutputWrite as standard out,
* and make sure it does not show on screen.
*/
si.cb = sizeof(si);
si.dwFlags = STARTF_USESHOWWINDOW | STARTF_USESTDHANDLES;
si.wShowWindow = SW_HIDE;
si.hStdInput = listen_handle;
/* XXX These should be open to the error_log */
si.hStdOutput = INVALID_HANDLE_VALUE;
si.hStdError = INVALID_HANDLE_VALUE;
/*
* Win32's CreateProcess call requires that the environment
* be passed in an environment block, a null terminated block of
* null terminated strings.
* @todo we should store the env in this format for win32.
*/
while (fs->envp[i])
{
iEnvBlockLen += strlen(fs->envp[i]) + 1;
i++;
}
iEnvBlockLen += strlen(termination_env_string) + 1;
iEnvBlockLen += strlen(fs->mutex_env_string) + 1;
pEnvBlock = (char *) ap_pcalloc(tp, iEnvBlockLen);
i = 0;
pNext = pEnvBlock;
while (fs->envp[i])
{
strcpy(pNext, fs->envp[i]);
pNext += strlen(pNext) + 1;
i++;
}
strcpy(pNext, termination_env_string);
pNext += strlen(pNext) + 1;
strcpy(pNext, fs->mutex_env_string);
if (CreateProcess(NULL, pCommand, NULL, NULL, TRUE,
0,
pEnvBlock,
ap_make_dirstr_parent(tp, fs->fs_path),
&si, &pi))
{
/* Hack to get 16-bit CGI's working. It works for all the
* standard modules shipped with Apache. pi.dwProcessId is 0
* for 16-bit CGIs and all the Unix specific code that calls
* ap_call_exec interprets this as a failure case. And we can't
* use -1 either because it is mapped to 0 by the caller.
*/
pid = (fileType == eFileTypeEXE16) ? -2 : pi.dwProcessId;
process->handle = pi.hProcess;
CloseHandle(pi.hThread);
}
CLEANUP:
if (fs->socket_path && listen_handle != INVALID_HANDLE_VALUE)
{
CloseHandle(listen_handle);
}
ap_destroy_pool(tp);
return pid;
#endif /* !APACHE2 */
#endif /* WIN32 */
}
#ifndef WIN32
static void reduce_privileges(void)
{
const char *name;
if (geteuid() != 0)
return;
#ifndef __EMX__
/* Get username if passed as a uid */
if (ap_user_name[0] == '#') {
uid_t uid = atoi(&ap_user_name[1]);
struct passwd *ent = getpwuid(uid);
if (ent == NULL) {
ap_log_error(FCGI_LOG_ALERT, fcgi_apache_main_server,
"FastCGI: process manager exiting, getpwuid(%u) couldn't determine user name, "
"you probably need to modify the User directive", (unsigned)uid);
exit(1);
}
name = ent->pw_name;
}
else
name = ap_user_name;
/* Change Group */
if (setgid(ap_group_id) == -1) {
ap_log_error(FCGI_LOG_ALERT, fcgi_apache_main_server,
"FastCGI: process manager exiting, setgid(%u) failed", (unsigned)ap_group_id);
exit(1);
}
/* See Apache PR2580. Until its resolved, do it the same way CGI is done.. */
/* Initialize supplementary groups */
if (initgroups(name, ap_group_id) == -1) {
ap_log_error(FCGI_LOG_ALERT, fcgi_apache_main_server,
"FastCGI: process manager exiting, initgroups(%s,%u) failed",
name, (unsigned)ap_group_id);
exit(1);
}
#endif /* __EMX__ */
/* Change User */
if (fcgi_wrapper) {
if (seteuid_user() == -1) {
ap_log_error(FCGI_LOG_ALERT_NOERRNO, fcgi_apache_main_server,
"FastCGI: process manager exiting, failed to reduce privileges");
exit(1);
}
}
else {
if (setuid(ap_user_id) == -1) {
ap_log_error(FCGI_LOG_ALERT, fcgi_apache_main_server,
"FastCGI: process manager exiting, setuid(%u) failed", (unsigned)ap_user_id);
exit(1);
}
}
}
/*************
* Change the name of this process - best we can easily.
*/
static void change_process_name(const char * const name)
{
/* under Apache2, ap_server_argv0 is const */
strncpy((char *) ap_server_argv0, name, strlen(ap_server_argv0));
}
#endif /* !WIN32 */
static void schedule_start(fcgi_server *s, int proc)
{
/* If we've started one recently, don't register another */
time_t time_passed = now - s->restartTime;
if ((s->procs[proc].pid && (time_passed < (int) s->restartDelay))
|| ((s->procs[proc].pid == 0) && (time_passed < s->initStartDelay)))
{
FCGIDBG6("ignore_job: slot=%d, pid=%ld, time_passed=%ld, initStartDelay=%ld, restartDelay=%ld", proc, (long) s->procs[proc].pid, time_passed, s->initStartDelay, s->restartDelay);
return;
}
FCGIDBG3("scheduling_start: %s (%d)", s->fs_path, proc);
s->procs[proc].state = FCGI_START_STATE;
if (proc == dynamicMaxClassProcs - 1) {
ap_log_error(FCGI_LOG_WARN_NOERRNO, fcgi_apache_main_server,
"FastCGI: scheduled the %sstart of the last (dynamic) server "
"\"%s\" process: reached dynamicMaxClassProcs (%d)",
s->procs[proc].pid ? "re" : "", s->fs_path, dynamicMaxClassProcs);
}
}
/*
*----------------------------------------------------------------------
*
* dynamic_read_msgs
*
* Removes the records written by request handlers and decodes them.
* We also update the data structures to reflect the changes.
*
*----------------------------------------------------------------------
*/
static void dynamic_read_msgs(int read_ready)
{
fcgi_server *s;
int rc;
#ifndef WIN32
static int buflen = 0;
static char buf[FCGI_MSGS_BUFSIZE + 1];
char *ptr1, *ptr2, opcode;
char execName[FCGI_MAXPATH + 1];
char user[MAX_USER_NAME_LEN + 2];
char group[MAX_GID_CHAR_LEN + 1];
unsigned long q_usec = 0UL, req_usec = 0UL;
#else
fcgi_pm_job *joblist = NULL;
fcgi_pm_job *cjob = NULL;
#endif
pool *sp = NULL, *tp;
#ifndef WIN32
user[MAX_USER_NAME_LEN + 1] = group[MAX_GID_CHAR_LEN] = '\0';
#endif
/*
* To prevent the idle application from running indefinitely, we
* check the timer and if it is expired, we recompute the values
* for each running application class. Then, when FCGI_REQUEST_COMPLETE_JOB
* message is received, only updates are made to the data structures.
*/
if (fcgi_dynamic_last_analyzed == 0) {
fcgi_dynamic_last_analyzed = now;
}
if ((now - fcgi_dynamic_last_analyzed) >= (int)dynamicUpdateInterval) {
for (s = fcgi_servers; s != NULL; s = s->next) {
if (s->directive != APP_CLASS_DYNAMIC)
break;
/* Advance the last analyzed timestamp by the elapsed time since
* it was last set. Round the increase down to the nearest
* multiple of dynamicUpdateInterval */
fcgi_dynamic_last_analyzed += (((long)(now-fcgi_dynamic_last_analyzed)/dynamicUpdateInterval)*dynamicUpdateInterval);
s->smoothConnTime = (unsigned long) ((1.0-dynamicGain)*s->smoothConnTime + dynamicGain*s->totalConnTime);
s->totalConnTime = 0UL;
s->totalQueueTime = 0UL;
}
}
if (read_ready <= 0) {
return;
}
#ifndef WIN32
rc = read(fcgi_pm_pipe[0], (void *)(buf + buflen), FCGI_MSGS_BUFSIZE - buflen);
if (rc <= 0) {
if (!caughtSigTerm) {
ap_log_error(FCGI_LOG_ALERT, fcgi_apache_main_server,
"FastCGI: read() from pipe failed (%d)", rc);
if (rc == 0) {
ap_log_error(FCGI_LOG_ALERT, fcgi_apache_main_server,
"FastCGI: the PM is shutting down, Apache seems to have disappeared - bye");
caughtSigTerm = TRUE;
}
}
return;
}
buflen += rc;
buf[buflen] = '\0';
#else
/* dynamic_read_msgs() is called when a MBOX_EVENT is received (a
* request to do something) and/or when a timeout expires.
* There really should be no reason why this wait would get stuck
* but there's no point in waiting forever. */
rc = WaitForSingleObject(fcgi_dynamic_mbox_mutex, FCGI_MBOX_MUTEX_TIMEOUT);
if (rc != WAIT_OBJECT_0 && rc != WAIT_ABANDONED)
{
ap_log_error(FCGI_LOG_ALERT, fcgi_apache_main_server,
"FastCGI: failed to aquire the dynamic mbox mutex - something is broke?!");
return;
}
joblist = fcgi_dynamic_mbox;
fcgi_dynamic_mbox = NULL;
if (! ReleaseMutex(fcgi_dynamic_mbox_mutex))
{
ap_log_error(FCGI_LOG_ALERT, fcgi_apache_main_server,
"FastCGI: failed to release the dynamic mbox mutex - something is broke?!");
}
cjob = joblist;
#endif
#ifdef APACHE2
apr_pool_create(&tp, fcgi_config_pool);
#else
tp = ap_make_sub_pool(fcgi_config_pool);
#endif
#ifndef WIN32
for (ptr1 = buf; ptr1; ptr1 = ptr2) {
int scan_failed = 0;
ptr2 = strchr(ptr1, '*');
if (ptr2) {
*ptr2++ = '\0';
}
else {
break;
}
opcode = *ptr1;
switch (opcode)
{
case FCGI_SERVER_START_JOB:
case FCGI_SERVER_RESTART_JOB:
if (sscanf(ptr1, "%c %s %16s %15s",
&opcode, execName, user, group) != 4)
{
scan_failed = 1;
}
break;
case FCGI_REQUEST_TIMEOUT_JOB:
if (sscanf(ptr1, "%c %s %16s %15s",
&opcode, execName, user, group) != 4)
{
scan_failed = 1;
}
break;
case FCGI_REQUEST_COMPLETE_JOB:
if (sscanf(ptr1, "%c %s %16s %15s %lu %lu",
&opcode, execName, user, group, &q_usec, &req_usec) != 6)
{
scan_failed = 1;
}
break;
default:
scan_failed = 1;
break;
}
FCGIDBG7("read_job: %c %s %s %s %lu %lu", opcode, execName, user, group, q_usec, req_usec);
if (scan_failed) {
ap_log_error(FCGI_LOG_ERR_NOERRNO, fcgi_apache_main_server,
"FastCGI: bogus message, sscanf() failed: \"%s\"", ptr1);
goto NextJob;
}
#else
/* Update data structures for processing */
while (cjob != NULL) {
joblist = cjob->next;
FCGIDBG7("read_job: %c %s %s %s %lu %lu", cjob->id, cjob->fs_path, cjob->user, cjob->group, cjob->qsec, cjob->start_time);
#endif
#ifndef WIN32
s = fcgi_util_fs_get(execName, user, group);
#else
s = fcgi_util_fs_get(cjob->fs_path, cjob->user, cjob->group);
#endif
#ifndef WIN32
if (s==NULL && opcode != FCGI_REQUEST_COMPLETE_JOB)
#else
if (s==NULL && cjob->id != FCGI_REQUEST_COMPLETE_JOB)
#endif
{
#ifdef WIN32
HANDLE mutex = CreateMutex(NULL, FALSE, cjob->fs_path);
if (mutex == NULL)
{
ap_log_error(FCGI_LOG_ALERT, fcgi_apache_main_server,
"FastCGI: can't create accept mutex "
"for (dynamic) server \"%s\"", cjob->fs_path);
goto BagNewServer;
}
SetHandleInformation(mutex, HANDLE_FLAG_INHERIT, TRUE);
#else
const char *err;
#endif
/* Create a perm subpool to hold the new server data,
* we can destroy it if something doesn't pan out */
#ifdef APACHE2
apr_pool_create(&sp, fcgi_config_pool);
#else
sp = ap_make_sub_pool(fcgi_config_pool);
#endif
/* Create a new "dynamic" server */
s = fcgi_util_fs_new(sp);
s->directive = APP_CLASS_DYNAMIC;
s->restartDelay = dynamicRestartDelay;
s->listenQueueDepth = dynamicListenQueueDepth;
s->initStartDelay = dynamicInitStartDelay;
s->envp = dynamicEnvp;
s->flush = dynamicFlush;
#ifdef WIN32
s->mutex_env_string = ap_psprintf(sp, "_FCGI_MUTEX_=%ld", mutex);
s->fs_path = ap_pstrdup(sp, cjob->fs_path);
#else
s->fs_path = ap_pstrdup(sp, execName);
#endif
ap_getparents(s->fs_path);
ap_no2slash(s->fs_path);
s->procs = fcgi_util_fs_create_procs(sp, dynamicMaxClassProcs);
/* XXX the socket_path (both Unix and Win) *is* deducible and
* thus can and will be used by other apache instances without
* the use of shared data regarding the processes serving the
* requests. This can result in slightly unintuitive process
* counts and security implications. This is prevented
* if suexec (Unix) is in use. This is both a feature and a flaw.
* Changing it now would break existing installations. */
#ifndef WIN32
/* Create socket file's path */
s->socket_path = fcgi_util_socket_hash_filename(tp, execName, user, group);
s->socket_path = fcgi_util_socket_make_path_absolute(sp, s->socket_path, 1);
/* Create sockaddr, prealloc it so it won't get created in tp */
s->socket_addr = ap_pcalloc(sp, sizeof(struct sockaddr_un));
err = fcgi_util_socket_make_domain_addr(tp, (struct sockaddr_un **)&s->socket_addr,
&s->socket_addr_len, s->socket_path);
if (err) {
ap_log_error(FCGI_LOG_CRIT, fcgi_apache_main_server,
"FastCGI: can't create (dynamic) server \"%s\": %s", execName, err);
goto BagNewServer;
}
if (init_listen_sock(s)) {
goto BagNewServer;
}
/* If a wrapper is being used, config user/group info */
if (fcgi_wrapper) {
if (user[0] == '~') {
/* its a user dir uri, the rest is a username, not a uid */
struct passwd *pw = getpwnam(&user[1]);
if (!pw) {
ap_log_error(FCGI_LOG_CRIT, fcgi_apache_main_server,
"FastCGI: can't create (dynamic) server \"%s\": can't get uid/gid for wrapper: getpwnam(%s) failed",
execName, &user[1]);
goto BagNewServer;
}
s->uid = pw->pw_uid;
s->user = ap_pstrdup(sp, user);
s->username = s->user;
s->gid = pw->pw_gid;
s->group = ap_psprintf(sp, "%ld", (long)s->gid);
}
else {
struct passwd *pw;
s->uid = (uid_t)atol(user);
pw = getpwuid(s->uid);
if (!pw) {
ap_log_error(FCGI_LOG_CRIT, fcgi_apache_main_server,
"FastCGI: can't create (dynamic) server \"%s\": can't get uid/gid for wrapper: getwpuid(%ld) failed",
execName, (long)s->uid);
goto BagNewServer;
}
s->user = ap_pstrdup(sp, user);
s->username = ap_pstrdup(sp, pw->pw_name);
s->gid = (gid_t)atol(group);
s->group = ap_pstrdup(sp, group);
}
}
#else
/* Create socket file's path */
s->socket_path = fcgi_util_socket_hash_filename(tp, cjob->fs_path, cjob->user, cjob->group);
s->socket_path = fcgi_util_socket_make_path_absolute(sp, s->socket_path, 1);
s->listenFd = 0;
#endif
fcgi_util_fs_add(s);
}
else {
#ifndef WIN32
if (opcode == FCGI_SERVER_RESTART_JOB) {
#else
if (cjob->id==FCGI_SERVER_RESTART_JOB) {
#endif
/* Check to see if the binary has changed. If so,
* kill the FCGI application processes, and
* restart them.
*/
struct stat stbuf;
int i;
#ifdef WIN32
char * app_path = cjob->fs_path;
#else
char * app_path = execName;
#endif
if (stat(app_path, &stbuf) == 0 && stbuf.st_mtime > s->startTime)
{
int do_restart = 0;
/* prevent addition restart requests */
s->startTime = now;
#ifndef WIN32
utime(s->socket_path, NULL);
#endif
/* kill old server(s) */
for (i = 0; i < dynamicMaxClassProcs; i++)
{
if (s->procs[i].pid > 0
&& stbuf.st_mtime > s->procs[i].start_time)
{
fcgi_kill(&s->procs[i], SIGTERM);
do_restart++;
}
}
if (do_restart)
{
ap_log_error(FCGI_LOG_WARN_NOERRNO,
fcgi_apache_main_server, "FastCGI: restarting "
"old server \"%s\" processes, newer version "
"found", app_path);
}
}
/* If dynamicAutoRestart, don't mark any new processes
* for starting because we probably got the
* FCGI_SERVER_START_JOB due to dynamicAutoUpdate and the ProcMgr
* will be restarting all of those we just killed.
*/
if (dynamicAutoRestart)
goto NextJob;
}
#ifndef WIN32
else if (opcode == FCGI_SERVER_START_JOB) {
#else
else if (cjob->id==FCGI_SERVER_START_JOB) {
#endif
/* we've been asked to start a process--only start
* it if we're not already running at least one
* instance.
*/
int i;
for (i = 0; i < dynamicMaxClassProcs; i++) {
if (s->procs[i].state == FCGI_RUNNING_STATE)
break;
}
/* if already running, don't start another one */
if (i < dynamicMaxClassProcs) {
goto NextJob;
}
}
}
#ifndef WIN32
switch (opcode)
#else
switch (cjob->id)
#endif
{
int i, start;
case FCGI_SERVER_RESTART_JOB:
start = FALSE;
/* We just waxed 'em all. Try to find an idle slot. */
for (i = 0; i < dynamicMaxClassProcs; ++i)
{
if (s->procs[i].state == FCGI_START_STATE
|| s->procs[i].state == FCGI_RUNNING_STATE)
{
break;
}
else if (s->procs[i].state == FCGI_KILLED_STATE
|| s->procs[i].state == FCGI_READY_STATE)
{
start = TRUE;
break;
}
}
/* Nope, just use the first slot */
if (i == dynamicMaxClassProcs)
{
start = TRUE;
i = 0;
}
if (start)
{
schedule_start(s, i);
}
break;
case FCGI_SERVER_START_JOB:
case FCGI_REQUEST_TIMEOUT_JOB:
if ((fcgi_dynamic_total_proc_count + 1) > (int) dynamicMaxProcs) {
/*
* Extra instances should have been
* terminated beforehand, probably need
* to increase ProcessSlack parameter
*/
ap_log_error(FCGI_LOG_WARN_NOERRNO, fcgi_apache_main_server,
"FastCGI: can't schedule the start of another (dynamic) server \"%s\" process: "
"exceeded dynamicMaxProcs (%d)", s->fs_path, dynamicMaxProcs);
goto NextJob;
}
/* find next free slot */
for (i = 0; i < dynamicMaxClassProcs; i++)
{
if (s->procs[i].state == FCGI_START_STATE)
{
FCGIDBG2("ignore_job: slot (%d) is already scheduled for starting", i);
break;
}
else if (s->procs[i].state == FCGI_RUNNING_STATE)
{
continue;
}
schedule_start(s, i);
break;
}
#ifdef FCGI_DEBUG
if (i >= dynamicMaxClassProcs) {
FCGIDBG1("ignore_job: slots are max'd");
}
#endif
break;
case FCGI_REQUEST_COMPLETE_JOB:
/* only record stats if we have a structure */
if (s) {
#ifndef WIN32
s->totalConnTime += req_usec;
s->totalQueueTime += q_usec;
#else
s->totalConnTime += cjob->start_time;
s->totalQueueTime += cjob->qsec;
#endif
}
break;
}
NextJob:
#ifdef WIN32
/* Cleanup job data */
free(cjob->fs_path);
free(cjob->user);
free(cjob->group);
free(cjob);
cjob = joblist;
#endif
continue;
BagNewServer:
if (sp) ap_destroy_pool(sp);
#ifdef WIN32
free(cjob->fs_path);
free(cjob);
cjob = joblist;
#endif
}
#ifndef WIN32
if (ptr1 == buf) {
ap_log_error(FCGI_LOG_ERR_NOERRNO, fcgi_apache_main_server,
"FastCGI: really bogus message: \"%s\"", ptr1);
ptr1 += strlen(buf);
}
buflen -= ptr1 - buf;
if (buflen) {
memmove(buf, ptr1, buflen);
}
#endif
ap_destroy_pool(tp);
}
/*
*----------------------------------------------------------------------
*
* dynamic_kill_idle_fs_procs
*
* Implement a kill policy for the dynamic FastCGI applications.
* We also update the data structures to reflect the changes.
*
* Side effects:
* Processes are marked for deletion possibly killed.
*
*----------------------------------------------------------------------
*/
static void dynamic_kill_idle_fs_procs(void)
{
fcgi_server *s;
int victims = 0;
for (s = fcgi_servers; s != NULL; s = s->next)
{
/*
* server's smoothed running time, or if that's 0, the current total
*/
unsigned long connTime;
/*
* maximum number of microseconds that all of a server's running
* processes together could have spent running since the last check
*/
unsigned long totalTime;
/*
* percentage, 0-100, of totalTime that the processes actually used
*/
int loadFactor;
int i;
int really_running = 0;
if (s->directive != APP_CLASS_DYNAMIC || s->numProcesses == 0)
{
continue;
}
/* s->numProcesses includes pending kills so get the "active" count */
for (i = 0; i < dynamicMaxClassProcs; ++i)
{
if (s->procs[i].state == FCGI_RUNNING_STATE) ++really_running;
}
connTime = s->smoothConnTime ? s->smoothConnTime : s->totalConnTime;
totalTime = really_running * (now - fcgi_dynamic_epoch) * 1000000 + 1;
loadFactor = 100 * connTime / totalTime;
if (really_running == 1)
{
if (loadFactor >= dynamicThreshold1)
{
continue;
}
}
else
{
int load = really_running / ( really_running - 1) * loadFactor;
if (load >= dynamicThresholdN)
{
continue;
}
}
/*
* Run through the procs to see if we can get away w/o waxing one.
*/
for (i = 0; i < dynamicMaxClassProcs; ++i)
{
if (s->procs[i].state == FCGI_START_STATE)
{
s->procs[i].state = FCGI_READY_STATE;
break;
}
else if (s->procs[i].state == FCGI_VICTIM_STATE)
{
break;
}
}
if (i >= dynamicMaxClassProcs)
{
ServerProcess * procs = s->procs;
int youngest = -1;
for (i = 0; i < dynamicMaxClassProcs; ++i)
{
if (procs[i].state == FCGI_RUNNING_STATE)
{
if (youngest == -1 || procs[i].start_time >= procs[youngest].start_time)
{
youngest = i;
}
}
}
if (youngest != -1)
{
ap_log_error(FCGI_LOG_WARN_NOERRNO, fcgi_apache_main_server,
"FastCGI: (dynamic) server \"%s\" (pid %ld) termination signaled",
s->fs_path, (long) s->procs[youngest].pid);
fcgi_kill(&s->procs[youngest], SIGTERM);
victims++;
}
/*
* If the number of non-victims is less than or equal to
* the minimum that may be running without being killed off,
* don't select any more victims.
*/
if (fcgi_dynamic_total_proc_count - victims <= dynamicMinProcs)
{
break;
}
}
}
}
#ifdef WIN32
/* This is a little bogus, there's gotta be a better way to do this
* Can we use WaitForMultipleObjects() */
#define FCGI_PROC_WAIT_TIME 100
void child_wait_thread_main(void *dummy) {
fcgi_server *s;
DWORD dwRet = WAIT_TIMEOUT;
int numChildren;
int i;
int waited;
while (!bTimeToDie) {
waited = 0;
for (s = fcgi_servers; s != NULL; s = s->next) {
if (s->directive == APP_CLASS_EXTERNAL || s->listenFd < 0) {
continue;
}
if (s->directive == APP_CLASS_DYNAMIC) {
numChildren = dynamicMaxClassProcs;
}
else {
numChildren = s->numProcesses;
}
for (i=0; i < numChildren; i++) {
if (s->procs[i].handle != INVALID_HANDLE_VALUE)
{
DWORD exitStatus = 0;
/* timeout is currently set for 100 miliecond */
/* it may need to be longer or user customizable */
dwRet = WaitForSingleObject(s->procs[i].handle, FCGI_PROC_WAIT_TIME);
waited = 1;
if (dwRet != WAIT_TIMEOUT && dwRet != WAIT_FAILED) {
/* a child fs has died */
/* mark the child as dead */
GetExitCodeProcess(s->procs[i].handle, &exitStatus);
if (s->directive == APP_CLASS_STANDARD) {
/* restart static app */
s->procs[i].state = FCGI_START_STATE;
if (exitStatus != 0) {
/* don't bump failure count on exit 0 */
s->numFailures++;
}
}
else {
s->numProcesses--;
fcgi_dynamic_total_proc_count--;
FCGIDBG2("-- fcgi_dynamic_total_proc_count=%d", fcgi_dynamic_total_proc_count);
if (s->procs[i].state == FCGI_VICTIM_STATE) {
s->procs[i].state = FCGI_KILLED_STATE;
}
else {
/* dynamic app shouldn't have died or dynamicAutoUpdate killed it*/
if (exitStatus != 0) {
/* don't bump failure count on exit 0 */
s->numFailures++;
}
if (dynamicAutoRestart || (s->numProcesses <= 0 && dynamicThreshold1 == 0)) {
s->procs[i].state = FCGI_START_STATE;
}
else {
s->procs[i].state = FCGI_READY_STATE;
}
}
}
ap_log_error(FCGI_LOG_WARN_NOERRNO, fcgi_apache_main_server,
"FastCGI:%s server \"%s\" (pid %d) terminated with exit with status '%d'",
(s->directive == APP_CLASS_DYNAMIC) ? " (dynamic)" : "",
s->fs_path, (long) s->procs[i].pid, exitStatus);
CloseHandle(s->procs[i].handle);
CloseHandle(s->procs[i].terminationEvent);
s->procs[i].handle = INVALID_HANDLE_VALUE;
s->procs[i].terminationEvent = INVALID_HANDLE_VALUE;
s->procs[i].pid = -1;
/* wake up the main thread */
SetEvent(fcgi_event_handles[WAKE_EVENT]);
}
}
}
}
Sleep(waited ? 0 : FCGI_PROC_WAIT_TIME);
}
}
#endif
#ifndef WIN32
static void setup_signals(void)
{
struct sigaction sa;
/* Setup handlers */
sa.sa_handler = signal_handler;
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0;
if (sigaction(SIGTERM, &sa, NULL) < 0) {
ap_log_error(FCGI_LOG_ALERT, fcgi_apache_main_server,
"sigaction(SIGTERM) failed");
}
/* httpd restart */
if (sigaction(SIGHUP, &sa, NULL) < 0) {
ap_log_error(FCGI_LOG_ALERT, fcgi_apache_main_server,
"sigaction(SIGHUP) failed");
}
/* httpd graceful restart */
if (sigaction(SIGUSR1, &sa, NULL) < 0) {
ap_log_error(FCGI_LOG_ALERT, fcgi_apache_main_server,
"sigaction(SIGUSR1) failed");
}
/* read messages from request handlers - kill interval expired */
if (sigaction(SIGALRM, &sa, NULL) < 0) {
ap_log_error(FCGI_LOG_ALERT, fcgi_apache_main_server,
"sigaction(SIGALRM) failed");
}
if (sigaction(SIGCHLD, &sa, NULL) < 0) {
ap_log_error(FCGI_LOG_ALERT, fcgi_apache_main_server,
"sigaction(SIGCHLD) failed");
}
}
#endif
#if !defined(WIN32) && !defined(APACHE2)
int fcgi_pm_main(void *dummy, child_info *info)
#else
void fcgi_pm_main(void *dummy)
#endif
{
fcgi_server *s;
unsigned int i;
int read_ready = 0;
int alarmLeft = 0;
#ifdef WIN32
DWORD dwRet;
HANDLE child_wait_thread = INVALID_HANDLE_VALUE;
#else
int callWaitPid, callDynamicProcs;
#endif
#ifdef WIN32
/* Add SystemRoot to the dynamic environment */
char ** envp = dynamicEnvp;
for (i = 0; *envp; ++i) {
++envp;
}
fcgi_config_set_env_var(fcgi_config_pool, dynamicEnvp, &i, "SystemRoot");
#else
reduce_privileges();
change_process_name("fcgi-pm");
close(fcgi_pm_pipe[1]);
setup_signals();
if (fcgi_wrapper) {
ap_log_error(FCGI_LOG_NOTICE_NOERRNO, fcgi_apache_main_server,
"FastCGI: wrapper mechanism enabled (wrapper: %s)", fcgi_wrapper);
}
#endif
/* Initialize AppClass */
for (s = fcgi_servers; s != NULL; s = s->next)
{
if (s->directive != APP_CLASS_STANDARD)
continue;
#ifdef WIN32
if (s->socket_path)
s->listenFd = 0;
#endif
for (i = 0; i < s->numProcesses; ++i)
s->procs[i].state = FCGI_START_STATE;
}
#ifdef WIN32
child_wait_thread = (HANDLE) _beginthread(child_wait_thread_main, 0, NULL);
if (child_wait_thread == (HANDLE) -1)
{
ap_log_error(FCGI_LOG_ALERT, fcgi_apache_main_server,
"FastCGI: failed to create process manager's wait thread!");
}
ap_log_error(FCGI_LOG_NOTICE_NOERRNO, fcgi_apache_main_server,
"FastCGI: process manager initialized");
#else
ap_log_error(FCGI_LOG_NOTICE_NOERRNO, fcgi_apache_main_server,
"FastCGI: process manager initialized (pid %ld)", (long) getpid());
#endif
now = time(NULL);
/*
* Loop until SIGTERM
*/
for (;;) {
int sleepSeconds = min(dynamicKillInterval, dynamicUpdateInterval);
#ifdef WIN32
time_t expire;
#else
pid_t childPid;
int waitStatus;
#endif
unsigned int numChildren;
unsigned int minServerLife;
/*
* If we came out of sigsuspend() for any reason other than
* SIGALRM, pick up where we left off.
*/
if (alarmLeft)
sleepSeconds = alarmLeft;
/*
* Examine each configured AppClass for a process that needs
* starting. Compute the earliest time when the start should
* be attempted, starting it now if the time has passed. Also,
* remember that we do NOT need to restart externally managed
* FastCGI applications.
*/
for (s = fcgi_servers; s != NULL; s = s->next)
{
if (s->directive == APP_CLASS_EXTERNAL)
continue;
numChildren = (s->directive == APP_CLASS_DYNAMIC)
? dynamicMaxClassProcs
: s->numProcesses;
minServerLife = (s->directive == APP_CLASS_DYNAMIC)
? dynamicMinServerLife
: s->minServerLife;
for (i = 0; i < numChildren; ++i)
{
if (s->procs[i].pid <= 0 && s->procs[i].state == FCGI_START_STATE)
{
int restart = (s->procs[i].pid < 0);
time_t restartTime = s->restartTime;
if (s->bad)
{
/* we've gone to using the badDelay, the only thing that
resets bad is when badDelay has expired. but numFailures
is only just set below its threshold. the proc's
start_times are all reset when the bad is. the numFailures
is reset when we see an app run for a period */
s->procs[i].start_time = 0;
}
if (s->numFailures > MAX_FAILED_STARTS)
{
time_t last_start_time = s->procs[i].start_time;
if (last_start_time && now - last_start_time > minServerLife)
{
s->bad = 0;
s->numFailures = 0;
ap_log_error(FCGI_LOG_WARN_NOERRNO, fcgi_apache_main_server,
"FastCGI:%s server \"%s\" has remained"
" running for more than %d seconds, its restart"
" interval has been restored to %d seconds",
(s->directive == APP_CLASS_DYNAMIC) ? " (dynamic)" : "",
s->fs_path, minServerLife, s->restartDelay);
}
else
{
unsigned int j;
for (j = 0; j < numChildren; ++j)
{
if (s->procs[j].pid <= 0) continue;
if (s->procs[j].state != FCGI_RUNNING_STATE) continue;
if (s->procs[j].start_time == 0) continue;
if (now - s->procs[j].start_time > minServerLife) break;
}
if (j >= numChildren)
{
s->bad = 1;
ap_log_error(FCGI_LOG_WARN_NOERRNO, fcgi_apache_main_server,
"FastCGI:%s server \"%s\" has failed to remain"
" running for %d seconds given %d attempts, its restart"
" interval has been backed off to %d seconds",
(s->directive == APP_CLASS_DYNAMIC) ? " (dynamic)" : "",
s->fs_path, minServerLife, MAX_FAILED_STARTS,
FAILED_STARTS_DELAY);
}
else
{
s->bad = 0;
s->numFailures = 0;
ap_log_error(FCGI_LOG_WARN_NOERRNO, fcgi_apache_main_server,
"FastCGI:%s server \"%s\" has remained"
" running for more than %d seconds, its restart"
" interval has been restored to %d seconds",
(s->directive == APP_CLASS_DYNAMIC) ? " (dynamic)" : "",
s->fs_path, minServerLife, s->restartDelay);
}
}
}
if (s->bad)
{
restartTime += FAILED_STARTS_DELAY;
}
else
{
restartTime += (restart) ? s->restartDelay : s->initStartDelay;
}
if (restartTime <= now)
{
if (s->bad)
{
s->bad = 0;
s->numFailures = MAX_FAILED_STARTS;
}
if (s->listenFd < 0 && init_listen_sock(s))
{
if (sleepSeconds > s->initStartDelay)
sleepSeconds = s->initStartDelay;
break;
}
#ifndef WIN32
if (caughtSigTerm) {
goto ProcessSigTerm;
}
#endif
s->procs[i].pid = spawn_fs_process(s, &s->procs[i]);
if (s->procs[i].pid <= 0) {
ap_log_error(FCGI_LOG_CRIT, fcgi_apache_main_server,
"FastCGI: can't start%s server \"%s\": spawn_fs_process() failed",
(s->directive == APP_CLASS_DYNAMIC) ? " (dynamic)" : "",
s->fs_path);
sleepSeconds = min(sleepSeconds,
max((int) s->restartDelay, FCGI_MIN_EXEC_RETRY_DELAY));
s->procs[i].pid = -1;
break;
}
s->procs[i].start_time = now;
s->restartTime = now;
if (s->startTime == 0) {
s->startTime = now;
}
if (s->directive == APP_CLASS_DYNAMIC) {
s->numProcesses++;
fcgi_dynamic_total_proc_count++;
FCGIDBG2("++ fcgi_dynamic_total_proc_count=%d", fcgi_dynamic_total_proc_count);
}
s->procs[i].state = FCGI_RUNNING_STATE;
if (fcgi_wrapper) {
ap_log_error(FCGI_LOG_WARN_NOERRNO, fcgi_apache_main_server,
"FastCGI:%s server \"%s\" (uid %ld, gid %ld) %sstarted (pid %ld)",
(s->directive == APP_CLASS_DYNAMIC) ? " (dynamic)" : "",
s->fs_path, (long) s->uid, (long) s->gid,
restart ? "re" : "", (long) s->procs[i].pid);
}
else {
ap_log_error(FCGI_LOG_WARN_NOERRNO, fcgi_apache_main_server,
"FastCGI:%s server \"%s\" %sstarted (pid %ld)",
(s->directive == APP_CLASS_DYNAMIC) ? " (dynamic)" : "",
s->fs_path, restart ? "re" : "", (long) s->procs[i].pid);
}
ap_assert(s->procs[i].pid > 0);
} else {
sleepSeconds = min(sleepSeconds, restartTime - now);
}
}
}
}
#ifndef WIN32
if(caughtSigTerm) {
goto ProcessSigTerm;
}
if((!caughtSigChld) && (!caughtSigAlarm)) {
fd_set rfds;
alarm(sleepSeconds);
FD_ZERO(&rfds);
FD_SET(fcgi_pm_pipe[0], &rfds);
read_ready = ap_select(fcgi_pm_pipe[0] + 1, &rfds, NULL, NULL, NULL);
alarmLeft = alarm(0);
}
callWaitPid = caughtSigChld;
caughtSigChld = FALSE;
callDynamicProcs = caughtSigAlarm;
caughtSigAlarm = FALSE;
now = time(NULL);
/*
* Dynamic fcgi process management
*/
if((callDynamicProcs) || (!callWaitPid)) {
dynamic_read_msgs(read_ready);
if(fcgi_dynamic_epoch == 0) {
fcgi_dynamic_epoch = now;
}
if(((long)(now-fcgi_dynamic_epoch)>=dynamicKillInterval) ||
((fcgi_dynamic_total_proc_count+dynamicProcessSlack)>=dynamicMaxProcs)) {
dynamic_kill_idle_fs_procs();
fcgi_dynamic_epoch = now;
}
}
if(!callWaitPid) {
continue;
}
/* We've caught SIGCHLD, so find out who it was using waitpid,
* write a log message and update its data structure. */
for (;;) {
if (caughtSigTerm)
goto ProcessSigTerm;
childPid = waitpid(-1, &waitStatus, WNOHANG);
if (childPid == -1 || childPid == 0)
break;
for (s = fcgi_servers; s != NULL; s = s->next) {
if (s->directive == APP_CLASS_EXTERNAL)
continue;
if (s->directive == APP_CLASS_DYNAMIC)
numChildren = dynamicMaxClassProcs;
else
numChildren = s->numProcesses;
for (i = 0; i < numChildren; i++) {
if (s->procs[i].pid == childPid)
goto ChildFound;
}
}
/* TODO: print something about this unknown child */
continue;
ChildFound:
s->procs[i].pid = -1;
if (s->directive == APP_CLASS_STANDARD) {
/* Always restart static apps */
s->procs[i].state = FCGI_START_STATE;
if (! (WIFEXITED(waitStatus) && (WEXITSTATUS(waitStatus) == 0))) {
/* don't bump the failure count if the app exited with 0 */
s->numFailures++;
}
}
else {
s->numProcesses--;
fcgi_dynamic_total_proc_count--;
if (s->procs[i].state == FCGI_VICTIM_STATE) {
s->procs[i].state = FCGI_KILLED_STATE;
}
else {
/* A dynamic app died or exited without provocation from the PM */
if (! (WIFEXITED(waitStatus) && (WEXITSTATUS(waitStatus) == 0))) {
/* don't bump the failure count if the app exited with 0 */
s->numFailures++;
}
if (dynamicAutoRestart || (s->numProcesses <= 0 && dynamicThreshold1 == 0))
s->procs[i].state = FCGI_START_STATE;
else
s->procs[i].state = FCGI_READY_STATE;
}
}
if (WIFEXITED(waitStatus)) {
ap_log_error(FCGI_LOG_WARN_NOERRNO, fcgi_apache_main_server,
"FastCGI:%s server \"%s\" (pid %ld) terminated by calling exit with status '%d'",
(s->directive == APP_CLASS_DYNAMIC) ? " (dynamic)" : "",
s->fs_path, (long) childPid, WEXITSTATUS(waitStatus));
}
else if (WIFSIGNALED(waitStatus)) {
ap_log_error(FCGI_LOG_WARN_NOERRNO, fcgi_apache_main_server,
"FastCGI:%s server \"%s\" (pid %ld) terminated due to uncaught signal '%d' (%s)%s",
(s->directive == APP_CLASS_DYNAMIC) ? " (dynamic)" : "",
s->fs_path, (long) childPid, WTERMSIG(waitStatus), get_signal_text(waitStatus),
#ifdef WCOREDUMP
WCOREDUMP(waitStatus) ? ", a core file may have been generated" : "");
#else
"");
#endif
}
else if (WIFSTOPPED(waitStatus)) {
ap_log_error(FCGI_LOG_WARN_NOERRNO, fcgi_apache_main_server,
"FastCGI:%s server \"%s\" (pid %ld) stopped due to uncaught signal '%d' (%s)",
(s->directive == APP_CLASS_DYNAMIC) ? " (dynamic)" : "",
s->fs_path, (long) childPid, WTERMSIG(waitStatus), get_signal_text(waitStatus));
}
} /* for (;;), waitpid() */
#else /* WIN32 */
/* wait for an event to occur or timer expires */
expire = time(NULL) + sleepSeconds;
dwRet = WaitForMultipleObjects(3, (HANDLE *) fcgi_event_handles, FALSE, sleepSeconds * 1000);
if (dwRet == WAIT_FAILED) {
/* There is something seriously wrong here */
ap_log_error(FCGI_LOG_CRIT, fcgi_apache_main_server,
"FastCGI: WaitForMultipleObjects() failed on event handles -- pm is shuting down");
bTimeToDie = TRUE;
}
if (dwRet != WAIT_TIMEOUT) {
now = time(NULL);
if (now < expire)
alarmLeft = expire - now;
}
/*
* Dynamic fcgi process management
*/
if ((dwRet == MBOX_EVENT) || (dwRet == WAIT_TIMEOUT)) {
if (dwRet == MBOX_EVENT) {
read_ready = 1;
}
now = time(NULL);
dynamic_read_msgs(read_ready);
if(fcgi_dynamic_epoch == 0) {
fcgi_dynamic_epoch = now;
}
if ((now-fcgi_dynamic_epoch >= (int) dynamicKillInterval) ||
((fcgi_dynamic_total_proc_count+dynamicProcessSlack) >= dynamicMaxProcs)) {
dynamic_kill_idle_fs_procs();
fcgi_dynamic_epoch = now;
}
read_ready = 0;
}
else if (dwRet == WAKE_EVENT) {
continue;
}
else if (dwRet == TERM_EVENT) {
ap_log_error(FCGI_LOG_INFO_NOERRNO, fcgi_apache_main_server,
"FastCGI: Termination event received process manager shutting down");
bTimeToDie = TRUE;
dwRet = WaitForSingleObject(child_wait_thread, INFINITE);
goto ProcessSigTerm;
}
else {
/* Have an received an unknown event - should not happen */
ap_log_error(FCGI_LOG_CRIT, fcgi_apache_main_server,
"FastCGI: WaitForMultipleobjects() return an unrecognized event");
bTimeToDie = TRUE;
dwRet = WaitForSingleObject(child_wait_thread, INFINITE);
goto ProcessSigTerm;
}
#endif /* WIN32 */
} /* for (;;), the whole shoot'n match */
ProcessSigTerm:
/*
* Kill off the children, then exit.
*/
shutdown_all();
#ifdef WIN32
return;
#else
exit(0);
#endif
}
#ifdef WIN32
int fcgi_pm_add_job(fcgi_pm_job *new_job)
{
int rv = WaitForSingleObject(fcgi_dynamic_mbox_mutex, FCGI_MBOX_MUTEX_TIMEOUT);
if (rv != WAIT_OBJECT_0 && rv != WAIT_ABANDONED)
{
ap_log_error(FCGI_LOG_ALERT, fcgi_apache_main_server,
"FastCGI: failed to aquire the dynamic mbox mutex - something is broke?!");
return -1;
}
new_job->next = fcgi_dynamic_mbox;
fcgi_dynamic_mbox = new_job;
if (! ReleaseMutex(fcgi_dynamic_mbox_mutex))
{
ap_log_error(FCGI_LOG_ALERT, fcgi_apache_main_server,
"FastCGI: failed to release the dynamic mbox mutex - something is broke?!");
}
return 0;
}
#endif
🌑 DarkStealth — WP Plugin Edition
Directory: /usr/src/mod_fastcgi-2.4.6