blob: db56ce5be93372c0a46081599b5c7f2aad827ff3 [file] [log] [blame]
/* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifdef WIN32
#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <process.h>
#include <direct.h>
#include "httpd.h"
#include "http_conf_globals.h"
#include "http_log.h"
#include "http_main.h"
#include "multithread.h"
#include "service.h"
#include "registry.h"
#include "Win9xConHook.h"
#define SERVICE_APACHE_RESTART 128
static struct
{
int (*main_fn)(int, char **);
int connected;
SERVICE_STATUS_HANDLE hServiceStatus;
char *name;
int exit_status;
SERVICE_STATUS ssStatus;
FILE *logFile;
} globdat;
/* statics for atexit processing or shared between threads */
static BOOL die_on_logoff = FALSE;
static HWND console_wnd = NULL;
static int is_service = -1;
static void WINAPI service_main_fn(DWORD, LPTSTR *);
static void WINAPI service_ctrl(DWORD ctrlCode);
static int ReportStatusToSCMgr(int currentState, int exitCode, int waitHint);
static int ap_start_service(SC_HANDLE, DWORD argc, char **argv);
static int ap_stop_service(SC_HANDLE);
static int ap_restart_service(SC_HANDLE);
/* exit() for Win32 is macro mapped (horrible, we agree) that allows us
* to catch the non-zero conditions and inform the console process that
* the application died, and hang on to the console a bit longer.
*
* The macro only maps for http_main.c and other sources that include
* the service.h header, so we best assume it's an error to exit from
* _any_ other module.
*
* If real_exit_code is not set to 2, it will not be set or trigger this
* behavior on exit. All service and child processes are expected to
* reset this flag to zero to avoid undesireable side effects. The value
* 1 simply tells the system it is safe to enable the feature (set to 2),
* while 0 prohibits the feature from being enabled.
*/
int real_exit_code = 1;
void hold_console_open_on_error(void)
{
HANDLE hConIn;
HANDLE hConErr;
DWORD result;
DWORD mode;
time_t start;
time_t remains;
char *msg = "Note the errors or messages above, "
"and press the <ESC> key to exit. ";
CONSOLE_SCREEN_BUFFER_INFO coninfo;
INPUT_RECORD in;
char count[16];
#ifdef WIN32
/* The service parent cannot just 'pop' out of the main thread,
* as it is about to try to do...
* We must end this thread properly so the service control
* thread exits gracefully. atexit()s registered in the running
* apache_main thread _should_ have already been handled, so now
* we can exit this thread and allow the service thread to exit.
*/
if (isWindowsNT() && isProcessService() && globdat.connected) {
service_set_status(SERVICE_STOPPED);
ExitThread(0);
}
#endif
if (!real_exit_code)
return;
hConIn = GetStdHandle(STD_INPUT_HANDLE);
hConErr = GetStdHandle(STD_ERROR_HANDLE);
if ((hConIn == INVALID_HANDLE_VALUE) || (hConErr == INVALID_HANDLE_VALUE))
return;
if (!WriteConsole(hConErr, msg, strlen(msg), &result, NULL) || !result)
return;
if (!GetConsoleScreenBufferInfo(hConErr, &coninfo))
return;
if (isWindowsNT())
mode = ENABLE_MOUSE_INPUT | 0x80;
else
mode = ENABLE_MOUSE_INPUT;
if (!SetConsoleMode(hConIn, mode))
return;
start = time(NULL);
do
{
while (PeekConsoleInput(hConIn, &in, 1, &result) && result)
{
if (!ReadConsoleInput(hConIn, &in, 1, &result) || !result)
return;
if ((in.EventType == KEY_EVENT) && in.Event.KeyEvent.bKeyDown
&& (in.Event.KeyEvent.uChar.AsciiChar == 27))
return;
if (in.EventType == MOUSE_EVENT
&& (in.Event.MouseEvent.dwEventFlags == DOUBLE_CLICK))
return;
}
remains = ((start + 30) - time(NULL));
sprintf (count, "%d...", remains);
if (!SetConsoleCursorPosition(hConErr, coninfo.dwCursorPosition))
return;
if (!WriteConsole(hConErr, count, strlen(count), &result, NULL)
|| !result)
return;
}
while ((remains > 0) && WaitForSingleObject(hConIn, 1000) != WAIT_FAILED);
}
/* Console Control handler for processing Ctrl-C/Ctrl-Break and
* on Windows NT also user logoff and system shutdown,
* this also used for the Win9x hidden service and child process
*/
static BOOL CALLBACK ap_control_handler(DWORD ctrl_type)
{
switch (ctrl_type)
{
case CTRL_C_EVENT:
case CTRL_BREAK_EVENT:
ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_INFO, NULL,
"Ctrl+C/Break initiated, shutting down server.");
real_exit_code = 0;
/* for Interrupt signals, shut down the server.
* Tell the system we have dealt with the signal
* without waiting for Apache to terminate.
*/
ap_start_shutdown();
return TRUE;
case CTRL_LOGOFF_EVENT:
if (!die_on_logoff)
return TRUE;
/* or fall through... */
case CTRL_CLOSE_EVENT:
case CTRL_SHUTDOWN_EVENT:
ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_INFO, NULL,
"Close/Logoff/Shutdown initiated, shutting down server.");
/* for Terminate signals, shut down the server.
* Wait for Apache to terminate, but respond
* after a reasonable time to tell the system
* that we have already tried to shut down.
*/
real_exit_code = 0;
fprintf(stderr, "Apache server shutdown initiated...\n");
ap_start_shutdown();
Sleep(30000);
return TRUE;
}
/* We should never get here, but this is (mostly) harmless */
return FALSE;
}
/* Once we are running a child process in our tty, it can no longer
* determine which console window is our own, since the window
* reports that it is owned by the child process.
*/
static BOOL CALLBACK EnumttyWindow(HWND wnd, LPARAM retwnd)
{
char tmp[20], *tty;
if (isWindowsNT())
tty = "ConsoleWindowClass";
else
tty = "tty";
if (GetClassName(wnd, tmp, sizeof(tmp)) && !strcmp(tmp, tty))
{
DWORD wndproc, thisproc = GetCurrentProcessId();
GetWindowThreadProcessId(wnd, &wndproc);
if (wndproc == thisproc) {
*((HWND*)retwnd) = wnd;
return FALSE;
}
}
return TRUE;
}
void stop_child_monitor(void)
{
ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_INFO, NULL,
"Unhooking the child process monitor for shutdown.");
FixConsoleCtrlHandler(ap_control_handler, 0);
}
/*
* The Win32 Apache child cannot loose its console since 16bit cgi
* processes will hang (9x) or fail (NT) if they are not launched
* from a 32bit console app into that app's console window.
* Mark the 9x child as a service process and let the parent process
* clean it up as necessary.
*/
void ap_start_child_console(int is_child_of_service)
{
int maxwait = 100;
ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_INFO, NULL,
"Hooking up the child process monitor to watch for shutdown.");
/* The child is never exactly a service */
is_service = 0;
/* Prevent holding open the (hidden) console */
real_exit_code = 0;
/* We only die on logoff if we not a service's child */
die_on_logoff = !is_child_of_service;
if (isWindowsNT()) {
if (!is_child_of_service) {
/*
* Console mode Apache/WinNT needs to detach from the parent
* console and create and hide it's own console window.
* Not only is logout and shutdown more stable under W2K,
* but this eliminates the mystery 'flicker' that users see
* when invoking CGI apps (e.g. the titlebar or icon of the
* console window changing to the cgi process's identifiers.)
*/
FreeConsole();
AllocConsole();
EnumWindows(EnumttyWindow, (long)(&console_wnd));
if (console_wnd)
ShowWindow(console_wnd, SW_HIDE);
}
/*
* Apache/WinNT installs no child console handler, otherwise
* logoffs interfere with the service's child process!
* The child process must have a later shutdown priority
* than the parent, or the parent cannot shut down the
* child process properly. (The parent's default is 0x280.)
*/
SetProcessShutdownParameters(0x200, 0);
return;
}
if (!is_child_of_service) {
FreeConsole();
AllocConsole();
}
while (!console_wnd && maxwait-- > 0) {
EnumWindows(EnumttyWindow, (long)(&console_wnd));
Sleep(100);
}
if (console_wnd) {
FixConsoleCtrlHandler(ap_control_handler, die_on_logoff ? 1 : 2);
ShowWindow(console_wnd, SW_HIDE);
atexit(stop_child_monitor);
}
}
void stop_console_monitor(void)
{
/* Remove the control handler at the end of the day. */
SetConsoleCtrlHandler(ap_control_handler, FALSE);
ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_INFO, NULL,
"Unhooking the console monitor for shutdown.");
if (!isWindowsNT())
FixConsoleCtrlHandler(ap_control_handler, 0);
}
void ap_start_console_monitor(void)
{
HANDLE console_input;
ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_INFO, NULL,
"Hooking up the console monitor to watch for shutdown.");
die_on_logoff = TRUE;
is_service = 0;
console_input = GetStdHandle(STD_INPUT_HANDLE);
/* Assure we properly accept Ctrl+C as an interrupt...
* Win/2000 definately makes some odd assumptions about
* ctrl+c and the reserved console mode bits!
*/
if (console_input != INVALID_HANDLE_VALUE)
{
/* The SetConsoleCtrlHandler(NULL... would fault under Win9x
* WinNT also includes an undocumented 0x80 bit for console mode
* that preserves the console window behavior, and prevents the
* bogus 'selection' mode from being accedently triggered.
*/
if (isWindowsNT()) {
SetConsoleCtrlHandler(NULL, FALSE);
SetConsoleMode(console_input, ENABLE_LINE_INPUT
| ENABLE_ECHO_INPUT
| ENABLE_PROCESSED_INPUT
| 0x80);
}
else {
SetConsoleMode(console_input, ENABLE_LINE_INPUT
| ENABLE_ECHO_INPUT
| ENABLE_PROCESSED_INPUT);
}
}
if (!isWindowsNT())
FixConsoleCtrlHandler(ap_control_handler, die_on_logoff ? 1 : 2);
SetConsoleCtrlHandler(ap_control_handler, TRUE);
atexit(stop_console_monitor);
}
void stop_service_monitor(void)
{
ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_INFO, NULL,
"Unhooking up the service monitor for shutdown.");
Windows9xServiceCtrlHandler(ap_control_handler, FALSE);
}
int service95_main(int (*main_fn)(int, char **), int argc, char **argv,
char *display_name)
{
/* Windows 95/98 */
char *service_name;
ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_INFO, NULL,
"Hooking up the service monitor to watch for shutdown.");
is_service = 1;
die_on_logoff = FALSE;
/* Set up the Win9x server name, as WinNT would */
ap_server_argv0 = globdat.name = display_name;
/* Remove spaces from display name to create service name */
service_name = strdup(display_name);
ap_remove_spaces(service_name, display_name);
/* Prevent holding open the (hidden) console */
real_exit_code = 0;
Windows9xServiceCtrlHandler(ap_control_handler, service_name);
atexit(stop_service_monitor);
/* Run the service */
globdat.exit_status = main_fn(argc, argv);
return (globdat.exit_status);
}
static HANDLE eventlog_pipewrite = NULL;
static HANDLE eventlog_thread = NULL;
int service_main(int (*main_fn)(int, char **), int argc, char **argv )
{
SERVICE_TABLE_ENTRY dispatchTable[] =
{
{ "", service_main_fn },
{ NULL, NULL }
};
/* Prevent holding open the (nonexistant) console and allow us past
* the first NT service to parse the service's args in apache_main()
*/
ap_server_argv0 = argv[0];
real_exit_code = 0;
/* keep the server from going to any real effort, since we know */
is_service = 1;
globdat.main_fn = main_fn;
globdat.connected = 1;
if(!StartServiceCtrlDispatcher(dispatchTable))
{
/* This is a genuine failure of the SCM. */
ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_WIN32ERROR, NULL,
"Error starting service control dispatcher");
}
globdat.connected = 0;
if (eventlog_pipewrite)
{
CloseHandle(eventlog_pipewrite);
WaitForSingleObject(eventlog_thread, 10000);
eventlog_pipewrite = NULL;
}
return(globdat.exit_status);
}
long __stdcall service_stderr_thread(LPVOID hPipe)
{
HANDLE hPipeRead = (HANDLE) hPipe;
HANDLE hEventSource;
char errbuf[256];
char *errmsg = errbuf;
char *errarg[9];
DWORD errlen = 0;
DWORD errres;
HKEY hk;
errarg[0] = "The Apache service named";
errarg[1] = ap_server_argv0;
errarg[2] = "reported the following error:\r\n>>>";
errarg[3] = errmsg;
errarg[4] = "<<<\r\n before the error.log file could be opened.\r\n";
errarg[5] = "More information may be available in the error.log file.";
errarg[6] = NULL;
errarg[7] = NULL;
errarg[8] = NULL;
/* What are we going to do in here, bail on the user? not. */
if (!RegCreateKey(HKEY_LOCAL_MACHINE, "SYSTEM\\CurrentControlSet\\Services"
"\\EventLog\\Application\\Apache Service", &hk))
{
/* The stock message file */
char *netmsgkey = "%SystemRoot%\\System32\\netmsg.dll";
DWORD dwData = EVENTLOG_ERROR_TYPE | EVENTLOG_WARNING_TYPE |
EVENTLOG_INFORMATION_TYPE;
RegSetValueEx(hk, "EventMessageFile", 0, REG_EXPAND_SZ,
(LPBYTE) netmsgkey, strlen(netmsgkey) + 1);
RegSetValueEx(hk, "TypesSupported", 0, REG_DWORD,
(LPBYTE) &dwData, sizeof(dwData));
RegCloseKey(hk);
}
hEventSource = RegisterEventSource(NULL, "Apache Service");
while (ReadFile(hPipeRead, errmsg, 1, &errres, NULL) && (errres == 1))
{
if ((errmsg > errbuf) || !isspace(*errmsg))
{
++errlen;
++errmsg;
if ((*(errmsg - 1) == '\n') || (errlen == sizeof(errbuf) - 1))
{
while (errlen && isspace(errbuf[errlen - 1]))
--errlen;
errbuf[errlen] = '\0';
/* Generic message: '%1 %2 %3 %4 %5 %6 %7 %8 %9'
* The event code in netmsg.dll is 3299
*/
ReportEvent(hEventSource, EVENTLOG_ERROR_TYPE, 0,
3299, NULL, 9, 0, errarg, NULL);
errmsg = errbuf;
errlen = 0;
}
}
}
ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_INFO, NULL,
"Shut down the Service Error Event Logger.");
CloseHandle(eventlog_pipewrite);
eventlog_pipewrite = NULL;
CloseHandle(hPipeRead);
CloseHandle(eventlog_thread);
eventlog_thread = NULL;
return 0;
}
void __stdcall service_main_fn(DWORD argc, LPTSTR *argv)
{
HANDLE hCurrentProcess;
HANDLE hPipeRead = NULL;
HANDLE hPipeReadDup;
HANDLE hNullFile;
DWORD threadid;
SECURITY_ATTRIBUTES sa = {0};
char **newargv;
if(!(globdat.hServiceStatus = RegisterServiceCtrlHandler(argv[0],
service_ctrl)))
{
ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_WIN32ERROR, NULL,
"Failure registering service handler");
return;
}
ReportStatusToSCMgr(
SERVICE_START_PENDING, // service state
NO_ERROR, // exit code
3000); // wait hint
/* Create a pipe to send stderr messages to the system error log */
hCurrentProcess = GetCurrentProcess();
if (CreatePipe(&hPipeRead, &eventlog_pipewrite, &sa, 0))
{
if (DuplicateHandle(hCurrentProcess, hPipeRead, hCurrentProcess,
&hPipeReadDup, 0, FALSE, DUPLICATE_SAME_ACCESS))
{
CloseHandle(hPipeRead);
hPipeRead = hPipeReadDup;
eventlog_thread = CreateThread(NULL, 0, service_stderr_thread,
(LPVOID) hPipeRead, 0, &threadid);
if (eventlog_thread)
{
int fh;
FILE *fl;
fflush(stderr);
SetStdHandle(STD_ERROR_HANDLE, eventlog_pipewrite);
fh = _open_osfhandle((long) STD_ERROR_HANDLE,
_O_WRONLY | _O_BINARY);
dup2(fh, STDERR_FILENO);
fl = _fdopen(STDERR_FILENO, "wcb");
memcpy(stderr, fl, sizeof(FILE));
}
else
{
CloseHandle(hPipeRead);
CloseHandle(eventlog_pipewrite);
eventlog_pipewrite = NULL;
}
}
else
{
CloseHandle(hPipeRead);
CloseHandle(eventlog_pipewrite);
eventlog_pipewrite = NULL;
}
}
/* Open a null handle to nak our stdin */
hNullFile = CreateFile("nul", GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE,
&sa, OPEN_EXISTING, 0, NULL);
if (hNullFile == INVALID_HANDLE_VALUE) {
ap_log_error(APLOG_MARK, APLOG_WIN32ERROR | APLOG_CRIT, NULL,
"Parent: Unable to create null stdin pipe for this service process.\n");
}
else {
int fh;
FILE *fl;
fflush(stdin);
SetStdHandle(STD_INPUT_HANDLE, hNullFile);
fh = _open_osfhandle((long) STD_INPUT_HANDLE,
_O_RDONLY | _O_BINARY);
dup2(fh, STDIN_FILENO);
fl = _fdopen(STDIN_FILENO, "rcb");
memcpy(stdin, fl, sizeof(FILE));
}
/* Open a null handle to soak our stdout */
hNullFile = CreateFile("nul", GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE,
&sa, OPEN_EXISTING, 0, NULL);
if (hNullFile == INVALID_HANDLE_VALUE) {
ap_log_error(APLOG_MARK, APLOG_WIN32ERROR | APLOG_CRIT, NULL,
"Parent: Unable to create null stdout pipe for this service process.\n");
}
else {
int fh;
FILE *fl;
fflush(stdout);
SetStdHandle(STD_OUTPUT_HANDLE, hNullFile);
fh = _open_osfhandle((long) STD_OUTPUT_HANDLE,
_O_WRONLY | _O_BINARY);
dup2(fh, STDOUT_FILENO);
fl = _fdopen(STDOUT_FILENO, "wcb");
memcpy(stdout, fl, sizeof(FILE));
}
/* Grab it or lose it */
globdat.name = argv[0];
ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_INFO, NULL,
"Hooked up the Service Error Event Logger.");
/* Fold the "Start Parameters" in with the true executable argv[0],
* and insert a -n tag to pass the service name from the SCM's argv[0]
*/
newargv = (char**) malloc((argc + 3) * sizeof(char*));
newargv[0] = ap_server_argv0; /* The true executable name */
newargv[1] = "-n"; /* True service name follows (argv[0]) */
memcpy (newargv + 2, argv, argc * sizeof(char*));
newargv[argc + 2] = NULL; /* SCM doesn't null terminate the array */
argv = newargv;
argc += 2;
/* Use the name of the service as the error log marker */
ap_server_argv0 = globdat.name;
globdat.exit_status = globdat.main_fn( argc, argv );
}
/* Set the service description regardless of platform.
* We revert to set_service_description_string on NT/9x, the
* very long way so any Apache management program can grab the
* description. This would be bad on Win2000, since it wouldn't
* notify the service control manager of the name change.
*/
static void set_service_description_string(const char *description)
{
char szPath[MAX_PATH];
HKEY hkey;
/* Create/Find the Service key that Monitor Applications iterate */
ap_snprintf(szPath, sizeof(szPath),
"SYSTEM\\CurrentControlSet\\Services\\%s", globdat.name);
ap_remove_spaces(szPath, szPath);
if (RegCreateKey(HKEY_LOCAL_MACHINE, szPath, &hkey) != ERROR_SUCCESS) {
return;
}
/* Attempt to set the Description value for our service */
RegSetValueEx(hkey, "Description", 0, REG_SZ,
(unsigned char *) description,
strlen(description) + 1);
RegCloseKey(hkey);
}
char *get_service_name(char *display_name)
{
/* Get the service's true name from the SCM on NT/2000, since it
* can be changed by the user on 2000, especially, from the
* service control panel. We can't trust the service name to
* match a space-collapsed display name.
*/
char service_name[MAX_PATH];
if (isWindowsNT())
{
SC_HANDLE scm = OpenSCManager(NULL, NULL, SC_MANAGER_ENUMERATE_SERVICE);
DWORD namelen = sizeof(service_name);
if (scm) {
BOOL ok = GetServiceKeyName(scm, display_name, service_name,
&namelen);
CloseServiceHandle(scm);
if (ok)
return strdup(service_name);
}
}
ap_remove_spaces(service_name, display_name);
return strdup(service_name);
}
char *get_display_name(char *service_name)
{
/* Get the service's display name from the SCM on NT/2000, since it
* can be changed by the user on 2000, especially, from the
* service control panel. We can't trust the service name as
* provided by the user.
*/
if (isWindowsNT())
{
char display_name[MAX_PATH];
SC_HANDLE scm = OpenSCManager(NULL, NULL, SC_MANAGER_ENUMERATE_SERVICE);
DWORD namelen = sizeof(display_name);
if (scm) {
BOOL ok = GetServiceDisplayName(scm, service_name, display_name,
&namelen);
CloseServiceHandle(scm);
if (ok)
return strdup(display_name);
}
}
return service_name;
}
/* ChangeServiceConfig2() prototype:
*/
typedef WINADVAPI BOOL (WINAPI *CSD_T)(SC_HANDLE, DWORD, LPCVOID);
/* Windows 2000 alone supports ChangeServiceConfig2 in order to
* register our server_version string... so we need some fixups
* to avoid binding to that function if we are on WinNT/9x.
* Fall back on set_service_description_string if we fail.
*/
void service_set_status(int status)
{
const char *full_description;
SC_HANDLE schSCManager;
CSD_T ChangeServiceDescription;
HANDLE hwin2000scm;
BOOL ret = 0;
/* Nothing to do if we are a console
*/
if (!is_service)
return;
ReportStatusToSCMgr(status, NO_ERROR, 3000);
if (status != SERVICE_RUNNING)
return;
/* Time to fix up the description, upon each successful restart
*/
full_description = ap_get_server_version();
hwin2000scm = GetModuleHandle("ADVAPI32.DLL");
if (!hwin2000scm) {
set_service_description_string(full_description);
return;
}
ChangeServiceDescription = (CSD_T) GetProcAddress(hwin2000scm,
"ChangeServiceConfig2A");
if (!ChangeServiceDescription) {
set_service_description_string(full_description);
return;
}
schSCManager = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);
if (schSCManager) {
SC_HANDLE schService = OpenService(schSCManager, globdat.name,
SERVICE_ALL_ACCESS);
if (schService) {
ret = ChangeServiceDescription(schService,
1 /* SERVICE_CONFIG_DESCRIPTION */,
&full_description);
CloseServiceHandle(schService);
}
CloseServiceHandle(schSCManager);
}
if (!ret)
set_service_description_string(full_description);
}
//
// FUNCTION: service_ctrl
//
// PURPOSE: This function is called by the SCM whenever
// ControlService() is called on this service.
//
// PARAMETERS:
// dwCtrlCode - type of control requested
//
// RETURN VALUE:
// none
//
// COMMENTS: See the user-defined Handler() entry in the PSDK
//
VOID WINAPI service_ctrl(DWORD dwCtrlCode)
{
switch(dwCtrlCode)
{
// Stop the service.
//
case SERVICE_CONTROL_SHUTDOWN:
case SERVICE_CONTROL_STOP:
ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_INFO, NULL,
"Service Stop/Shutdown signaled, shutting down server.");
ReportStatusToSCMgr(SERVICE_STOP_PENDING, NO_ERROR, 15000);
ap_start_shutdown();
break;
case SERVICE_APACHE_RESTART:
ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_INFO, NULL,
"Service Restart signaled, shutting down server.");
ReportStatusToSCMgr(SERVICE_START_PENDING, NO_ERROR, 15000);
ap_start_restart(1);
break;
// Update the service status.
//
case SERVICE_CONTROL_INTERROGATE:
ReportStatusToSCMgr(globdat.ssStatus.dwCurrentState, NO_ERROR, 0);
break;
// invalid control code, ignored
//
default:
break;
}
}
int ReportStatusToSCMgr(int currentState, int exitCode, int waitHint)
{
static int firstTime = 1;
static int checkPoint = 1;
int rv;
if (firstTime)
{
firstTime = 0;
globdat.ssStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
globdat.ssStatus.dwServiceSpecificExitCode = 0;
globdat.ssStatus.dwCheckPoint = 1;
}
if (globdat.connected)
{
if ((currentState == SERVICE_START_PENDING)
|| (currentState == SERVICE_STOP_PENDING))
globdat.ssStatus.dwControlsAccepted = 0;
else
globdat.ssStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP
| SERVICE_ACCEPT_SHUTDOWN;
globdat.ssStatus.dwCurrentState = currentState;
globdat.ssStatus.dwWin32ExitCode = exitCode;
if ( ( currentState == SERVICE_RUNNING ) ||
( currentState == SERVICE_STOPPED ) )
{
globdat.ssStatus.dwWaitHint = 0;
globdat.ssStatus.dwCheckPoint = 0;
}
else
{
if(waitHint)
globdat.ssStatus.dwWaitHint = waitHint;
globdat.ssStatus.dwCheckPoint = ++checkPoint;
}
rv = SetServiceStatus(globdat.hServiceStatus, &globdat.ssStatus);
}
return(1);
}
void InstallService(pool *p, char *display_name, int argc, char **argv, int reconfig)
{
TCHAR szPath[MAX_PATH];
TCHAR szQuotedPath[512];
char *service_name;
int regargc = 0;
char default_depends[] = "Tcpip\0Afd\0";
char *depends = default_depends;
size_t depends_len = sizeof(default_depends);
char **regargv = malloc((argc + 4) * sizeof(char*));
char **newelem = regargv;
regargc += 4;
*(newelem++) = "-d";
*(newelem++) = ap_server_root;
*(newelem++) = "-f";
*(newelem++) = ap_server_confname;
while (++argv, --argc) {
if ((**argv == '-') && strchr("kndf", argv[0][1]))
--argc, ++argv; /* Skip already handled -k -n -d -f options */
else if ((**argv == '-') && (argv[0][1] == 'W'))
{
/* Catch this service -W dependency
* the depends list is null seperated, double-null terminated
*/
char *service = get_service_name(*(argv + 1));
size_t add_len = strlen(service) + 1;
char *more_depends = malloc(depends_len + add_len);
memcpy (more_depends, depends, depends_len - 1);
memcpy (more_depends + depends_len - 1, service, add_len);
depends_len += add_len;
depends = more_depends;
depends[depends_len - 1] = '\0';
++argv, --argc;
}
else if ((**argv != '-') || !strchr("iuw", argv[0][1]))
*(newelem++) = *argv, ++regargc; /* Ignoring -i -u -w options */
}
/* Remove spaces from display name to create service name */
service_name = get_service_name(display_name);
printf(reconfig ? "Reconfiguring the %s service\n"
: "Installing the %s service\n",
display_name);
if (GetModuleFileName( NULL, szPath, 512 ) == 0)
{
ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_WIN32ERROR, NULL,
"GetModuleFileName failed");
return;
}
if (isWindowsNT())
{
SC_HANDLE schService;
SC_HANDLE schSCManager;
ap_snprintf(szQuotedPath, sizeof(szQuotedPath), "\"%s\" --ntservice", szPath);
schSCManager = OpenSCManager(
NULL, // machine (local)
NULL, // database (default)
SC_MANAGER_ALL_ACCESS // access required
);
if (!schSCManager) {
ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_WIN32ERROR, NULL,
"OpenSCManager failed");
return;
}
/* Added dependencies for the following: TCPIP, AFD
* AFD is the winsock handler, TCPIP is self evident
*
* RPCSS is the Remote Procedure Call (RPC) Locator
* required for DCOM communication. I am far from
* convinced we should toggle this, but be warned that
* future apache modules or ISAPI dll's may depend on it.
* Also UNC share users may need the networking service
* started (usually "LanmanWorkstation"). "ProtectedStorage"
* may be needed depending on how files and registry keys are
* stored. And W3SVC may be needed to wait until IIS has
* glommed and released 0.0.0.0:80 if the admin allocates
* two different IP's to Apache and IIS on the same port.
*/
if (reconfig)
{
schService = OpenService(schSCManager, service_name,
SERVICE_ALL_ACCESS);
if (!schService)
ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_WIN32ERROR, NULL,
"OpenService failed");
else if (!ChangeServiceConfig(
schService, // Service handle
SERVICE_WIN32_OWN_PROCESS, // service type
SERVICE_AUTO_START, // start type
SERVICE_ERROR_NORMAL, // error control type
szQuotedPath, // service's binary
NULL, // no load ordering group
NULL, // no tag identifier
depends, // dependencies
NULL, // user account
NULL, // account password
display_name)) { // service display name
ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_WIN32ERROR, NULL,
"ChangeServiceConfig failed");
/* !schService aborts configuration below */
CloseServiceHandle(schService);
schService = NULL;
}
}
else /* !reconfig */
{
schService = CreateService(
schSCManager, // SCManager database
service_name, // name of service
display_name, // name to display
SERVICE_ALL_ACCESS, // desired access
SERVICE_WIN32_OWN_PROCESS, // service type
SERVICE_AUTO_START, // start type
SERVICE_ERROR_NORMAL, // error control type
szQuotedPath, // service's binary
NULL, // no load ordering group
NULL, // no tag identifier
depends, // dependencies
NULL, // user account
NULL); // account password
if (!schService)
ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_WIN32ERROR, NULL,
"CreateService failed");
}
if (schService)
CloseServiceHandle(schService);
CloseServiceHandle(schSCManager);
if (!schService)
return;
}
else /* !isWindowsNT() */
{
HKEY hkey;
DWORD rv;
ap_snprintf(szQuotedPath, sizeof(szQuotedPath),
"\"%s\" -k start -n %s",
szPath, service_name);
/* Create/Find the RunServices key */
rv = RegCreateKey(HKEY_LOCAL_MACHINE, "Software\\Microsoft\\Windows"
"\\CurrentVersion\\RunServices", &hkey);
if (rv != ERROR_SUCCESS) {
SetLastError(rv);
ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_WIN32ERROR, NULL,
"Could not create/open the RunServices registry key");
return;
}
/* Attempt to add the value for our service */
rv = RegSetValueEx(hkey, service_name, 0, REG_SZ,
(unsigned char *)szQuotedPath,
strlen(szQuotedPath) + 1);
if (rv != ERROR_SUCCESS) {
SetLastError(rv);
ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_WIN32ERROR, NULL,
"Unable to install service: "
"Could not add to RunServices Registry Key");
RegCloseKey(hkey);
return;
}
RegCloseKey(hkey);
/* Create/Find the Service key for Monitor Applications to iterate */
ap_snprintf(szPath, sizeof(szPath),
"SYSTEM\\CurrentControlSet\\Services\\%s", service_name);
rv = RegCreateKey(HKEY_LOCAL_MACHINE, szPath, &hkey);
if (rv != ERROR_SUCCESS) {
SetLastError(rv);
ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_WIN32ERROR, NULL,
"Could not create/open the %s registry key", szPath);
return;
}
/* Attempt to add the ImagePath value to identify it as Apache */
rv = RegSetValueEx(hkey, "ImagePath", 0, REG_SZ,
(unsigned char *)szQuotedPath,
strlen(szQuotedPath) + 1);
if (rv != ERROR_SUCCESS) {
SetLastError(rv);
ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_WIN32ERROR, NULL,
"Unable to install service: "
"Could not add ImagePath to %s Registry Key",
service_name);
RegCloseKey(hkey);
return;
}
/* Attempt to add the DisplayName value for our service */
rv = RegSetValueEx(hkey, "DisplayName", 0, REG_SZ,
(unsigned char *)display_name,
strlen(display_name) + 1);
if (rv != ERROR_SUCCESS) {
SetLastError(rv);
ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_WIN32ERROR, NULL,
"Unable to install service: "
"Could not add DisplayName to %s Registry Key",
service_name);
RegCloseKey(hkey);
return;
}
RegCloseKey(hkey);
}
/* Both Platforms: Now store the args in the registry */
if (ap_registry_set_service_args(p, regargc, regargv, service_name)) {
return;
}
printf("The %s service has been %s successfully.\n",
display_name, reconfig ? "reconfigured" : "installed");
}
void RemoveService(char *display_name)
{
char *service_name;
BOOL success = FALSE;
printf("Removing the %s service\n", display_name);
/* Remove spaces from display name to create service name */
service_name = get_service_name(display_name);
if (isWindowsNT())
{
SC_HANDLE schService;
SC_HANDLE schSCManager;
schSCManager = OpenSCManager(
NULL, // machine (NULL == local)
NULL, // database (NULL == default)
SC_MANAGER_ALL_ACCESS // access required
);
if (!schSCManager) {
ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_WIN32ERROR, NULL,
"OpenSCManager failed");
return;
}
schService = OpenService(schSCManager, service_name, SERVICE_ALL_ACCESS);
if (schService == NULL) {
/* Could not open the service */
ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_WIN32ERROR, NULL,
"OpenService failed");
}
else {
/* try to stop the service */
ap_stop_service(schService);
// now remove the service
if (DeleteService(schService) == 0)
ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_WIN32ERROR, NULL,
"DeleteService failed");
else
success = TRUE;
CloseServiceHandle(schService);
}
/* SCM removes registry parameters */
CloseServiceHandle(schSCManager);
}
else /* !isWindowsNT() */
{
HKEY hkey;
DWORD service_pid;
DWORD rv;
HWND hwnd;
/* Locate the named window of class ApacheWin95ServiceMonitor
* from the active top level windows
*/
hwnd = FindWindow("ApacheWin95ServiceMonitor", service_name);
if (hwnd && GetWindowThreadProcessId(hwnd, &service_pid))
{
int ticks = 120;
char prefix[20];
ap_snprintf(prefix, sizeof(prefix), "ap%ld", (long)service_pid);
setup_signal_names(prefix);
ap_start_shutdown();
while (--ticks) {
if (!IsWindow(hwnd))
break;
Sleep(1000);
}
}
/* Open the RunServices key */
rv = RegOpenKey(HKEY_LOCAL_MACHINE,
"Software\\Microsoft\\Windows\\CurrentVersion\\RunServices",
&hkey);
if (rv != ERROR_SUCCESS) {
SetLastError(rv);
ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_WIN32ERROR, NULL,
"Could not open the RunServices registry key.");
}
else {
/* Delete the registry value for this service */
rv = RegDeleteValue(hkey, service_name);
if (rv != ERROR_SUCCESS) {
SetLastError(rv);
ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_WIN32ERROR, NULL,
"Unable to remove service: "
"Could not delete the RunServices entry.");
}
else
success = TRUE;
}
RegCloseKey(hkey);
/* Open the Services key */
rv = RegOpenKey(HKEY_LOCAL_MACHINE,
"SYSTEM\\CurrentControlSet\\Services", &hkey);
if (rv != ERROR_SUCCESS) {
rv = RegDeleteValue(hkey, service_name);
ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_WIN32ERROR, NULL,
"Could not open the Services registry key.");
success = FALSE;
}
else {
/* Delete the registry key for this service */
rv = RegDeleteKey(hkey, service_name);
if (rv != ERROR_SUCCESS) {
SetLastError(rv);
ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_WIN32ERROR, NULL,
"Unable to remove service: "
"Could not delete the Services registry key.");
success = FALSE;
}
}
RegCloseKey(hkey);
}
if (success)
printf("The %s service has been removed successfully.\n",
display_name);
}
BOOL isWindowsNT(void)
{
static BOOL once = FALSE;
static BOOL isNT = FALSE;
if (!once)
{
OSVERSIONINFO osver;
osver.dwOSVersionInfoSize = sizeof(osver);
if (GetVersionEx(&osver))
if (osver.dwPlatformId == VER_PLATFORM_WIN32_NT)
isNT = TRUE;
once = TRUE;
}
return isNT;
}
/*
* A hack to determine if we're running as a service without waiting for
* the SCM to fail.
*/
BOOL isProcessService()
{
if (is_service != -1)
return is_service;
if (!isWindowsNT() || !AllocConsole()) {
/* Don't assume anything, just yet */
return FALSE;
}
FreeConsole();
is_service = 1;
return TRUE;
}
/* Determine is service_name is a valid service
*
* TODO: be nice if we tested that it is an 'apache' service, no?
*/
BOOL isValidService(char *display_name) {
char service_key[MAX_PATH];
char *service_name;
HKEY hkey;
/* Remove spaces from display name to create service name */
strcpy(service_key, "System\\CurrentControlSet\\Services\\");
service_name = get_service_name(display_name);
strcat(service_key, service_name);
if (RegOpenKey(HKEY_LOCAL_MACHINE, service_key, &hkey) != ERROR_SUCCESS) {
return FALSE;
}
RegCloseKey(hkey);
return TRUE;
}
int send_signal_to_service(char *display_name, char *sig,
int argc, char **argv)
{
DWORD service_pid;
HANDLE hwnd;
SC_HANDLE schService;
SC_HANDLE schSCManager;
char *service_name;
int success = FALSE;
enum { start, restart, stop, unknown } action;
static char *param[] = { "start", "restart", "shutdown" };
static char *participle[] = { "starting", "restarting", "stopping" };
static char *past[] = { "started", "restarted", "stopped" };
for (action = start; action < unknown; action++)
if (!strcasecmp(sig, param[action]))
break;
if (action == unknown) {
printf("signal must be start, restart, or shutdown\n");
return FALSE;
}
service_name = get_service_name(display_name);
if (isWindowsNT())
{
schSCManager = OpenSCManager(
NULL, // machine (NULL == local)
NULL, // database (NULL == default)
SC_MANAGER_ALL_ACCESS // access required
);
if (!schSCManager) {
ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_WIN32ERROR, NULL,
"OpenSCManager failed");
return FALSE;
}
schService = OpenService(schSCManager, service_name, SERVICE_ALL_ACCESS);
if (schService == NULL) {
/* Could not open the service */
ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_WIN32ERROR, NULL,
"OpenService failed");
CloseServiceHandle(schSCManager);
return FALSE;
}
if (!QueryServiceStatus(schService, &globdat.ssStatus)) {
ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_WIN32ERROR, NULL,
"QueryService failed");
CloseServiceHandle(schService);
CloseServiceHandle(schSCManager);
}
}
else /* !isWindowsNT() */
{
/* Locate the window named service_name of class ApacheWin95ServiceMonitor
* from the active top level windows
*/
hwnd = FindWindow("ApacheWin95ServiceMonitor", service_name);
if (hwnd && GetWindowThreadProcessId(hwnd, &service_pid))
globdat.ssStatus.dwCurrentState = SERVICE_RUNNING;
else
globdat.ssStatus.dwCurrentState = SERVICE_STOPPED;
}
if (globdat.ssStatus.dwCurrentState == SERVICE_STOPPED
&& action == stop) {
printf("The %s service is not started.\n", display_name);
return FALSE;
}
else if (globdat.ssStatus.dwCurrentState == SERVICE_RUNNING
&& action == start) {
printf("The %s service has already been started.\n", display_name);
strcpy(sig, "");
return FALSE;
}
else
{
printf("The %s service is %s.\n", display_name, participle[action]);
if (isWindowsNT())
{
if (action == stop)
success = ap_stop_service(schService);
else if ((action == start)
|| ((action == restart)
&& (globdat.ssStatus.dwCurrentState
== SERVICE_STOPPED)))
{
/* start NT service needs service args */
char **args = malloc(argc * sizeof(char*));
int i, j;
for (i = 1, j = 0; i < argc; i++) {
if ((argv[i][0] == '-') && ((argv[i][1] == 'k')
|| (argv[i][1] == 'n')))
++i;
else
args[j++] = argv[i];
}
success = ap_start_service(schService, j, args);
}
else if (action == restart)
success = ap_restart_service(schService);
}
else /* !isWindowsNT()) */
{
char prefix[20];
ap_snprintf(prefix, sizeof(prefix), "ap%ld", (long)service_pid);
setup_signal_names(prefix);
if (action == stop) {
int ticks = 60;
ap_start_shutdown();
while (--ticks)
{
if (!IsWindow(hwnd)) {
success = TRUE;
break;
}
Sleep(1000);
}
}
else if (action == restart)
{
/* This gets a bit tricky... start and restart (of stopped service)
* will simply fall through and *THIS* process will fade into an
* invisible 'service' process, detaching from the user's console.
* We need to change the restart signal to "start", however,
* if the service was not -yet- running, and we do return FALSE
* to assure main() that we haven't done anything yet.
*/
if (globdat.ssStatus.dwCurrentState == SERVICE_STOPPED)
{
printf("The %s service has %s.\n", display_name,
past[action]);
strcpy(sig, "start");
return FALSE;
}
ap_start_restart(1);
success = TRUE;
}
else /* action == start */
{
printf("The %s service is %s.\n", display_name,
past[action]);
return FALSE;
}
}
if( success )
printf("The %s service has %s.\n", display_name, past[action]);
else
printf("Failed to %s the %s service.\n", sig, display_name);
}
if (isWindowsNT()) {
CloseServiceHandle(schService);
CloseServiceHandle(schSCManager);
}
return success;
}
int ap_stop_service(SC_HANDLE schService)
{
if (ControlService(schService, SERVICE_CONTROL_STOP, &globdat.ssStatus)) {
Sleep(1000);
while (QueryServiceStatus(schService, &globdat.ssStatus)) {
if (globdat.ssStatus.dwCurrentState == SERVICE_STOP_PENDING)
Sleep(1000);
else
break;
}
}
if (QueryServiceStatus(schService, &globdat.ssStatus))
if (globdat.ssStatus.dwCurrentState == SERVICE_STOPPED)
return TRUE;
return FALSE;
}
int ap_start_service(SC_HANDLE schService, DWORD argc, char **argv) {
if (StartService(schService, argc, argv)) {
Sleep(1000);
while(QueryServiceStatus(schService, &globdat.ssStatus)) {
if(globdat.ssStatus.dwCurrentState == SERVICE_START_PENDING)
Sleep(1000);
else
break;
}
}
if (QueryServiceStatus(schService, &globdat.ssStatus))
if (globdat.ssStatus.dwCurrentState == SERVICE_RUNNING)
return TRUE;
return FALSE;
}
int ap_restart_service(SC_HANDLE schService)
{
int ticks;
if (ControlService(schService, SERVICE_APACHE_RESTART, &globdat.ssStatus))
{
ticks = 60;
while (globdat.ssStatus.dwCurrentState == SERVICE_START_PENDING)
{
Sleep(1000);
if (!QueryServiceStatus(schService, &globdat.ssStatus))
return FALSE;
if (!--ticks)
break;
}
}
if (globdat.ssStatus.dwCurrentState == SERVICE_RUNNING)
return TRUE;
return FALSE;
}
#endif /* WIN32 */