diff options
Diffstat (limited to 'rubbos/app/httpd-2.0.64/server/mpm/winnt')
-rw-r--r-- | rubbos/app/httpd-2.0.64/server/mpm/winnt/Win9xConHook.c | 697 | ||||
-rw-r--r-- | rubbos/app/httpd-2.0.64/server/mpm/winnt/Win9xConHook.def | 10 | ||||
-rw-r--r-- | rubbos/app/httpd-2.0.64/server/mpm/winnt/Win9xConHook.dsp | 103 | ||||
-rw-r--r-- | rubbos/app/httpd-2.0.64/server/mpm/winnt/Win9xConHook.h | 57 | ||||
-rw-r--r-- | rubbos/app/httpd-2.0.64/server/mpm/winnt/child.c | 1167 | ||||
-rw-r--r-- | rubbos/app/httpd-2.0.64/server/mpm/winnt/mpm.h | 39 | ||||
-rw-r--r-- | rubbos/app/httpd-2.0.64/server/mpm/winnt/mpm_default.h | 80 | ||||
-rw-r--r-- | rubbos/app/httpd-2.0.64/server/mpm/winnt/mpm_winnt.c | 1728 | ||||
-rw-r--r-- | rubbos/app/httpd-2.0.64/server/mpm/winnt/mpm_winnt.h | 114 | ||||
-rw-r--r-- | rubbos/app/httpd-2.0.64/server/mpm/winnt/nt_eventlog.c | 175 | ||||
-rw-r--r-- | rubbos/app/httpd-2.0.64/server/mpm/winnt/service.c | 1346 |
11 files changed, 5516 insertions, 0 deletions
diff --git a/rubbos/app/httpd-2.0.64/server/mpm/winnt/Win9xConHook.c b/rubbos/app/httpd-2.0.64/server/mpm/winnt/Win9xConHook.c new file mode 100644 index 00000000..a352dd1b --- /dev/null +++ b/rubbos/app/httpd-2.0.64/server/mpm/winnt/Win9xConHook.c @@ -0,0 +1,697 @@ +/* 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 + +/* + * Win9xConHook.dll - a hook proc to clean up Win95/98 console behavior. + * + * It is well(?) documented by Microsoft that the Win9x HandlerRoutine + * hooked by the SetConsoleCtrlHandler never receives the CTRL_CLOSE_EVENT, + * CTRL_LOGOFF_EVENT or CTRL_SHUTDOWN_EVENT signals. + * + * It is possible to have a second window to monitor the WM_ENDSESSION + * message, but the close button still fails.. + * + * There is a 16bit polling method for the close window option, but this + * is CPU intensive and requires thunking. + * + * Attempts to subclass the 'tty' console fail, since that message thread + * is actually owned by the 16 bit winoldap.mod process, although the + * window reports it is owned by the process/thread of the console app. + * + * Win9xConHook is thunks the WM_CLOSE and WM_ENDSESSION messages, + * first through a window hook procedure in the winoldap context, into + * a subclass WndProc, and on to a second hidden monitor window in the + * console application's context that dispatches them to the console app's + * registered HandlerRoutine. + */ + +/* This debugging define turns on output to COM1, although you better init + * the port first (even using hyperterm). It's the only way to catch the + * goings on within system logoff/shutdown. + * #define DBG 1 + */ + +#include <windows.h> + +/* Variables used within any process context: + * hookwndmsg is a shared message to send Win9xConHook signals + * origwndprop is a wndprop atom to store the orig wndproc of the tty + * hookwndprop is a wndprop atom to store the hwnd of the hidden child + * is_service reminds us to unmark this process on the way out + */ +static UINT hookwndmsg = 0; +static LPCTSTR origwndprop; +static LPCTSTR hookwndprop; +static BOOL is_service = 0; +//static HMODULE hmodThis = NULL; + +/* Variables used within the tty processes' context: + * is_tty flags this process; -1 == unknown, 1 == if tty, 0 == if not + * hw_tty is the handle of the top level tty in this process context + * is_subclassed is toggled to assure DllMain removes the subclass on unload + * hmodLock is there to try and prevent this dll from being unloaded if the + * hook is removed while we are subclassed + */ +static int is_tty = -1; +static HWND hwtty = NULL; +static BOOL is_subclassed = 0; + +// This simply causes a gpfault the moment it tries to FreeLibrary within +// the subclass procedure ... not good. +//static HMODULE hmodLock = NULL; + +/* Variables used within the service or console app's context: + * hmodHook is the instance handle of this module for registering the hooks + * hhkGetMessage is the hook handle for catching Posted messages + * hhkGetMessage is the hook handle for catching Sent messages + * monitor_hwnd is the invisible window that handles our tty messages + * the tty_info strucure is used to pass args into the hidden window's thread + */ +static HMODULE hmodHook = NULL; +static HHOOK hhkGetMessage; +//static HHOOK hhkCallWndProc; +static HWND monitor_hwnd = NULL; + +typedef struct { + PHANDLER_ROUTINE phandler; + HINSTANCE instance; + HWND parent; + INT type; + LPCSTR name; +} tty_info; + +/* These are the GetWindowLong offsets for the hidden window's internal info + * gwltty_phandler is the address of the app's HandlerRoutine + * gwltty_ttywnd is the tty this hidden window will handle messages from + */ +#define gwltty_phandler 0 +#define gwltty_ttywnd 4 + +/* Forward declaration prototypes for internal functions + */ +static BOOL CALLBACK EnumttyWindow(HWND wnd, LPARAM retwnd); +static LRESULT WINAPI RegisterWindows9xService(BOOL set_service); +static LRESULT CALLBACK ttyConsoleCtrlWndProc(HWND hwnd, UINT msg, + WPARAM wParam, LPARAM lParam); +static DWORD WINAPI ttyConsoleCtrlThread(LPVOID tty); +static LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, + WPARAM wParam, LPARAM lParam); +static int HookProc(int hc, HWND *hwnd, UINT *msg, + WPARAM *wParam, LPARAM *lParam); +#ifdef DBG +static VOID DbgPrintf(LPTSTR fmt, ...); +#endif + + +/* DllMain is invoked by every process in the entire system that is hooked + * by our window hooks, notably the tty processes' context, and by the user + * who wants tty messages (the app). Keep it light and simple. + */ +BOOL __declspec(dllexport) APIENTRY DllMain(HINSTANCE hModule, ULONG ulReason, + LPVOID pctx) +{ + if (ulReason == DLL_PROCESS_ATTACH) + { + //hmodThis = hModule; + if (!hookwndmsg) { + origwndprop = MAKEINTATOM(GlobalAddAtom("Win9xConHookOrigProc")); + hookwndprop = MAKEINTATOM(GlobalAddAtom("Win9xConHookThunkWnd")); + hookwndmsg = RegisterWindowMessage("Win9xConHookMsg"); + } +#ifdef DBG +// DbgPrintf("H ProcessAttach:%8.8x\r\n", +// GetCurrentProcessId()); +#endif + } + else if ( ulReason == DLL_PROCESS_DETACH ) + { +#ifdef DBG +// DbgPrintf("H ProcessDetach:%8.8x\r\n", GetCurrentProcessId()); +#endif + if (monitor_hwnd) + SendMessage(monitor_hwnd, WM_DESTROY, 0, 0); + if (is_subclassed) + SendMessage(hwtty, hookwndmsg, 0, (LPARAM)hwtty); + if (hmodHook) + { + if (hhkGetMessage) { + UnhookWindowsHookEx(hhkGetMessage); + hhkGetMessage = NULL; + } + //if (hhkCallWndProc) { + // UnhookWindowsHookEx(hhkCallWndProc); + // hhkCallWndProc = NULL; + //} + FreeLibrary(hmodHook); + hmodHook = NULL; + } + if (is_service) + RegisterWindows9xService(FALSE); + if (hookwndmsg) { + GlobalDeleteAtom((ATOM)origwndprop); + GlobalDeleteAtom((ATOM)hookwndprop); + hookwndmsg = 0; + } + } + return TRUE; +} + + +/* This group of functions are provided for the service/console app + * to register itself a HandlerRoutine to accept tty or service messages + */ + + +/* Exported function that creates a Win9x 'service' via a hidden window, + * that notifies the process via the HandlerRoutine messages. + */ +BOOL __declspec(dllexport) WINAPI Windows9xServiceCtrlHandler( + PHANDLER_ROUTINE phandler, + LPCSTR name) +{ + /* If we have not yet done so */ + FreeConsole(); + + if (name) + { + DWORD tid; + HANDLE hThread; + /* NOTE: this is static so the module can continue to + * access these args while we go on to other things + */ + static tty_info tty; + tty.instance = GetModuleHandle(NULL); + tty.phandler = phandler; + tty.parent = NULL; + tty.name = name; + tty.type = 2; + RegisterWindows9xService(TRUE); + hThread = CreateThread(NULL, 0, ttyConsoleCtrlThread, + (LPVOID)&tty, 0, &tid); + if (hThread) + { + CloseHandle(hThread); + return TRUE; + } + } + else /* remove */ + { + if (monitor_hwnd) + SendMessage(monitor_hwnd, WM_DESTROY, 0, 0); + RegisterWindows9xService(FALSE); + return TRUE; + } + return FALSE; +} + + +/* Exported function that registers a HandlerRoutine to accept missing + * Win9x CTRL_EVENTs from the tty window, as NT does without a hassle. + * If add is 1 or 2, register the handler, if 2 also mark it as a service. + * If add is 0 deregister the handler, and unmark if a service + */ +BOOL __declspec(dllexport) WINAPI FixConsoleCtrlHandler( + PHANDLER_ROUTINE phandler, + INT add) +{ + HWND parent; + + if (add) + { + HANDLE hThread; + DWORD tid; + /* NOTE: this is static so the module can continue to + * access these args while we go on to other things + */ + static tty_info tty; + EnumWindows(EnumttyWindow, (LPARAM)&parent); + if (!parent) { +#ifdef DBG + DbgPrintf("A EnumttyWindow failed (%d)\r\n", GetLastError()); +#endif + return FALSE; + } + tty.instance = GetModuleHandle(NULL); + tty.phandler = phandler; + tty.parent = parent; + tty.type = add; + if (add == 2) { + tty.name = "ttyService"; + RegisterWindows9xService(TRUE); + } + else + tty.name = "ttyMonitor"; + hThread = CreateThread(NULL, 0, ttyConsoleCtrlThread, + (LPVOID)&tty, 0, &tid); + if (!hThread) + return FALSE; + CloseHandle(hThread); + hmodHook = LoadLibrary("Win9xConHook.dll"); + if (hmodHook) + { + hhkGetMessage = SetWindowsHookEx(WH_GETMESSAGE, + (HOOKPROC)GetProcAddress(hmodHook, "GetMsgProc"), hmodHook, 0); + //hhkCallWndProc = SetWindowsHookEx(WH_CALLWNDPROC, + // (HOOKPROC)GetProcAddress(hmodHook, "CallWndProc"), hmodHook, 0); + } + return TRUE; + } + else /* remove */ + { + if (monitor_hwnd) { + SendMessage(monitor_hwnd, WM_DESTROY, 0, 0); + } + if (hmodHook) + { + if (hhkGetMessage) { + UnhookWindowsHookEx(hhkGetMessage); + hhkGetMessage = NULL; + } + //if (hhkCallWndProc) { + // UnhookWindowsHookEx(hhkCallWndProc); + // hhkCallWndProc = NULL; + //} + FreeLibrary(hmodHook); + hmodHook = NULL; + } + if (is_service) + RegisterWindows9xService(FALSE); + return TRUE; + } + return FALSE; +} + + +/* The following internal helpers are only used within the app's context + */ + +/* ttyConsoleCreateThread is the process that runs within the user app's + * context. It creates and pumps the messages of a hidden monitor window, + * watching for messages from the system, or the associated subclassed tty + * window. Things can happen in our context that can't be done from the + * tty's context, and visa versa, so the subclass procedure and this hidden + * window work together to make it all happen. + */ +static DWORD WINAPI ttyConsoleCtrlThread(LPVOID tty) +{ + WNDCLASS wc; + MSG msg; + wc.style = CS_GLOBALCLASS; + wc.lpfnWndProc = ttyConsoleCtrlWndProc; + wc.cbClsExtra = 0; + wc.cbWndExtra = 8; + wc.hInstance = NULL; + wc.hIcon = NULL; + wc.hCursor = NULL; + wc.hbrBackground = NULL; + wc.lpszMenuName = NULL; + if (((tty_info*)tty)->parent) + wc.lpszClassName = "ttyConHookChild"; + else + wc.lpszClassName = "ApacheWin95ServiceMonitor"; + + if (!RegisterClass(&wc)) { +#ifdef DBG + DbgPrintf("A proc %8.8x Error creating class %s (%d)\r\n", + GetCurrentProcessId(), wc.lpszClassName, GetLastError()); +#endif + return 0; + } + + /* Create an invisible window */ + monitor_hwnd = CreateWindow(wc.lpszClassName, ((tty_info*)tty)->name, + WS_OVERLAPPED & ~WS_VISIBLE, + CW_USEDEFAULT, CW_USEDEFAULT, + CW_USEDEFAULT, CW_USEDEFAULT, + NULL, NULL, + ((tty_info*)tty)->instance, tty); + + if (!monitor_hwnd) { +#ifdef DBG + DbgPrintf("A proc %8.8x Error creating window %s %s (%d)\r\n", + GetCurrentProcessId(), wc.lpszClassName, + ((tty_info*)tty)->name, GetLastError()); +#endif + return 0; + } + + while (GetMessage(&msg, NULL, 0, 0)) + { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + + /* Tag again as deleted, just in case we missed WM_DESTROY */ + monitor_hwnd = NULL; + return 0; +} + + +/* This is the WndProc procedure for our invisible window. + * When our subclasssed tty window receives the WM_CLOSE, WM_ENDSESSION, + * or WM_QUERYENDSESSION messages, the message is dispatched to our hidden + * window (this message process), and we call the installed HandlerRoutine + * that was registered by the app. + */ +static LRESULT CALLBACK ttyConsoleCtrlWndProc(HWND hwnd, UINT msg, + WPARAM wParam, LPARAM lParam) +{ + if (msg == WM_CREATE) + { + tty_info *tty = (tty_info*)(((LPCREATESTRUCT)lParam)->lpCreateParams); + SetWindowLong(hwnd, gwltty_phandler, (LONG)tty->phandler); + SetWindowLong(hwnd, gwltty_ttywnd, (LONG)tty->parent); +#ifdef DBG + DbgPrintf("A proc %8.8x created %8.8x %s for tty wnd %8.8x\r\n", + GetCurrentProcessId(), hwnd, + tty->name, tty->parent); +#endif + if (tty->parent) { + SetProp(tty->parent, hookwndprop, hwnd); + PostMessage(tty->parent, hookwndmsg, + tty->type, (LPARAM)tty->parent); + } + return 0; + } + else if (msg == WM_DESTROY) + { + HWND parent = (HWND)GetWindowLong(hwnd, gwltty_ttywnd); +#ifdef DBG + DbgPrintf("A proc %8.8x destroyed %8.8x ttyConHookChild\r\n", + GetCurrentProcessId(), hwnd); +#endif + if (parent) { + RemoveProp(parent, hookwndprop); + SendMessage(parent, hookwndmsg, 0, (LPARAM)parent); + } + monitor_hwnd = NULL; + } + else if (msg == WM_CLOSE) + { + PHANDLER_ROUTINE phandler = + (PHANDLER_ROUTINE)GetWindowLong(hwnd, gwltty_phandler); + LRESULT rv = phandler(CTRL_CLOSE_EVENT); +#ifdef DBG + DbgPrintf("A proc %8.8x invoked CTRL_CLOSE_EVENT " + "returning %d\r\n", + GetCurrentProcessId(), rv); +#endif + if (rv) + return !rv; + } + else if ((msg == WM_QUERYENDSESSION) || (msg == WM_ENDSESSION)) + { + if (lParam & ENDSESSION_LOGOFF) + { + PHANDLER_ROUTINE phandler = + (PHANDLER_ROUTINE)GetWindowLong(hwnd, gwltty_phandler); + LRESULT rv = phandler(CTRL_LOGOFF_EVENT); +#ifdef DBG + DbgPrintf("A proc %8.8x invoked CTRL_LOGOFF_EVENT " + "returning %d\r\n", + GetCurrentProcessId(), rv); +#endif + if (rv) + return ((msg == WM_QUERYENDSESSION) ? rv : !rv); + } + else + { + PHANDLER_ROUTINE phandler = + (PHANDLER_ROUTINE)GetWindowLong(hwnd, gwltty_phandler); + LRESULT rv = phandler(CTRL_SHUTDOWN_EVENT); +#ifdef DBG + DbgPrintf("A proc %8.8x invoked CTRL_SHUTDOWN_EVENT " + "returning %d\r\n", GetCurrentProcessId(), rv); +#endif + if (rv) + return ((msg == WM_QUERYENDSESSION) ? rv : !rv); + } + } + return (DefWindowProc(hwnd, msg, wParam, lParam)); +} + + +/* The following internal helpers are invoked by the hooked tty and our app + */ + + +/* Register or deregister the current process as a Windows9x style service. + * Experience shows this call is ignored across processes, so the second + * arg to RegisterServiceProcess (process group id) is effectively useless. + */ +static LRESULT WINAPI RegisterWindows9xService(BOOL set_service) +{ + static HINSTANCE hkernel; + static DWORD (WINAPI *register_service_process)(DWORD, DWORD) = NULL; + BOOL rv; + + if (set_service == is_service) + return 1; + +#ifdef DBG + DbgPrintf("R %s proc %8.8x as a service\r\n", + set_service ? "installing" : "removing", + GetCurrentProcessId()); +#endif + + if (!register_service_process) + { + /* Obtain a handle to the kernel library */ + hkernel = LoadLibrary("KERNEL32.DLL"); + if (!hkernel) + return 0; + + /* Find the RegisterServiceProcess function */ + register_service_process = (DWORD (WINAPI *)(DWORD, DWORD)) + GetProcAddress(hkernel, "RegisterServiceProcess"); + if (register_service_process == NULL) { + FreeLibrary(hkernel); + return 0; + } + } + + /* Register this process as a service */ + rv = register_service_process(0, set_service != FALSE); + if (rv) + is_service = set_service; + + if (!is_service) + { + /* Unload the kernel library */ + FreeLibrary(hkernel); + register_service_process = NULL; + } + return rv; +} + + +/* + * This function only works when this process is the active process + * (e.g. once it is running a child process, it can no longer determine + * which console window is its own.) + */ +static BOOL CALLBACK EnumttyWindow(HWND wnd, LPARAM retwnd) +{ + char tmp[8]; + 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; +} + + +/* The remaining code all executes --in the tty's own process context-- + * + * That means special attention must be paid to what it's doing... + */ + +/* Subclass message process for the tty window + * + * This code -handles- WM_CLOSE, WM_ENDSESSION and WM_QUERYENDSESSION + * by dispatching them to the window identified by the hookwndprop + * property atom set against our window. Messages are then dispatched + * to origwndprop property atom we set against the window when we + * injected this subclass. This trick did not work with simply a hook. + */ +static LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, + WPARAM wParam, LPARAM lParam) +{ + WNDPROC origproc = (WNDPROC) GetProp(hwnd, origwndprop); + if (!origproc) + return 0; + + if (msg == WM_NCDESTROY + || (msg == hookwndmsg && !LOWORD(wParam) && (HWND)lParam == hwnd)) + { + if (is_subclassed) { +#ifdef DBG + DbgPrintf("W proc %08x hwnd:%08x Subclass removed\r\n", + GetCurrentProcessId(), hwnd); +#endif + if (is_service) + RegisterWindows9xService(FALSE); + SetWindowLong(hwnd, GWL_WNDPROC, (LONG)origproc); + RemoveProp(hwnd, origwndprop); + RemoveProp(hwnd, hookwndprop); + is_subclassed = FALSE; + //if (hmodLock) + // FreeLibrary(hmodLock); + //hmodLock = NULL; + } + } + else if (msg == WM_CLOSE || msg == WM_ENDSESSION + || msg == WM_QUERYENDSESSION) + { + HWND child = (HWND)GetProp(hwnd, hookwndprop); + if (child) { +#ifdef DBG + DbgPrintf("W proc %08x hwnd:%08x forwarded msg:%d\r\n", + GetCurrentProcessId(), hwnd, msg); +#endif + return SendMessage(child, msg, wParam, lParam); + } + } + return CallWindowProc(origproc, hwnd, msg, wParam, lParam); +} + + +/* HookProc, once installed, is responsible for subclassing the system + * tty windows. It generally does nothing special itself, since + * research indicates that it cannot deal well with the messages we are + * interested in, that is, WM_CLOSE, WM_QUERYSHUTDOWN and WM_SHUTDOWN + * of the tty process. + * + * Respond and subclass only when a WM_NULL is received by the window. + */ +int HookProc(int hc, HWND *hwnd, UINT *msg, WPARAM *wParam, LPARAM *lParam) +{ + if (is_tty == -1 && *hwnd) + { + char ttybuf[8]; + HWND htty; + hwtty = *hwnd; + while (htty = GetParent(hwtty)) + hwtty = htty; + is_tty = (GetClassName(hwtty, ttybuf, sizeof(ttybuf)) + && !strcmp(ttybuf, "tty")); +#ifdef DBG + if (is_tty) + DbgPrintf("H proc %08x tracking hwnd %08x\r\n", + GetCurrentProcessId(), hwtty); +#endif + } + + if (*msg == hookwndmsg && *wParam && *lParam == (LPARAM)hwtty && is_tty) + { + WNDPROC origproc = (WNDPROC)GetWindowLong(hwtty, GWL_WNDPROC); + //char myname[MAX_PATH]; + //if (GetModuleFileName(hmodThis, myname, sizeof(myname))) + // hmodLock = LoadLibrary(myname); + SetProp(hwtty, origwndprop, origproc); + SetWindowLong(hwtty, GWL_WNDPROC, (LONG)WndProc); + is_subclassed = TRUE; +#ifdef DBG + DbgPrintf("H proc %08x hwnd:%08x Subclassed\r\n", + GetCurrentProcessId(), hwtty); +#endif + if (LOWORD(*wParam) == 2) + RegisterWindows9xService(TRUE); + } + + return -1; +} + + +/* + * PostMessage Hook: + */ +LRESULT __declspec(dllexport) CALLBACK GetMsgProc(INT hc, WPARAM wParam, + LPARAM lParam) +{ + PMSG pmsg; + + pmsg = (PMSG)lParam; + + if (pmsg) { + int rv = HookProc(hc, &pmsg->hwnd, &pmsg->message, + &pmsg->wParam, &pmsg->lParam); + if (rv != -1) + return rv; + } + /* + * CallNextHookEx apparently ignores the hhook argument, so pass NULL + */ + return CallNextHookEx(NULL, hc, wParam, lParam); +} + + +/* + * SendMessage Hook: + */ +LRESULT __declspec(dllexport) CALLBACK CallWndProc(INT hc, WPARAM wParam, + LPARAM lParam) +{ + PCWPSTRUCT pcwps = (PCWPSTRUCT)lParam; + + if (pcwps) { + int rv = HookProc(hc, &pcwps->hwnd, &pcwps->message, + &pcwps->wParam, &pcwps->lParam); + if (rv != -1) + return rv; + } + /* + * CallNextHookEx apparently ignores the hhook argument, so pass NULL + */ + return CallNextHookEx(NULL, hc, wParam, lParam); +} + + +#ifdef DBG +VOID DbgPrintf( + LPTSTR fmt, + ... + ) +{ + static HANDLE mutex; + va_list marker; + TCHAR szBuf[256]; + DWORD t; + HANDLE gDbgOut; + + va_start(marker, fmt); + wvsprintf(szBuf, fmt, marker); + va_end(marker); + + if (!mutex) + mutex = CreateMutex(NULL, FALSE, "Win9xConHookDbgOut"); + WaitForSingleObject(mutex, INFINITE); + gDbgOut = CreateFile("COM1", GENERIC_READ | GENERIC_WRITE, 0, + NULL, OPEN_EXISTING, FILE_FLAG_WRITE_THROUGH, NULL); + WriteFile(gDbgOut, szBuf, strlen(szBuf), &t, NULL); + CloseHandle(gDbgOut); + ReleaseMutex(mutex); +} +#endif + +#endif /* WIN32 */ diff --git a/rubbos/app/httpd-2.0.64/server/mpm/winnt/Win9xConHook.def b/rubbos/app/httpd-2.0.64/server/mpm/winnt/Win9xConHook.def new file mode 100644 index 00000000..85ec1664 --- /dev/null +++ b/rubbos/app/httpd-2.0.64/server/mpm/winnt/Win9xConHook.def @@ -0,0 +1,10 @@ +LIBRARY Win9xConHook + +EXETYPE WINDOWS + +EXPORTS + DllMain + GetMsgProc + CallWndProc + FixConsoleCtrlHandler + Windows9xServiceCtrlHandler diff --git a/rubbos/app/httpd-2.0.64/server/mpm/winnt/Win9xConHook.dsp b/rubbos/app/httpd-2.0.64/server/mpm/winnt/Win9xConHook.dsp new file mode 100644 index 00000000..77cbacfc --- /dev/null +++ b/rubbos/app/httpd-2.0.64/server/mpm/winnt/Win9xConHook.dsp @@ -0,0 +1,103 @@ +# Microsoft Developer Studio Project File - Name="Win9xConHook" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102 + +CFG=Win9xConHook - Win32 Release +!MESSAGE This is not a valid makefile. To build this project using NMAKE, +!MESSAGE use the Export Makefile command and run +!MESSAGE +!MESSAGE NMAKE /f "Win9xConHook.mak". +!MESSAGE +!MESSAGE You can specify a configuration when running NMAKE +!MESSAGE by defining the macro CFG on the command line. For example: +!MESSAGE +!MESSAGE NMAKE /f "Win9xConHook.mak" CFG="Win9xConHook - Win32 Release" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "Win9xConHook - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library") +!MESSAGE "Win9xConHook - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName "" +# PROP Scc_LocalPath "" +CPP=cl.exe +MTL=midl.exe +RSC=rc.exe + +!IF "$(CFG)" == "Win9xConHook - Win32 Release" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir ".\Release" +# PROP BASE Intermediate_Dir ".\Release" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "Release" +# PROP Intermediate_Dir "Release" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /MD /W3 /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /FD /c +# ADD CPP /nologo /MD /W3 /O2 /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /D "SHARED_MODULE" /Fd"Release\Win9xConHook" /FD /c +# ADD BASE MTL /nologo /D "NDEBUG" /win32 +# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32 +# ADD BASE RSC /l 0x809 /d "NDEBUG" +# ADD RSC /l 0x809 /d "NDEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib /nologo /subsystem:windows /dll /incremental:no /base:"0x1c0f0000" +# ADD LINK32 kernel32.lib user32.lib gdi32.lib /nologo /subsystem:windows /dll /incremental:no /base:"0x1c0f0000" /opt:ref + +!ELSEIF "$(CFG)" == "Win9xConHook - Win32 Debug" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "Debug" +# PROP BASE Intermediate_Dir "Debug" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "Debug" +# PROP Intermediate_Dir "Debug" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /MDd /W3 /EHsc /Zi /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /FD /c +# ADD CPP /nologo /MDd /W3 /EHsc /Zi /Od /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /D "SHARED_MODULE" /Fd"Debug\Win9xConHook" /FD /c +# ADD BASE MTL /nologo /D "_DEBUG" /win32 +# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32 +# ADD BASE RSC /l 0x809 /d "_DEBUG" +# ADD RSC /l 0x809 /d "_DEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib /nologo /subsystem:windows /dll /incremental:no /debug /base:"0x1c0f0000" +# ADD LINK32 kernel32.lib user32.lib gdi32.lib /nologo /subsystem:windows /dll /incremental:no /debug /base:"0x1c0f0000" + +!ENDIF + +# Begin Target + +# Name "Win9xConHook - Win32 Release" +# Name "Win9xConHook - Win32 Debug" +# Begin Source File + +SOURCE=.\Win9xConHook.c +# End Source File +# Begin Source File + +SOURCE=.\Win9xConHook.def +# End Source File +# Begin Source File + +SOURCE=.\Win9xConHook.h +# End Source File +# End Target +# End Project diff --git a/rubbos/app/httpd-2.0.64/server/mpm/winnt/Win9xConHook.h b/rubbos/app/httpd-2.0.64/server/mpm/winnt/Win9xConHook.h new file mode 100644 index 00000000..e3471034 --- /dev/null +++ b/rubbos/app/httpd-2.0.64/server/mpm/winnt/Win9xConHook.h @@ -0,0 +1,57 @@ +/* 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. + */ + +#ifndef AP_WIN9XCONHOOK_H +#define AP_WIN9XCONHOOK_H + +#ifdef WIN32 + +/* Windows9xServiceCtrlHandler registers a handler routine, frees the + * console window, and registers this process as a service in Win9x. + * It creats a hidden window of class "ApacheWin95ServiceMonitor" + * and titled by the name passed, which passes the WM_SHUTDOWN message + * through the given HandlerRoutine's CTRL_SHUTDOWN event. + * Call with name of NULL to remove the Service handler. + */ +BOOL WINAPI Windows9xServiceCtrlHandler(PHANDLER_ROUTINE phandler, LPCSTR name); + + +/* FixConsoleControlHandler registers a handler routine with the + * Win9xConHook.dll, creating a hidden window and forwarding the + * WM_ENDSESSION and WM_CLOSE messages to the given HandlerRoutine + * as CTRL_SHUTDOWN_EVENT, CTRL_LOGOFF_EVENT and CTRL_CLOSE_EVENT. + * The application should still use SetConsoleCtrlHandler to grab + * the CTRL_BREAK_EVENT and CTRL_C_EVENT, if desired. + */ +BOOL WINAPI FixConsoleCtrlHandler(PHANDLER_ROUTINE phandler, BOOL add); + + +/* + * Exported PostMessage Hook, never use this directly: + * + * LRESULT CALLBACK GetMsgProc(INT hc, WPARAM wParam, LPARAM lParam); + */ + + +/* + * Exported SendMessage Hook, never use this directly: + * + * LRESULT CALLBACK CallWndProc(INT hc, WPARAM wParam, LPARAM lParam); + */ + +#endif /* WIN32 */ + +#endif AP_WIN9XCONHOOK_H diff --git a/rubbos/app/httpd-2.0.64/server/mpm/winnt/child.c b/rubbos/app/httpd-2.0.64/server/mpm/winnt/child.c new file mode 100644 index 00000000..266d0b3c --- /dev/null +++ b/rubbos/app/httpd-2.0.64/server/mpm/winnt/child.c @@ -0,0 +1,1167 @@ +/* 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 + +#define CORE_PRIVATE +#include "httpd.h" +#include "http_main.h" +#include "http_log.h" +#include "http_config.h" /* for read_config */ +#include "http_core.h" /* for get_remote_host */ +#include "http_connection.h" +#include "apr_portable.h" +#include "apr_thread_proc.h" +#include "apr_getopt.h" +#include "apr_strings.h" +#include "apr_lib.h" +#include "apr_shm.h" +#include "apr_thread_mutex.h" +#include "ap_mpm.h" +#include "ap_config.h" +#include "ap_listen.h" +#include "mpm_default.h" +#include "mpm_winnt.h" +#include "mpm_common.h" +#include <malloc.h> +#include "apr_atomic.h" + +/* shared with mpm_winnt.c */ +extern DWORD my_pid; + +/* used by parent to signal the child to start and exit */ +/* shared with mpm_winnt.c, but should be private to child.c */ +apr_proc_mutex_t *start_mutex; +HANDLE exit_event; + +/* child_main() should never need to modify is_graceful!?! */ +extern int volatile is_graceful; + +/* Queue for managing the passing of COMP_CONTEXTs between + * the accept and worker threads. + */ +static apr_pool_t *pchild; +static int shutdown_in_progress = 0; +static int workers_may_exit = 0; +static unsigned int g_blocked_threads = 0; +static HANDLE max_requests_per_child_event; + +static apr_thread_mutex_t *child_lock; +static apr_thread_mutex_t *qlock; +static PCOMP_CONTEXT qhead = NULL; +static PCOMP_CONTEXT qtail = NULL; +static int num_completion_contexts = 0; +static int max_num_completion_contexts = 0; +static HANDLE ThreadDispatchIOCP = NULL; +static HANDLE qwait_event = NULL; + + +AP_DECLARE(void) mpm_recycle_completion_context(PCOMP_CONTEXT context) +{ + /* Recycle the completion context. + * - clear the ptrans pool + * - put the context on the queue to be consumed by the accept thread + * Note: + * context->accept_socket may be in a disconnected but reusable + * state so -don't- close it. + */ + if (context) { + apr_pool_clear(context->ptrans); + context->ba = apr_bucket_alloc_create(context->ptrans); + context->next = NULL; + ResetEvent(context->Overlapped.hEvent); + apr_thread_mutex_lock(qlock); + if (qtail) { + qtail->next = context; + } else { + qhead = context; + SetEvent(qwait_event); + } + qtail = context; + apr_thread_mutex_unlock(qlock); + } +} + +AP_DECLARE(PCOMP_CONTEXT) mpm_get_completion_context(void) +{ + apr_status_t rv; + PCOMP_CONTEXT context = NULL; + + while (1) { + /* Grab a context off the queue */ + apr_thread_mutex_lock(qlock); + if (qhead) { + context = qhead; + qhead = qhead->next; + if (!qhead) + qtail = NULL; + } else { + ResetEvent(qwait_event); + } + apr_thread_mutex_unlock(qlock); + + if (!context) { + /* We failed to grab a context off the queue, consider allocating + * a new one out of the child pool. There may be up to + * (ap_threads_per_child + num_listeners) contexts in the system + * at once. + */ + if (num_completion_contexts >= max_num_completion_contexts) { + /* All workers are busy, need to wait for one */ + static int reported = 0; + if (!reported) { + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, ap_server_conf, + "Server ran out of threads to serve requests. Consider " + "raising the ThreadsPerChild setting"); + reported = 1; + } + + /* Wait for a worker to free a context. Once per second, give + * the caller a chance to check for shutdown. If the wait + * succeeds, get the context off the queue. It must be available, + * since there's only one consumer. + */ + rv = WaitForSingleObject(qwait_event, 1000); + if (rv == WAIT_OBJECT_0) + continue; + else /* Hopefully, WAIT_TIMEOUT */ + return NULL; + } else { + /* Allocate another context. + * Note: + * Multiple failures in the next two steps will cause the pchild pool + * to 'leak' storage. I don't think this is worth fixing... + */ + apr_allocator_t *allocator; + + apr_thread_mutex_lock(child_lock); + context = (PCOMP_CONTEXT) apr_pcalloc(pchild, sizeof(COMP_CONTEXT)); + + context->Overlapped.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); + if (context->Overlapped.hEvent == NULL) { + /* Hopefully this is a temporary condition ... */ + ap_log_error(APLOG_MARK,APLOG_WARNING, apr_get_os_error(), ap_server_conf, + "mpm_get_completion_context: CreateEvent failed."); + + apr_thread_mutex_unlock(child_lock); + return NULL; + } + + /* Create the tranaction pool */ + apr_allocator_create(&allocator); + apr_allocator_max_free_set(allocator, ap_max_mem_free); + rv = apr_pool_create_ex(&context->ptrans, pchild, NULL, allocator); + if (rv != APR_SUCCESS) { + ap_log_error(APLOG_MARK,APLOG_WARNING, rv, ap_server_conf, + "mpm_get_completion_context: Failed to create the transaction pool."); + CloseHandle(context->Overlapped.hEvent); + + apr_thread_mutex_unlock(child_lock); + return NULL; + } + apr_allocator_owner_set(allocator, context->ptrans); + apr_pool_tag(context->ptrans, "transaction"); + context->accept_socket = INVALID_SOCKET; + context->ba = apr_bucket_alloc_create(context->ptrans); + apr_atomic_inc(&num_completion_contexts); + + apr_thread_mutex_unlock(child_lock); + break; + } + } else { + /* Got a context from the queue */ + break; + } + } + + return context; +} + +AP_DECLARE(apr_status_t) mpm_post_completion_context(PCOMP_CONTEXT context, + io_state_e state) +{ + LPOVERLAPPED pOverlapped; + if (context) + pOverlapped = &context->Overlapped; + else + pOverlapped = NULL; + + PostQueuedCompletionStatus(ThreadDispatchIOCP, 0, state, pOverlapped); + return APR_SUCCESS; +} + + +/* + * find_ready_listener() + * Only used by Win9* and should go away when the win9*_accept() function is + * reimplemented using apr_poll(). + */ +static ap_listen_rec *head_listener; + +static APR_INLINE ap_listen_rec *find_ready_listener(fd_set * main_fds) +{ + ap_listen_rec *lr; + SOCKET nsd; + + lr = head_listener; + do { + apr_os_sock_get(&nsd, lr->sd); + if (FD_ISSET(nsd, main_fds)) { + head_listener = lr->next; + if (!head_listener) { + head_listener = ap_listeners; + } + return lr; + } + lr = lr->next; + if (!lr) { + lr = ap_listeners; + } + } while (lr != head_listener); + return NULL; +} + + +/* Windows 9x specific code... + * Accept processing for on Windows 95/98 uses a producer/consumer queue + * model. A single thread accepts connections and queues the accepted socket + * to the accept queue for consumption by a pool of worker threads. + * + * win9x_accept() + * The accept threads runs this function, which accepts connections off + * the network and calls add_job() to queue jobs to the accept_queue. + * add_job()/remove_job() + * Add or remove an accepted socket from the list of sockets + * connected to clients. allowed_globals.jobmutex protects + * against multiple concurrent access to the linked list of jobs. + * win9x_get_connection() + * Calls remove_job() to pull a job from the accept queue. All the worker + * threads block on remove_job. + */ + +typedef struct joblist_s { + struct joblist_s *next; + int sock; +} joblist; + +typedef struct globals_s { + HANDLE jobsemaphore; + joblist *jobhead; + joblist *jobtail; + apr_thread_mutex_t *jobmutex; + int jobcount; +} globals; + +globals allowed_globals = {NULL, NULL, NULL, NULL, 0}; + +#define MAX_SELECT_ERRORS 100 + + +static void add_job(int sock) +{ + joblist *new_job; + + new_job = (joblist *) malloc(sizeof(joblist)); + if (new_job == NULL) { + ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL, + "Ouch! Out of memory in add_job()!"); + return; + } + new_job->next = NULL; + new_job->sock = sock; + + apr_thread_mutex_lock(allowed_globals.jobmutex); + + if (allowed_globals.jobtail != NULL) + allowed_globals.jobtail->next = new_job; + allowed_globals.jobtail = new_job; + if (!allowed_globals.jobhead) + allowed_globals.jobhead = new_job; + allowed_globals.jobcount++; + ReleaseSemaphore(allowed_globals.jobsemaphore, 1, NULL); + + apr_thread_mutex_unlock(allowed_globals.jobmutex); +} + + +static int remove_job(void) +{ + joblist *job; + int sock; + + WaitForSingleObject(allowed_globals.jobsemaphore, INFINITE); + apr_thread_mutex_lock(allowed_globals.jobmutex); + + if (shutdown_in_progress && !allowed_globals.jobhead) { + apr_thread_mutex_unlock(allowed_globals.jobmutex); + return (INVALID_SOCKET); + } + job = allowed_globals.jobhead; + ap_assert(job); + allowed_globals.jobhead = job->next; + if (allowed_globals.jobhead == NULL) + allowed_globals.jobtail = NULL; + apr_thread_mutex_unlock(allowed_globals.jobmutex); + sock = job->sock; + free(job); + + return (sock); +} + + +static unsigned int __stdcall win9x_accept(void * dummy) +{ + struct timeval tv; + fd_set main_fds; + int wait_time = 1; + int csd; + SOCKET nsd = INVALID_SOCKET; + struct sockaddr_in sa_client; + int count_select_errors = 0; + int rc; + int clen; + ap_listen_rec *lr; + struct fd_set listenfds; + SOCKET listenmaxfd = INVALID_SOCKET; + + /* Setup the listeners + * ToDo: Use apr_poll() + */ + FD_ZERO(&listenfds); + for (lr = ap_listeners; lr; lr = lr->next) { + if (lr->sd != NULL) { + apr_os_sock_get(&nsd, lr->sd); + FD_SET(nsd, &listenfds); + if (listenmaxfd == INVALID_SOCKET || nsd > listenmaxfd) { + listenmaxfd = nsd; + } + ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, ap_server_conf, + "Child %d: Listening on port %d.", my_pid, lr->bind_addr->port); + } + } + + head_listener = ap_listeners; + + while (!shutdown_in_progress) { + tv.tv_sec = wait_time; + tv.tv_usec = 0; + memcpy(&main_fds, &listenfds, sizeof(fd_set)); + + rc = select(listenmaxfd + 1, &main_fds, NULL, NULL, &tv); + + if (rc == 0 || (rc == SOCKET_ERROR && APR_STATUS_IS_EINTR(apr_get_netos_error()))) { + count_select_errors = 0; /* reset count of errors */ + continue; + } + else if (rc == SOCKET_ERROR) { + /* A "real" error occurred, log it and increment the count of + * select errors. This count is used to ensure we don't go into + * a busy loop of continuous errors. + */ + ap_log_error(APLOG_MARK, APLOG_INFO, apr_get_netos_error(), ap_server_conf, + "select failed with error %d", apr_get_netos_error()); + count_select_errors++; + if (count_select_errors > MAX_SELECT_ERRORS) { + shutdown_in_progress = 1; + ap_log_error(APLOG_MARK, APLOG_ERR, apr_get_netos_error(), ap_server_conf, + "Too many errors in select loop. Child process exiting."); + break; + } + } else { + ap_listen_rec *lr; + + lr = find_ready_listener(&main_fds); + if (lr != NULL) { + /* fetch the native socket descriptor */ + apr_os_sock_get(&nsd, lr->sd); + } + } + + do { + clen = sizeof(sa_client); + csd = accept(nsd, (struct sockaddr *) &sa_client, &clen); + } while (csd < 0 && APR_STATUS_IS_EINTR(apr_get_netos_error())); + + if (csd < 0) { + if (APR_STATUS_IS_ECONNABORTED(apr_get_netos_error())) { + ap_log_error(APLOG_MARK, APLOG_ERR, apr_get_netos_error(), ap_server_conf, + "accept: (client socket)"); + } + } + else { + add_job(csd); + } + } + SetEvent(exit_event); + return 0; +} + + +static PCOMP_CONTEXT win9x_get_connection(PCOMP_CONTEXT context) +{ + apr_os_sock_info_t sockinfo; + int len; + + if (context == NULL) { + /* allocate the completion context and the transaction pool */ + apr_allocator_t *allocator; + apr_thread_mutex_lock(child_lock); + context = apr_pcalloc(pchild, sizeof(COMP_CONTEXT)); + apr_allocator_create(&allocator); + apr_allocator_max_free_set(allocator, ap_max_mem_free); + apr_pool_create_ex(&context->ptrans, pchild, NULL, allocator); + apr_allocator_owner_set(allocator, context->ptrans); + apr_pool_tag(context->ptrans, "transaction"); + apr_thread_mutex_unlock(child_lock); + } + + while (1) { + apr_pool_clear(context->ptrans); + context->ba = apr_bucket_alloc_create(context->ptrans); + context->accept_socket = remove_job(); + if (context->accept_socket == INVALID_SOCKET) { + return NULL; + } + len = sizeof(struct sockaddr); + context->sa_server = apr_palloc(context->ptrans, len); + if (getsockname(context->accept_socket, + context->sa_server, &len)== SOCKET_ERROR) { + ap_log_error(APLOG_MARK, APLOG_WARNING, apr_get_netos_error(), ap_server_conf, + "getsockname failed"); + continue; + } + len = sizeof(struct sockaddr); + context->sa_client = apr_palloc(context->ptrans, len); + if ((getpeername(context->accept_socket, + context->sa_client, &len)) == SOCKET_ERROR) { + ap_log_error(APLOG_MARK, APLOG_WARNING, apr_get_netos_error(), ap_server_conf, + "getpeername failed"); + memset(&context->sa_client, '\0', sizeof(context->sa_client)); + } + sockinfo.os_sock = &context->accept_socket; + sockinfo.local = context->sa_server; + sockinfo.remote = context->sa_client; + sockinfo.family = APR_INET; + sockinfo.type = SOCK_STREAM; + apr_os_sock_make(&context->sock, &sockinfo, context->ptrans); + + return context; + } +} + + +/* Windows NT/2000 specific code... + * Accept processing for on Windows NT uses a producer/consumer queue + * model. An accept thread accepts connections off the network then issues + * PostQueuedCompletionStatus() to awake a thread blocked on the ThreadDispatch + * IOCompletionPort. + * + * winnt_accept() + * One or more accept threads run in this function, each of which accepts + * connections off the network and calls PostQueuedCompletionStatus() to + * queue an io completion packet to the ThreadDispatch IOCompletionPort. + * winnt_get_connection() + * Worker threads block on the ThreadDispatch IOCompletionPort awaiting + * connections to service. + */ +#define MAX_ACCEPTEX_ERR_COUNT 250 +static unsigned int __stdcall winnt_accept(void *lr_) +{ + ap_listen_rec *lr = (ap_listen_rec *)lr_; + apr_os_sock_info_t sockinfo; + PCOMP_CONTEXT context = NULL; + DWORD BytesRead; + SOCKET nlsd; + int rv, err_count = 0; + + apr_os_sock_get(&nlsd, lr->sd); + + while (!shutdown_in_progress) { + if (!context) { + context = mpm_get_completion_context(); + if (!context) { + /* Temporary resource constraint? */ + Sleep(0); + continue; + } + } + + /* Create and initialize the accept socket */ + if (context->accept_socket == INVALID_SOCKET) { + context->accept_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + if (context->accept_socket == INVALID_SOCKET) { + /* Another temporary condition? */ + ap_log_error(APLOG_MARK,APLOG_WARNING, apr_get_netos_error(), ap_server_conf, + "winnt_accept: Failed to allocate an accept socket. " + "Temporary resource constraint? Try again."); + Sleep(100); + continue; + } + } + + /* AcceptEx on the completion context. The completion context will be + * signaled when a connection is accepted. + */ + if (!AcceptEx(nlsd, context->accept_socket, + context->buff, + 0, + PADDED_ADDR_SIZE, + PADDED_ADDR_SIZE, + &BytesRead, + &context->Overlapped)) { + rv = apr_get_netos_error(); + if ((rv == APR_FROM_OS_ERROR(WSAEINVAL)) || + (rv == APR_FROM_OS_ERROR(WSAENOTSOCK))) { + /* We can get here when: + * 1) the client disconnects early + * 2) TransmitFile does not properly recycle the accept socket (typically + * because the client disconnected) + * 3) there is VPN or Firewall software installed with buggy AcceptEx implementation + * 4) the webserver is using a dynamic address that has changed + */ + ++err_count; + closesocket(context->accept_socket); + context->accept_socket = INVALID_SOCKET; + if (err_count > MAX_ACCEPTEX_ERR_COUNT) { + ap_log_error(APLOG_MARK, APLOG_ERR, rv, ap_server_conf, + "Child %d: Encountered too many errors accepting client connections. " + "Possible causes: dynamic address renewal, or incompatible VPN or firewall software. " + "Try using the Win32DisableAcceptEx directive.", my_pid); + err_count = 0; + } + continue; + } + else if ((rv != APR_FROM_OS_ERROR(ERROR_IO_PENDING)) && + (rv != APR_FROM_OS_ERROR(WSA_IO_PENDING))) { + ++err_count; + closesocket(context->accept_socket); + context->accept_socket = INVALID_SOCKET; + if (err_count > MAX_ACCEPTEX_ERR_COUNT) { + ap_log_error(APLOG_MARK,APLOG_ERR, rv, ap_server_conf, + "Child %d: Encountered too many errors accepting client connections. " + "Possible causes: Unknown. " + "Try using the Win32DisableAcceptEx directive.", my_pid); + err_count = 0; + } + continue; + } + + /* Wait for pending i/o. + * Wake up once per second to check for shutdown . + * XXX: We should be waiting on exit_event instead of polling + */ + while (1) { + rv = WaitForSingleObject(context->Overlapped.hEvent, 1000); + if (rv == WAIT_OBJECT_0) { + if (context->accept_socket == INVALID_SOCKET) { + /* socket already closed */ + break; + } + if (!GetOverlappedResult((HANDLE)context->accept_socket, + &context->Overlapped, + &BytesRead, FALSE)) { + ap_log_error(APLOG_MARK, APLOG_WARNING, + apr_get_os_error(), ap_server_conf, + "winnt_accept: Asynchronous AcceptEx failed."); + closesocket(context->accept_socket); + context->accept_socket = INVALID_SOCKET; + } + break; + } + /* WAIT_TIMEOUT */ + if (shutdown_in_progress) { + closesocket(context->accept_socket); + context->accept_socket = INVALID_SOCKET; + break; + } + } + if (context->accept_socket == INVALID_SOCKET) { + continue; + } + } + + err_count = 0; + /* Inherit the listen socket settings. Required for + * shutdown() to work + */ + if (setsockopt(context->accept_socket, SOL_SOCKET, + SO_UPDATE_ACCEPT_CONTEXT, (char *)&nlsd, + sizeof(nlsd))) { + ap_log_error(APLOG_MARK, APLOG_WARNING, apr_get_netos_error(), ap_server_conf, + "setsockopt(SO_UPDATE_ACCEPT_CONTEXT) failed."); + /* Not a failure condition. Keep running. */ + } + + /* Get the local & remote address */ + GetAcceptExSockaddrs(context->buff, + 0, + PADDED_ADDR_SIZE, + PADDED_ADDR_SIZE, + &context->sa_server, + &context->sa_server_len, + &context->sa_client, + &context->sa_client_len); + + sockinfo.os_sock = &context->accept_socket; + sockinfo.local = context->sa_server; + sockinfo.remote = context->sa_client; + sockinfo.family = APR_INET; + sockinfo.type = SOCK_STREAM; + apr_os_sock_make(&context->sock, &sockinfo, context->ptrans); + + /* When a connection is received, send an io completion notification to + * the ThreadDispatchIOCP. This function could be replaced by + * mpm_post_completion_context(), but why do an extra function call... + */ + PostQueuedCompletionStatus(ThreadDispatchIOCP, 0, IOCP_CONNECTION_ACCEPTED, + &context->Overlapped); + context = NULL; + } + if (!shutdown_in_progress) { + /* Yow, hit an irrecoverable error! Tell the child to die. */ + SetEvent(exit_event); + } + ap_log_error(APLOG_MARK, APLOG_INFO, APR_SUCCESS, ap_server_conf, + "Child %d: Accept thread exiting.", my_pid); + return 0; +} + + +static PCOMP_CONTEXT winnt_get_connection(PCOMP_CONTEXT context) +{ + int rc; + DWORD BytesRead; + DWORD CompKey; + LPOVERLAPPED pol; + + mpm_recycle_completion_context(context); + + apr_atomic_inc(&g_blocked_threads); + while (1) { + if (workers_may_exit) { + apr_atomic_dec(&g_blocked_threads); + return NULL; + } + rc = GetQueuedCompletionStatus(ThreadDispatchIOCP, &BytesRead, &CompKey, + &pol, INFINITE); + if (!rc) { + rc = apr_get_os_error(); + ap_log_error(APLOG_MARK,APLOG_DEBUG, rc, ap_server_conf, + "Child %d: GetQueuedComplationStatus returned %d", my_pid, rc); + continue; + } + + switch (CompKey) { + case IOCP_CONNECTION_ACCEPTED: + context = CONTAINING_RECORD(pol, COMP_CONTEXT, Overlapped); + break; + case IOCP_SHUTDOWN: + apr_atomic_dec(&g_blocked_threads); + return NULL; + default: + apr_atomic_dec(&g_blocked_threads); + return NULL; + } + break; + } + apr_atomic_dec(&g_blocked_threads); + + return context; +} + + +/* + * worker_main() + * Main entry point for the worker threads. Worker threads block in + * win*_get_connection() awaiting a connection to service. + */ +static unsigned int __stdcall worker_main(void *thread_num_val) +{ + static int requests_this_child = 0; + PCOMP_CONTEXT context = NULL; + int thread_num = (int)thread_num_val; + ap_sb_handle_t *sbh; + + ap_log_error(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, ap_server_conf, + "Child %d: Worker thread %ld starting.", my_pid, thread_num); + while (1) { + conn_rec *c; + apr_int32_t disconnected; + + ap_update_child_status_from_indexes(0, thread_num, SERVER_READY, NULL); + + /* Grab a connection off the network */ + if (use_acceptex) { + context = winnt_get_connection(context); + } + else { + context = win9x_get_connection(context); + } + if (!context) { + /* Time for the thread to exit */ + break; + } + + /* Have we hit MaxRequestPerChild connections? */ + if (ap_max_requests_per_child) { + requests_this_child++; + if (requests_this_child > ap_max_requests_per_child) { + SetEvent(max_requests_per_child_event); + } + } + + ap_create_sb_handle(&sbh, context->ptrans, 0, thread_num); + c = ap_run_create_connection(context->ptrans, ap_server_conf, + context->sock, thread_num, sbh, + context->ba); + + if (c) { + ap_process_connection(c, context->sock); + apr_socket_opt_get(context->sock, APR_SO_DISCONNECTED, + &disconnected); + if (!disconnected) { + context->accept_socket = INVALID_SOCKET; + ap_lingering_close(c); + } + else if (!use_acceptex) { + /* If the socket is disconnected but we are not using acceptex, + * we cannot reuse the socket. Disconnected sockets are removed + * from the apr_socket_t struct by apr_sendfile() to prevent the + * socket descriptor from being inadvertently closed by a call + * to apr_socket_close(), so close it directly. + */ + closesocket(context->accept_socket); + context->accept_socket = INVALID_SOCKET; + } + } + else { + /* ap_run_create_connection closes the socket on failure */ + context->accept_socket = INVALID_SOCKET; + } + } + + ap_update_child_status_from_indexes(0, thread_num, SERVER_DEAD, + (request_rec *) NULL); + + ap_log_error(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, ap_server_conf, + "Child %d: Worker thread %ld exiting.", my_pid, thread_num); + return 0; +} + + +static void cleanup_thread(HANDLE *handles, int *thread_cnt, int thread_to_clean) +{ + int i; + + CloseHandle(handles[thread_to_clean]); + for (i = thread_to_clean; i < ((*thread_cnt) - 1); i++) + handles[i] = handles[i + 1]; + (*thread_cnt)--; +} + + +/* + * child_main() + * Entry point for the main control thread for the child process. + * This thread creates the accept thread, worker threads and + * monitors the child process for maintenance and shutdown + * events. + */ +static void create_listener_thread() +{ + int tid; + int num_listeners = 0; + if (!use_acceptex) { + _beginthreadex(NULL, 0, win9x_accept, + NULL, 0, &tid); + } else { + /* Start an accept thread per listener + * XXX: Why would we have a NULL sd in our listeners? + */ + ap_listen_rec *lr; + + /* Number of completion_contexts allowed in the system is + * (ap_threads_per_child + num_listeners). We need the additional + * completion contexts to prevent server hangs when ThreadsPerChild + * is configured to something less than or equal to the number + * of listeners. This is not a usual case, but people have + * encountered it. + * */ + for (lr = ap_listeners; lr ; lr = lr->next) { + num_listeners++; + } + max_num_completion_contexts = ap_threads_per_child + num_listeners; + + /* Now start a thread per listener */ + for (lr = ap_listeners; lr; lr = lr->next) { + if (lr->sd != NULL) { + _beginthreadex(NULL, 1000, winnt_accept, + (void *) lr, 0, &tid); + } + } + } +} + + +void child_main(apr_pool_t *pconf) +{ + apr_status_t status; + apr_hash_t *ht; + ap_listen_rec *lr; + HANDLE child_events[2]; + HANDLE *child_handles; + int listener_started = 0; + int threads_created = 0; + int watch_thread; + int time_remains; + int cld; + int tid; + int rv; + int i; + + apr_pool_create(&pchild, pconf); + apr_pool_tag(pchild, "pchild"); + + ap_run_child_init(pchild, ap_server_conf); + ht = apr_hash_make(pchild); + + /* Initialize the child_events */ + max_requests_per_child_event = CreateEvent(NULL, TRUE, FALSE, NULL); + if (!max_requests_per_child_event) { + ap_log_error(APLOG_MARK, APLOG_CRIT, apr_get_os_error(), ap_server_conf, + "Child %d: Failed to create a max_requests event.", my_pid); + exit(APEXIT_CHILDINIT); + } + child_events[0] = exit_event; + child_events[1] = max_requests_per_child_event; + + allowed_globals.jobsemaphore = CreateSemaphore(NULL, 0, 1000000, NULL); + apr_thread_mutex_create(&allowed_globals.jobmutex, + APR_THREAD_MUTEX_DEFAULT, pchild); + + /* + * Wait until we have permission to start accepting connections. + * start_mutex is used to ensure that only one child ever + * goes into the listen/accept loop at once. + */ + status = apr_proc_mutex_lock(start_mutex); + if (status != APR_SUCCESS) { + ap_log_error(APLOG_MARK,APLOG_ERR, status, ap_server_conf, + "Child %d: Failed to acquire the start_mutex. Process will exit.", my_pid); + exit(APEXIT_CHILDINIT); + } + ap_log_error(APLOG_MARK,APLOG_NOTICE, APR_SUCCESS, ap_server_conf, + "Child %d: Acquired the start mutex.", my_pid); + + /* + * Create the worker thread dispatch IOCompletionPort + * on Windows NT/2000 + */ + if (use_acceptex) { + /* Create the worker thread dispatch IOCP */ + ThreadDispatchIOCP = CreateIoCompletionPort(INVALID_HANDLE_VALUE, + NULL, + 0, + 0); /* CONCURRENT ACTIVE THREADS */ + apr_thread_mutex_create(&qlock, APR_THREAD_MUTEX_DEFAULT, pchild); + qwait_event = CreateEvent(NULL, TRUE, FALSE, NULL); + if (!qwait_event) { + ap_log_error(APLOG_MARK, APLOG_CRIT, apr_get_os_error(), ap_server_conf, + "Child %d: Failed to create a qwait event.", my_pid); + exit(APEXIT_CHILDINIT); + } + } + + /* + * Create the pool of worker threads + */ + ap_log_error(APLOG_MARK,APLOG_NOTICE, APR_SUCCESS, ap_server_conf, + "Child %d: Starting %d worker threads.", my_pid, ap_threads_per_child); + child_handles = (HANDLE) apr_pcalloc(pchild, ap_threads_per_child * sizeof(int)); + apr_thread_mutex_create(&child_lock, APR_THREAD_MUTEX_DEFAULT, pchild); + + while (1) { + for (i = 0; i < ap_threads_per_child; i++) { + int *score_idx; + int status = ap_scoreboard_image->servers[0][i].status; + if (status != SERVER_GRACEFUL && status != SERVER_DEAD) { + continue; + } + ap_update_child_status_from_indexes(0, i, SERVER_STARTING, NULL); + child_handles[i] = (HANDLE) _beginthreadex(NULL, 0, worker_main, + (void *) i, 0, &tid); + if (child_handles[i] == 0) { + ap_log_error(APLOG_MARK, APLOG_CRIT, apr_get_os_error(), ap_server_conf, + "Child %d: _beginthreadex failed. Unable to create all worker threads. " + "Created %d of the %d threads requested with the ThreadsPerChild configuration directive.", + my_pid, threads_created, ap_threads_per_child); + ap_signal_parent(SIGNAL_PARENT_SHUTDOWN); + goto shutdown; + } + threads_created++; + /* Save the score board index in ht keyed to the thread handle. We need this + * when cleaning up threads down below... + */ + apr_thread_mutex_lock(child_lock); + score_idx = apr_pcalloc(pchild, sizeof(int)); + *score_idx = i; + apr_hash_set(ht, &child_handles[i], sizeof(HANDLE), score_idx); + apr_thread_mutex_unlock(child_lock); + } + /* Start the listener only when workers are available */ + if (!listener_started && threads_created) { + create_listener_thread(); + listener_started = 1; + winnt_mpm_state = AP_MPMQ_RUNNING; + } + if (threads_created == ap_threads_per_child) { + break; + } + /* Check to see if the child has been told to exit */ + if (WaitForSingleObject(exit_event, 0) != WAIT_TIMEOUT) { + break; + } + /* wait for previous generation to clean up an entry in the scoreboard */ + apr_sleep(1 * APR_USEC_PER_SEC); + } + + /* Wait for one of three events: + * exit_event: + * The exit_event is signaled by the parent process to notify + * the child that it is time to exit. + * + * max_requests_per_child_event: + * This event is signaled by the worker threads to indicate that + * the process has handled MaxRequestsPerChild connections. + * + * TIMEOUT: + * To do periodic maintenance on the server (check for thread exits, + * number of completion contexts, etc.) + * + * XXX: thread exits *aren't* being checked. + * + * XXX: other_child - we need the process handles to the other children + * in order to map them to apr_proc_other_child_read (which is not + * named well, it's more like a_p_o_c_died.) + * + * XXX: however - if we get a_p_o_c handle inheritance working, and + * the parent process creates other children and passes the pipes + * to our worker processes, then we have no business doing such + * things in the child_main loop, but should happen in master_main. + */ + while (1) { +#if !APR_HAS_OTHER_CHILD + rv = WaitForMultipleObjects(2, (HANDLE *) child_events, FALSE, INFINITE); + cld = rv - WAIT_OBJECT_0; +#else + rv = WaitForMultipleObjects(2, (HANDLE *) child_events, FALSE, 1000); + cld = rv - WAIT_OBJECT_0; + if (rv == WAIT_TIMEOUT) { + apr_proc_other_child_check(); + } + else +#endif + if (rv == WAIT_FAILED) { + /* Something serious is wrong */ + ap_log_error(APLOG_MARK, APLOG_CRIT, apr_get_os_error(), ap_server_conf, + "Child %d: WAIT_FAILED -- shutting down server", my_pid); + break; + } + else if (cld == 0) { + /* Exit event was signaled */ + ap_log_error(APLOG_MARK, APLOG_NOTICE, APR_SUCCESS, ap_server_conf, + "Child %d: Exit event signaled. Child process is ending.", my_pid); + break; + } + else { + /* MaxRequestsPerChild event set by the worker threads. + * Signal the parent to restart + */ + ap_log_error(APLOG_MARK, APLOG_NOTICE, APR_SUCCESS, ap_server_conf, + "Child %d: Process exiting because it reached " + "MaxRequestsPerChild. Signaling the parent to " + "restart a new child process.", my_pid); + ap_signal_parent(SIGNAL_PARENT_RESTART); + break; + } + } + + /* + * Time to shutdown the child process + */ + + shutdown: + + winnt_mpm_state = AP_MPMQ_STOPPING; + /* Setting is_graceful will cause threads handling keep-alive connections + * to close the connection after handling the current request. + */ + is_graceful = 1; + + /* Close the listening sockets. Note, we must close the listeners + * before closing any accept sockets pending in AcceptEx to prevent + * memory leaks in the kernel. + */ + for (lr = ap_listeners; lr ; lr = lr->next) { + apr_socket_close(lr->sd); + } + + /* Shutdown listener threads and pending AcceptEx socksts + * but allow the worker threads to continue consuming from + * the queue of accepted connections. + */ + shutdown_in_progress = 1; + + Sleep(1000); + + /* Tell the worker threads to exit */ + workers_may_exit = 1; + + /* Release the start_mutex to let the new process (in the restart + * scenario) a chance to begin accepting and servicing requests + */ + rv = apr_proc_mutex_unlock(start_mutex); + if (rv == APR_SUCCESS) { + ap_log_error(APLOG_MARK,APLOG_NOTICE, rv, ap_server_conf, + "Child %d: Released the start mutex", my_pid); + } + else { + ap_log_error(APLOG_MARK,APLOG_ERR, rv, ap_server_conf, + "Child %d: Failure releasing the start mutex", my_pid); + } + + /* Shutdown the worker threads */ + if (!use_acceptex) { + for (i = 0; i < threads_created; i++) { + add_job(INVALID_SOCKET); + } + } + else { /* Windows NT/2000 */ + /* Post worker threads blocked on the ThreadDispatch IOCompletion port */ + while (g_blocked_threads > 0) { + ap_log_error(APLOG_MARK,APLOG_INFO, APR_SUCCESS, ap_server_conf, + "Child %d: %d threads blocked on the completion port", my_pid, g_blocked_threads); + for (i=g_blocked_threads; i > 0; i--) { + PostQueuedCompletionStatus(ThreadDispatchIOCP, 0, IOCP_SHUTDOWN, NULL); + } + Sleep(1000); + } + /* Empty the accept queue of completion contexts */ + apr_thread_mutex_lock(qlock); + while (qhead) { + CloseHandle(qhead->Overlapped.hEvent); + closesocket(qhead->accept_socket); + qhead = qhead->next; + } + apr_thread_mutex_unlock(qlock); + } + + /* Give busy threads a chance to service their connections, + * (no more than the global server timeout period which + * we track in msec remaining). + */ + watch_thread = 0; + time_remains = (int)(ap_server_conf->timeout / APR_TIME_C(1000)); + + while (threads_created) + { + int nFailsafe = MAXIMUM_WAIT_OBJECTS; + DWORD dwRet; + + /* Every time we roll over to wait on the first group + * of MAXIMUM_WAIT_OBJECTS threads, take a breather, + * and infrequently update the error log. + */ + if (watch_thread >= threads_created) { + if ((time_remains -= 100) < 0) + break; + + /* Every 30 seconds give an update */ + if ((time_remains % 30000) == 0) { + ap_log_error(APLOG_MARK, APLOG_NOTICE, APR_SUCCESS, + ap_server_conf, + "Child %d: Waiting %d more seconds " + "for %d worker threads to finish.", + my_pid, time_remains / 1000, threads_created); + } + /* We'll poll from the top, 10 times per second */ + Sleep(100); + watch_thread = 0; + } + + /* Fairness, on each iteration we will pick up with the thread + * after the one we just removed, even if it's a single thread. + * We don't block here. + */ + dwRet = WaitForMultipleObjects(min(threads_created - watch_thread, + MAXIMUM_WAIT_OBJECTS), + child_handles + watch_thread, 0, 0); + + if (dwRet == WAIT_FAILED) { + break; + } + if (dwRet == WAIT_TIMEOUT) { + /* none ready */ + watch_thread += MAXIMUM_WAIT_OBJECTS; + continue; + } + else if (dwRet >= WAIT_ABANDONED_0) { + /* We just got the ownership of the object, which + * should happen at most MAXIMUM_WAIT_OBJECTS times. + * It does NOT mean that the object is signaled. + */ + if ((nFailsafe--) < 1) + break; + } + else { + watch_thread += (dwRet - WAIT_OBJECT_0); + if (watch_thread >= threads_created) + break; + cleanup_thread(child_handles, &threads_created, watch_thread); + } + } + + /* Kill remaining threads off the hard way */ + if (threads_created) { + ap_log_error(APLOG_MARK,APLOG_NOTICE, APR_SUCCESS, ap_server_conf, + "Child %d: Terminating %d threads that failed to exit.", + my_pid, threads_created); + } + for (i = 0; i < threads_created; i++) { + int *score_idx; + TerminateThread(child_handles[i], 1); + CloseHandle(child_handles[i]); + /* Reset the scoreboard entry for the thread we just whacked */ + score_idx = apr_hash_get(ht, &child_handles[i], sizeof(HANDLE)); + ap_update_child_status_from_indexes(0, *score_idx, SERVER_DEAD, NULL); + } + ap_log_error(APLOG_MARK,APLOG_NOTICE, APR_SUCCESS, ap_server_conf, + "Child %d: All worker threads have exited.", my_pid); + + CloseHandle(allowed_globals.jobsemaphore); + apr_thread_mutex_destroy(allowed_globals.jobmutex); + apr_thread_mutex_destroy(child_lock); + + if (use_acceptex) { + apr_thread_mutex_destroy(qlock); + CloseHandle(qwait_event); + } + + apr_pool_destroy(pchild); + CloseHandle(exit_event); +} + +#endif /* def WIN32 */ diff --git a/rubbos/app/httpd-2.0.64/server/mpm/winnt/mpm.h b/rubbos/app/httpd-2.0.64/server/mpm/winnt/mpm.h new file mode 100644 index 00000000..2cae7a8e --- /dev/null +++ b/rubbos/app/httpd-2.0.64/server/mpm/winnt/mpm.h @@ -0,0 +1,39 @@ +/* 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. + */ + +#ifndef APACHE_MPM_H +#define APACHE_MPM_H + +/* mpm.h is the place to make declarations that are MPM specific but that must be + * shared with non-mpm specific code in the server. Hummm, perhaps we can + * move most of this stuff to mpm_common.h? + */ + +#include "scoreboard.h" + +#define MPM_NAME "WinNT" + +#define AP_MPM_WANT_SET_PIDFILE +#define AP_MPM_WANT_SET_MAX_REQUESTS +#define AP_MPM_WANT_SET_COREDUMPDIR +#define AP_MPM_WANT_SET_SCOREBOARD +#define AP_MPM_WANT_SET_MAX_MEM_FREE + +extern int ap_threads_per_child; +extern int ap_thread_limit; +extern server_rec *ap_server_conf; + +#endif /* APACHE_MPM_H */ diff --git a/rubbos/app/httpd-2.0.64/server/mpm/winnt/mpm_default.h b/rubbos/app/httpd-2.0.64/server/mpm/winnt/mpm_default.h new file mode 100644 index 00000000..847bd732 --- /dev/null +++ b/rubbos/app/httpd-2.0.64/server/mpm/winnt/mpm_default.h @@ -0,0 +1,80 @@ +/* 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. + */ + +#ifndef APACHE_MPM_DEFAULT_H +#define APACHE_MPM_DEFAULT_H + +/* Default limit on the maximum setting of the ThreadsPerChild configuration + * directive. This limit can be overridden with the ThreadLimit directive. + * This limit directly influences the amount of shared storage that is allocated + * for the scoreboard. DEFAULT_THREAD_LIMIT represents a good compromise + * between scoreboard size and the ability of the server to handle the most + * common installation requirements. + */ +#ifndef DEFAULT_THREAD_LIMIT +#define DEFAULT_THREAD_LIMIT 1920 +#endif + +/* The ThreadLimit directive can be used to override the DEFAULT_THREAD_LIMIT. + * ThreadLimit cannot be tuned larger than MAX_THREAD_LIMIT. + * This is a sort of compile-time limit to help catch typos. + */ +#ifndef MAX_THREAD_LIMIT +#define MAX_THREAD_LIMIT 15000 +#endif + +/* Number of threads started in the child process in the absence + * of a ThreadsPerChild configuration directive + */ +#ifndef DEFAULT_THREADS_PER_CHILD +#define DEFAULT_THREADS_PER_CHILD 64 +#endif + +/* Max number of child processes allowed. + */ +#define HARD_SERVER_LIMIT 1 + +/* Number of servers to spawn off by default + */ +#ifndef DEFAULT_NUM_DAEMON +#define DEFAULT_NUM_DAEMON 1 +#endif + +/* Check for definition of DEFAULT_REL_RUNTIMEDIR */ +#ifndef DEFAULT_REL_RUNTIMEDIR +#define DEFAULT_REL_RUNTIMEDIR "logs" +#endif + +/* Where the main/parent process's pid is logged */ +#ifndef DEFAULT_PIDLOG +#define DEFAULT_PIDLOG DEFAULT_REL_RUNTIMEDIR "/httpd.pid" +#endif + +/* + * Interval, in microseconds, between scoreboard maintenance. + */ +#ifndef SCOREBOARD_MAINTENANCE_INTERVAL +#define SCOREBOARD_MAINTENANCE_INTERVAL 1000000 +#endif + +/* Number of requests to try to handle in a single process. If <= 0, + * the children don't die off. + */ +#ifndef DEFAULT_MAX_REQUESTS_PER_CHILD +#define DEFAULT_MAX_REQUESTS_PER_CHILD 0 +#endif + +#endif /* AP_MPM_DEFAULT_H */ diff --git a/rubbos/app/httpd-2.0.64/server/mpm/winnt/mpm_winnt.c b/rubbos/app/httpd-2.0.64/server/mpm/winnt/mpm_winnt.c new file mode 100644 index 00000000..6fc0ae60 --- /dev/null +++ b/rubbos/app/httpd-2.0.64/server/mpm/winnt/mpm_winnt.c @@ -0,0 +1,1728 @@ +/* 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 + +#define CORE_PRIVATE +#include "httpd.h" +#include "http_main.h" +#include "http_log.h" +#include "http_config.h" /* for read_config */ +#include "http_core.h" /* for get_remote_host */ +#include "http_connection.h" +#include "apr_portable.h" +#include "apr_thread_proc.h" +#include "apr_getopt.h" +#include "apr_strings.h" +#include "apr_lib.h" +#include "apr_shm.h" +#include "apr_thread_mutex.h" +#include "ap_mpm.h" +#include "ap_config.h" +#include "ap_listen.h" +#include "mpm_default.h" +#include "mpm_winnt.h" +#include "mpm_common.h" +#include <malloc.h> +#include "apr_atomic.h" + + +/* scoreboard.c does the heavy lifting; all we do is create the child + * score by moving a handle down the pipe into the child's stdin. + */ +extern apr_shm_t *ap_scoreboard_shm; +server_rec *ap_server_conf; + +/* Definitions of WINNT MPM specific config globals */ +static HANDLE shutdown_event; /* used to signal the parent to shutdown */ +static HANDLE restart_event; /* used to signal the parent to restart */ + +static char ap_coredump_dir[MAX_STRING_LEN]; + +static int one_process = 0; +static char const* signal_arg = NULL; + +OSVERSIONINFO osver; /* VER_PLATFORM_WIN32_NT */ + +static DWORD parent_pid; +DWORD my_pid; + +int ap_threads_per_child = 0; +int use_acceptex = 1; +static int thread_limit = DEFAULT_THREAD_LIMIT; +static int first_thread_limit = 0; +static int changed_limit_at_restart; +int winnt_mpm_state = AP_MPMQ_STARTING; +/* ap_my_generation are used by the scoreboard code */ +ap_generation_t volatile ap_my_generation=0; + + +/* shared by service.c as global, although + * perhaps it should be private. + */ +apr_pool_t *pconf; + + +/* definitions from child.c */ +void child_main(apr_pool_t *pconf); + +/* used by parent to signal the child to start and exit + * NOTE: these are not sophisticated enough for multiple children + * so they ultimately should not be shared with child.c + */ +extern apr_proc_mutex_t *start_mutex; +extern HANDLE exit_event; + +/* Only one of these, the pipe from our parent, ment only for + * one child worker's consumption (not to be inherited!) + * XXX: decorate this name for the trunk branch, was left simplified + * only to make the 2.2 patch trivial to read. + */ +static HANDLE pipe; + +/* Stub functions until this MPM supports the connection status API */ + +AP_DECLARE(void) ap_update_connection_status(long conn_id, const char *key, \ + const char *value) +{ + /* NOP */ +} + +AP_DECLARE(void) ap_reset_connection_status(long conn_id) +{ + /* NOP */ +} + +AP_DECLARE(apr_array_header_t *) ap_get_status_table(apr_pool_t *p) +{ + /* NOP */ + return NULL; +} + +/* + * Command processors + */ + +static const char *set_threads_per_child (cmd_parms *cmd, void *dummy, char *arg) +{ + const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY); + if (err != NULL) { + return err; + } + + ap_threads_per_child = atoi(arg); + if (ap_threads_per_child > thread_limit) { + ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL, + "WARNING: ThreadsPerChild of %d exceeds ThreadLimit " + "value of %d threads,", ap_threads_per_child, + thread_limit); + ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL, + " lowering ThreadsPerChild to %d. To increase, please" + " see the", thread_limit); + ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL, + " ThreadLimit directive."); + ap_threads_per_child = thread_limit; + } + else if (ap_threads_per_child < 1) { + ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL, + "WARNING: Require ThreadsPerChild > 0, setting to 1"); + ap_threads_per_child = 1; + } + return NULL; +} +static const char *set_thread_limit (cmd_parms *cmd, void *dummy, const char *arg) +{ + int tmp_thread_limit; + + const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY); + if (err != NULL) { + return err; + } + + tmp_thread_limit = atoi(arg); + /* you cannot change ThreadLimit across a restart; ignore + * any such attempts + */ + if (first_thread_limit && + tmp_thread_limit != thread_limit) { + /* how do we log a message? the error log is a bit bucket at this + * point; we'll just have to set a flag so that ap_mpm_run() + * logs a warning later + */ + changed_limit_at_restart = 1; + return NULL; + } + thread_limit = tmp_thread_limit; + + if (thread_limit > MAX_THREAD_LIMIT) { + ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL, + "WARNING: ThreadLimit of %d exceeds compile time limit " + "of %d threads,", thread_limit, MAX_THREAD_LIMIT); + ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL, + " lowering ThreadLimit to %d.", MAX_THREAD_LIMIT); + thread_limit = MAX_THREAD_LIMIT; + } + else if (thread_limit < 1) { + ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL, + "WARNING: Require ThreadLimit > 0, setting to 1"); + thread_limit = 1; + } + return NULL; +} +static const char *set_disable_acceptex(cmd_parms *cmd, void *dummy, char *arg) +{ + const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY); + if (err != NULL) { + return err; + } + if (use_acceptex) { + use_acceptex = 0; + ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, NULL, + "Disabled use of AcceptEx() WinSock2 API"); + } + return NULL; +} + +static const command_rec winnt_cmds[] = { +LISTEN_COMMANDS, +AP_INIT_TAKE1("ThreadsPerChild", set_threads_per_child, NULL, RSRC_CONF, + "Number of threads each child creates" ), +AP_INIT_TAKE1("ThreadLimit", set_thread_limit, NULL, RSRC_CONF, + "Maximum worker threads in a server for this run of Apache"), +AP_INIT_NO_ARGS("Win32DisableAcceptEx", set_disable_acceptex, NULL, RSRC_CONF, + "Disable use of the high performance AcceptEx WinSock2 API to work around buggy VPN or Firewall software"), +{ NULL } +}; + + +/* + * Signalling Apache on NT. + * + * Under Unix, Apache can be told to shutdown or restart by sending various + * signals (HUP, USR, TERM). On NT we don't have easy access to signals, so + * we use "events" instead. The parent apache process goes into a loop + * where it waits forever for a set of events. Two of those events are + * called + * + * apPID_shutdown + * apPID_restart + * + * (where PID is the PID of the apache parent process). When one of these + * is signalled, the Apache parent performs the appropriate action. The events + * can become signalled through internal Apache methods (e.g. if the child + * finds a fatal error and needs to kill its parent), via the service + * control manager (the control thread will signal the shutdown event when + * requested to stop the Apache service), from the -k Apache command line, + * or from any external program which finds the Apache PID from the + * httpd.pid file. + * + * The signal_parent() function, below, is used to signal one of these events. + * It can be called by any child or parent process, since it does not + * rely on global variables. + * + * On entry, type gives the event to signal. 0 means shutdown, 1 means + * graceful restart. + */ +/* + * Initialise the signal names, in the global variables signal_name_prefix, + * signal_restart_name and signal_shutdown_name. + */ +#define MAX_SIGNAL_NAME 30 /* Long enough for apPID_shutdown, where PID is an int */ +char signal_name_prefix[MAX_SIGNAL_NAME]; +char signal_restart_name[MAX_SIGNAL_NAME]; +char signal_shutdown_name[MAX_SIGNAL_NAME]; +void setup_signal_names(char *prefix) +{ + apr_snprintf(signal_name_prefix, sizeof(signal_name_prefix), prefix); + apr_snprintf(signal_shutdown_name, sizeof(signal_shutdown_name), + "%s_shutdown", signal_name_prefix); + apr_snprintf(signal_restart_name, sizeof(signal_restart_name), + "%s_restart", signal_name_prefix); +} + +int volatile is_graceful = 0; + +AP_DECLARE(int) ap_graceful_stop_signalled(void) +{ + return is_graceful; +} + +AP_DECLARE(void) ap_signal_parent(ap_signal_parent_e type) +{ + HANDLE e; + char *signal_name; + + if (parent_pid == my_pid) { + switch(type) { + case SIGNAL_PARENT_SHUTDOWN: + { + SetEvent(shutdown_event); + break; + } + /* This MPM supports only graceful restarts right now */ + case SIGNAL_PARENT_RESTART: + case SIGNAL_PARENT_RESTART_GRACEFUL: + { + is_graceful = 1; + SetEvent(restart_event); + break; + } + } + return; + } + + switch(type) { + case SIGNAL_PARENT_SHUTDOWN: + { + signal_name = signal_shutdown_name; + break; + } + /* This MPM supports only graceful restarts right now */ + case SIGNAL_PARENT_RESTART: + case SIGNAL_PARENT_RESTART_GRACEFUL: + { + signal_name = signal_restart_name; + is_graceful = 1; + break; + } + default: + return; + } + + e = OpenEvent(EVENT_MODIFY_STATE, FALSE, signal_name); + if (!e) { + /* Um, problem, can't signal the parent, which means we can't + * signal ourselves to die. Ignore for now... + */ + ap_log_error(APLOG_MARK, APLOG_EMERG, apr_get_os_error(), ap_server_conf, + "OpenEvent on %s event", signal_name); + return; + } + if (SetEvent(e) == 0) { + /* Same problem as above */ + ap_log_error(APLOG_MARK, APLOG_EMERG, apr_get_os_error(), ap_server_conf, + "SetEvent on %s event", signal_name); + CloseHandle(e); + return; + } + CloseHandle(e); +} + + +/* + * Passed the following handles [in sync with send_handles_to_child()] + * + * ready event [signal the parent immediately, then close] + * exit event [save to poll later] + * start mutex [signal from the parent to begin accept()] + * scoreboard shm handle [to recreate the ap_scoreboard] + */ +void get_handles_from_parent(server_rec *s, HANDLE *child_exit_event, + apr_proc_mutex_t **child_start_mutex, + apr_shm_t **scoreboard_shm) +{ + HANDLE hScore; + HANDLE ready_event; + HANDLE os_start; + DWORD BytesRead; + void *sb_shared; + apr_status_t rv; + + /* *** We now do this was back in winnt_rewrite_args + * pipe = GetStdHandle(STD_INPUT_HANDLE); + */ + if (!ReadFile(pipe, &ready_event, sizeof(HANDLE), + &BytesRead, (LPOVERLAPPED) NULL) + || (BytesRead != sizeof(HANDLE))) { + ap_log_error(APLOG_MARK, APLOG_CRIT, apr_get_os_error(), ap_server_conf, + "Child %d: Unable to retrieve the ready event from the parent", my_pid); + exit(APEXIT_CHILDINIT); + } + + SetEvent(ready_event); + CloseHandle(ready_event); + + if (!ReadFile(pipe, child_exit_event, sizeof(HANDLE), + &BytesRead, (LPOVERLAPPED) NULL) + || (BytesRead != sizeof(HANDLE))) { + ap_log_error(APLOG_MARK, APLOG_CRIT, apr_get_os_error(), ap_server_conf, + "Child %d: Unable to retrieve the exit event from the parent", my_pid); + exit(APEXIT_CHILDINIT); + } + + if (!ReadFile(pipe, &os_start, sizeof(os_start), + &BytesRead, (LPOVERLAPPED) NULL) + || (BytesRead != sizeof(os_start))) { + ap_log_error(APLOG_MARK, APLOG_CRIT, apr_get_os_error(), ap_server_conf, + "Child %d: Unable to retrieve the start_mutex from the parent", my_pid); + exit(APEXIT_CHILDINIT); + } + *child_start_mutex = NULL; + if ((rv = apr_os_proc_mutex_put(child_start_mutex, &os_start, s->process->pool)) + != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_CRIT, rv, ap_server_conf, + "Child %d: Unable to access the start_mutex from the parent", my_pid); + exit(APEXIT_CHILDINIT); + } + + if (!ReadFile(pipe, &hScore, sizeof(hScore), + &BytesRead, (LPOVERLAPPED) NULL) + || (BytesRead != sizeof(hScore))) { + ap_log_error(APLOG_MARK, APLOG_CRIT, apr_get_os_error(), ap_server_conf, + "Child %d: Unable to retrieve the scoreboard from the parent", my_pid); + exit(APEXIT_CHILDINIT); + } + *scoreboard_shm = NULL; + if ((rv = apr_os_shm_put(scoreboard_shm, &hScore, s->process->pool)) + != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_CRIT, rv, ap_server_conf, + "Child %d: Unable to access the scoreboard from the parent", my_pid); + exit(APEXIT_CHILDINIT); + } + + rv = ap_reopen_scoreboard(s->process->pool, scoreboard_shm, 1); + if (rv || !(sb_shared = apr_shm_baseaddr_get(*scoreboard_shm))) { + ap_log_error(APLOG_MARK, APLOG_CRIT, rv, NULL, + "Child %d: Unable to reopen the scoreboard from the parent", my_pid); + exit(APEXIT_CHILDINIT); + } + /* We must 'initialize' the scoreboard to relink all the + * process-local pointer arrays into the shared memory block. + */ + ap_init_scoreboard(sb_shared); + + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, ap_server_conf, + "Child %d: Retrieved our scoreboard from the parent.", my_pid); +} + + +static int send_handles_to_child(apr_pool_t *p, + HANDLE child_ready_event, + HANDLE child_exit_event, + apr_proc_mutex_t *child_start_mutex, + apr_shm_t *scoreboard_shm, + HANDLE hProcess, + apr_file_t *child_in) +{ + apr_status_t rv; + HANDLE hCurrentProcess = GetCurrentProcess(); + HANDLE hDup; + HANDLE os_start; + HANDLE hScore; + DWORD BytesWritten; + + if (!DuplicateHandle(hCurrentProcess, child_ready_event, hProcess, &hDup, + EVENT_MODIFY_STATE | SYNCHRONIZE, FALSE, 0)) { + ap_log_error(APLOG_MARK, APLOG_CRIT, apr_get_os_error(), ap_server_conf, + "Parent: Unable to duplicate the ready event handle for the child"); + return -1; + } + if ((rv = apr_file_write_full(child_in, &hDup, sizeof(hDup), &BytesWritten)) + != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_CRIT, rv, ap_server_conf, + "Parent: Unable to send the exit event handle to the child"); + return -1; + } + if (!DuplicateHandle(hCurrentProcess, child_exit_event, hProcess, &hDup, + EVENT_MODIFY_STATE | SYNCHRONIZE, FALSE, 0)) { + ap_log_error(APLOG_MARK, APLOG_CRIT, apr_get_os_error(), ap_server_conf, + "Parent: Unable to duplicate the exit event handle for the child"); + return -1; + } + if ((rv = apr_file_write_full(child_in, &hDup, sizeof(hDup), &BytesWritten)) + != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_CRIT, rv, ap_server_conf, + "Parent: Unable to send the exit event handle to the child"); + return -1; + } + if ((rv = apr_os_proc_mutex_get(&os_start, child_start_mutex)) != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_CRIT, rv, ap_server_conf, + "Parent: Unable to retrieve the start mutex for the child"); + return -1; + } + if (!DuplicateHandle(hCurrentProcess, os_start, hProcess, &hDup, + SYNCHRONIZE, FALSE, 0)) { + ap_log_error(APLOG_MARK, APLOG_CRIT, apr_get_os_error(), ap_server_conf, + "Parent: Unable to duplicate the start mutex to the child"); + return -1; + } + if ((rv = apr_file_write_full(child_in, &hDup, sizeof(hDup), &BytesWritten)) + != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_CRIT, rv, ap_server_conf, + "Parent: Unable to send the start mutex to the child"); + return -1; + } + if ((rv = apr_os_shm_get(&hScore, scoreboard_shm)) != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_CRIT, rv, ap_server_conf, + "Parent: Unable to retrieve the scoreboard handle for the child"); + return -1; + } + if (!DuplicateHandle(hCurrentProcess, hScore, hProcess, &hDup, + FILE_MAP_READ | FILE_MAP_WRITE, FALSE, 0)) { + ap_log_error(APLOG_MARK, APLOG_CRIT, apr_get_os_error(), ap_server_conf, + "Parent: Unable to duplicate the scoreboard handle to the child"); + return -1; + } + if ((rv = apr_file_write_full(child_in, &hDup, sizeof(hDup), &BytesWritten)) + != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_CRIT, rv, ap_server_conf, + "Parent: Unable to send the scoreboard handle to the child"); + return -1; + } + + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, ap_server_conf, + "Parent: Sent the scoreboard to the child"); + return 0; +} + + +/* + * get_listeners_from_parent() + * The listen sockets are opened in the parent. This function, which runs + * exclusively in the child process, receives them from the parent and + * makes them availeble in the child. + */ +void get_listeners_from_parent(server_rec *s) +{ + WSAPROTOCOL_INFO WSAProtocolInfo; + ap_listen_rec *lr; + DWORD BytesRead; + int lcnt = 0; + SOCKET nsd; + + /* Set up a default listener if necessary */ + if (ap_listeners == NULL) { + ap_listen_rec *lr; + lr = apr_palloc(s->process->pool, sizeof(ap_listen_rec)); + lr->sd = NULL; + lr->next = ap_listeners; + ap_listeners = lr; + } + + /* Open the pipe to the parent process to receive the inherited socket + * data. The sockets have been set to listening in the parent process. + * + * *** We now do this was back in winnt_rewrite_args + * pipe = GetStdHandle(STD_INPUT_HANDLE); + */ + for (lr = ap_listeners; lr; lr = lr->next, ++lcnt) { + if (!ReadFile(pipe, &WSAProtocolInfo, sizeof(WSAPROTOCOL_INFO), + &BytesRead, (LPOVERLAPPED) NULL)) { + ap_log_error(APLOG_MARK, APLOG_CRIT, apr_get_os_error(), ap_server_conf, + "setup_inherited_listeners: Unable to read socket data from parent"); + exit(APEXIT_CHILDINIT); + } + nsd = WSASocket(FROM_PROTOCOL_INFO, FROM_PROTOCOL_INFO, FROM_PROTOCOL_INFO, + &WSAProtocolInfo, 0, 0); + if (nsd == INVALID_SOCKET) { + ap_log_error(APLOG_MARK, APLOG_CRIT, apr_get_netos_error(), ap_server_conf, + "Child %d: setup_inherited_listeners(), WSASocket failed to open the inherited socket.", my_pid); + exit(APEXIT_CHILDINIT); + } + + if (osver.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS) { + HANDLE hProcess = GetCurrentProcess(); + HANDLE dup; + if (DuplicateHandle(hProcess, (HANDLE) nsd, hProcess, &dup, + 0, FALSE, DUPLICATE_SAME_ACCESS)) { + closesocket(nsd); + nsd = (SOCKET) dup; + } + } + else { + /* A different approach. Many users report errors such as + * (32538)An operation was attempted on something that is not + * a socket. : Parent: WSADuplicateSocket failed... + * + * This appears that the duplicated handle is no longer recognized + * as a socket handle. SetHandleInformation should overcome that + * problem by not altering the handle identifier. But this won't + * work on 9x - it's unsupported. + */ + if (!SetHandleInformation((HANDLE)nsd, HANDLE_FLAG_INHERIT, 0)) { + ap_log_error(APLOG_MARK, APLOG_ERR, apr_get_os_error(), ap_server_conf, + "set_listeners_noninheritable: SetHandleInformation failed."); + } + } + apr_os_sock_put(&lr->sd, &nsd, s->process->pool); + } + + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, ap_server_conf, + "Child %d: retrieved %d listeners from parent", my_pid, lcnt); +} + + +static int send_listeners_to_child(apr_pool_t *p, DWORD dwProcessId, + apr_file_t *child_in) +{ + apr_status_t rv; + int lcnt = 0; + ap_listen_rec *lr; + LPWSAPROTOCOL_INFO lpWSAProtocolInfo; + DWORD BytesWritten; + + /* Run the chain of open sockets. For each socket, duplicate it + * for the target process then send the WSAPROTOCOL_INFO + * (returned by dup socket) to the child. + */ + for (lr = ap_listeners; lr; lr = lr->next, ++lcnt) { + int nsd; + lpWSAProtocolInfo = apr_pcalloc(p, sizeof(WSAPROTOCOL_INFO)); + apr_os_sock_get(&nsd,lr->sd); + ap_log_error(APLOG_MARK, APLOG_INFO, APR_SUCCESS, ap_server_conf, + "Parent: Duplicating socket %d and sending it to child process %d", + nsd, dwProcessId); + if (WSADuplicateSocket(nsd, dwProcessId, + lpWSAProtocolInfo) == SOCKET_ERROR) { + ap_log_error(APLOG_MARK, APLOG_CRIT, apr_get_netos_error(), ap_server_conf, + "Parent: WSADuplicateSocket failed for socket %d. Check the FAQ.", lr->sd ); + return -1; + } + + if ((rv = apr_file_write_full(child_in, lpWSAProtocolInfo, + sizeof(WSAPROTOCOL_INFO), &BytesWritten)) + != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_CRIT, rv, ap_server_conf, + "Parent: Unable to write duplicated socket %d to the child.", lr->sd ); + return -1; + } + } + + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, ap_server_conf, + "Parent: Sent %d listeners to child %d", lcnt, dwProcessId); + return 0; +} + +enum waitlist_e { + waitlist_ready = 0, + waitlist_term = 1 +}; + +static int create_process(apr_pool_t *p, HANDLE *child_proc, HANDLE *child_exit_event, + DWORD *child_pid) +{ + /* These NEVER change for the lifetime of this parent + */ + static char **args = NULL; + static char pidbuf[28]; + + apr_status_t rv; + apr_pool_t *ptemp; + apr_procattr_t *attr; + apr_proc_t new_child; + apr_file_t *child_out, *child_err; + HANDLE hExitEvent; + HANDLE waitlist[2]; /* see waitlist_e */ + char *cmd; + char *cwd; + char **env; + int envc; + + apr_pool_sub_make(&ptemp, p, NULL); + + /* Build the command line. Should look something like this: + * C:/apache/bin/apache.exe -f ap_server_confname + * First, get the path to the executable... + */ + apr_procattr_create(&attr, ptemp); + apr_procattr_cmdtype_set(attr, APR_PROGRAM); + apr_procattr_detach_set(attr, 1); + if (((rv = apr_filepath_get(&cwd, 0, ptemp)) != APR_SUCCESS) + || ((rv = apr_procattr_dir_set(attr, cwd)) != APR_SUCCESS)) { + ap_log_error(APLOG_MARK, APLOG_CRIT, rv, ap_server_conf, + "Parent: Failed to get the current path"); + } + + if (!args) { + /* Build the args array, only once since it won't change + * for the lifetime of this parent process. + */ + if ((rv = ap_os_proc_filepath(&cmd, ptemp)) + != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_CRIT, ERROR_BAD_PATHNAME, ap_server_conf, + "Parent: Failed to get full path of %s", + ap_server_conf->process->argv[0]); + apr_pool_destroy(ptemp); + return -1; + } + + args = malloc((ap_server_conf->process->argc + 1) * sizeof (char*)); + memcpy(args + 1, ap_server_conf->process->argv + 1, + (ap_server_conf->process->argc - 1) * sizeof (char*)); + args[0] = malloc(strlen(cmd) + 1); + strcpy(args[0], cmd); + args[ap_server_conf->process->argc] = NULL; + } + else { + cmd = args[0]; + } + + /* Create a pipe to send handles to the child */ + if ((rv = apr_procattr_io_set(attr, APR_FULL_BLOCK, + APR_NO_PIPE, APR_NO_PIPE)) != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_CRIT, rv, ap_server_conf, + "Parent: Unable to create child stdin pipe."); + apr_pool_destroy(ptemp); + return -1; + } + + /* httpd-2.0/2.2 specific to work around apr_proc_create bugs */ + /* set "NUL" as sysout for the child */ + if (((rv = apr_file_open(&child_out, "NUL", APR_WRITE | APR_READ, APR_OS_DEFAULT,p)) + != APR_SUCCESS) || + ((rv = apr_procattr_child_out_set(attr, child_out, NULL)) + != APR_SUCCESS)) { + ap_log_error(APLOG_MARK, APLOG_ERR, rv, ap_server_conf, + "Parent: Could not set child process stdout"); + } + if (((rv = apr_file_open_stderr(&child_err, p)) + != APR_SUCCESS) || + ((rv = apr_procattr_child_err_set(attr, child_err, NULL)) + != APR_SUCCESS)) { + ap_log_error(APLOG_MARK, APLOG_ERR, rv, ap_server_conf, + "Parent: Could not set child process stderr"); + } + + /* Create the child_ready_event */ + waitlist[waitlist_ready] = CreateEvent(NULL, TRUE, FALSE, NULL); + if (!waitlist[waitlist_ready]) { + ap_log_error(APLOG_MARK, APLOG_CRIT, apr_get_os_error(), ap_server_conf, + "Parent: Could not create ready event for child process"); + apr_pool_destroy (ptemp); + return -1; + } + + /* Create the child_exit_event */ + hExitEvent = CreateEvent(NULL, TRUE, FALSE, NULL); + if (!hExitEvent) { + ap_log_error(APLOG_MARK, APLOG_CRIT, apr_get_os_error(), ap_server_conf, + "Parent: Could not create exit event for child process"); + apr_pool_destroy(ptemp); + CloseHandle(waitlist[waitlist_ready]); + return -1; + } + + /* Build the env array */ + for (envc = 0; _environ[envc]; ++envc) { + ; + } + env = apr_palloc(ptemp, (envc + 2) * sizeof (char*)); + memcpy(env, _environ, envc * sizeof (char*)); + apr_snprintf(pidbuf, sizeof(pidbuf), "AP_PARENT_PID=%i", parent_pid); + env[envc] = pidbuf; + env[envc + 1] = NULL; + + rv = apr_proc_create(&new_child, cmd, args, env, attr, ptemp); + if (rv != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_CRIT, rv, ap_server_conf, + "Parent: Failed to create the child process."); + apr_pool_destroy(ptemp); + CloseHandle(hExitEvent); + CloseHandle(waitlist[waitlist_ready]); + CloseHandle(new_child.hproc); + return -1; + } + apr_file_close(child_out); + ap_log_error(APLOG_MARK, APLOG_NOTICE, APR_SUCCESS, ap_server_conf, + "Parent: Created child process %d", new_child.pid); + + if (send_handles_to_child(ptemp, waitlist[waitlist_ready], hExitEvent, + start_mutex, ap_scoreboard_shm, + new_child.hproc, new_child.in)) { + /* + * This error is fatal, mop up the child and move on + * We toggle the child's exit event to cause this child + * to quit even as it is attempting to start. + */ + SetEvent(hExitEvent); + apr_pool_destroy(ptemp); + CloseHandle(hExitEvent); + CloseHandle(waitlist[waitlist_ready]); + CloseHandle(new_child.hproc); + return -1; + } + + /* Important: + * Give the child process a chance to run before dup'ing the sockets. + * We have already set the listening sockets noninheritable, but if + * WSADuplicateSocket runs before the child process initializes + * the listeners will be inherited anyway. + */ + waitlist[waitlist_term] = new_child.hproc; + rv = WaitForMultipleObjects(2, waitlist, FALSE, INFINITE); + CloseHandle(waitlist[waitlist_ready]); + if (rv != WAIT_OBJECT_0) { + /* + * Outch... that isn't a ready signal. It's dead, Jim! + */ + SetEvent(hExitEvent); + apr_pool_destroy(ptemp); + CloseHandle(hExitEvent); + CloseHandle(new_child.hproc); + return -1; + } + + if (send_listeners_to_child(ptemp, new_child.pid, new_child.in)) { + /* + * This error is fatal, mop up the child and move on + * We toggle the child's exit event to cause this child + * to quit even as it is attempting to start. + */ + SetEvent(hExitEvent); + apr_pool_destroy(ptemp); + CloseHandle(hExitEvent); + CloseHandle(new_child.hproc); + return -1; + } + + apr_file_close(new_child.in); + + *child_exit_event = hExitEvent; + *child_proc = new_child.hproc; + *child_pid = new_child.pid; + + return 0; +} + +/*********************************************************************** + * master_main() + * master_main() runs in the parent process. It creates the child + * process which handles HTTP requests then waits on one of three + * events: + * + * restart_event + * ------------- + * The restart event causes master_main to start a new child process and + * tells the old child process to exit (by setting the child_exit_event). + * The restart event is set as a result of one of the following: + * 1. An apache -k restart command on the command line + * 2. A command received from Windows service manager which gets + * translated into an ap_signal_parent(SIGNAL_PARENT_RESTART) + * call by code in service.c. + * 3. The child process calling ap_signal_parent(SIGNAL_PARENT_RESTART) + * as a result of hitting MaxRequestsPerChild. + * + * shutdown_event + * -------------- + * The shutdown event causes master_main to tell the child process to + * exit and that the server is shutting down. The shutdown event is + * set as a result of one of the following: + * 1. An apache -k shutdown command on the command line + * 2. A command received from Windows service manager which gets + * translated into an ap_signal_parent(SIGNAL_PARENT_SHUTDOWN) + * call by code in service.c. + * + * child process handle + * -------------------- + * The child process handle will be signaled if the child process + * exits for any reason. In a normal running server, the signaling + * of this event means that the child process has exited prematurely + * due to a seg fault or other irrecoverable error. For server + * robustness, master_main will restart the child process under this + * condtion. + * + * master_main uses the child_exit_event to signal the child process + * to exit. + **********************************************************************/ +#define NUM_WAIT_HANDLES 3 +#define CHILD_HANDLE 0 +#define SHUTDOWN_HANDLE 1 +#define RESTART_HANDLE 2 +static int master_main(server_rec *s, HANDLE shutdown_event, HANDLE restart_event) +{ + int rv, cld; + int restart_pending; + int shutdown_pending; + HANDLE child_exit_event; + HANDLE event_handles[NUM_WAIT_HANDLES]; + DWORD child_pid; + + restart_pending = shutdown_pending = 0; + + event_handles[SHUTDOWN_HANDLE] = shutdown_event; + event_handles[RESTART_HANDLE] = restart_event; + + /* Create a single child process */ + rv = create_process(pconf, &event_handles[CHILD_HANDLE], + &child_exit_event, &child_pid); + if (rv < 0) + { + ap_log_error(APLOG_MARK, APLOG_CRIT, apr_get_os_error(), ap_server_conf, + "master_main: create child process failed. Exiting."); + shutdown_pending = 1; + goto die_now; + } + if (!strcasecmp(signal_arg, "runservice")) { + mpm_service_started(); + } + + /* Update the scoreboard. Note that there is only a single active + * child at once. + */ + ap_scoreboard_image->parent[0].quiescing = 0; + ap_scoreboard_image->parent[0].pid = child_pid; + + /* Wait for shutdown or restart events or for child death */ + winnt_mpm_state = AP_MPMQ_RUNNING; + rv = WaitForMultipleObjects(NUM_WAIT_HANDLES, (HANDLE *) event_handles, FALSE, INFINITE); + cld = rv - WAIT_OBJECT_0; + if (rv == WAIT_FAILED) { + /* Something serious is wrong */ + ap_log_error(APLOG_MARK,APLOG_CRIT, apr_get_os_error(), ap_server_conf, + "master_main: WaitForMultipeObjects WAIT_FAILED -- doing server shutdown"); + shutdown_pending = 1; + } + else if (rv == WAIT_TIMEOUT) { + /* Hey, this cannot happen */ + ap_log_error(APLOG_MARK, APLOG_ERR, apr_get_os_error(), s, + "master_main: WaitForMultipeObjects with INFINITE wait exited with WAIT_TIMEOUT"); + shutdown_pending = 1; + } + else if (cld == SHUTDOWN_HANDLE) { + /* shutdown_event signalled */ + shutdown_pending = 1; + ap_log_error(APLOG_MARK, APLOG_NOTICE, APR_SUCCESS, s, + "Parent: Received shutdown signal -- Shutting down the server."); + if (ResetEvent(shutdown_event) == 0) { + ap_log_error(APLOG_MARK, APLOG_ERR, apr_get_os_error(), s, + "ResetEvent(shutdown_event)"); + } + } + else if (cld == RESTART_HANDLE) { + /* Received a restart event. Prepare the restart_event to be reused + * then signal the child process to exit. + */ + restart_pending = 1; + ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, s, + "Parent: Received restart signal -- Restarting the server."); + if (ResetEvent(restart_event) == 0) { + ap_log_error(APLOG_MARK, APLOG_ERR, apr_get_os_error(), s, + "Parent: ResetEvent(restart_event) failed."); + } + if (SetEvent(child_exit_event) == 0) { + ap_log_error(APLOG_MARK, APLOG_ERR, apr_get_os_error(), s, + "Parent: SetEvent for child process %d failed.", + event_handles[CHILD_HANDLE]); + } + /* Don't wait to verify that the child process really exits, + * just move on with the restart. + */ + CloseHandle(event_handles[CHILD_HANDLE]); + event_handles[CHILD_HANDLE] = NULL; + } + else { + /* The child process exited prematurely due to a fatal error. */ + DWORD exitcode; + if (!GetExitCodeProcess(event_handles[CHILD_HANDLE], &exitcode)) { + /* HUH? We did exit, didn't we? */ + exitcode = APEXIT_CHILDFATAL; + } + if ( exitcode == APEXIT_CHILDFATAL + || exitcode == APEXIT_CHILDINIT + || exitcode == APEXIT_INIT) { + ap_log_error(APLOG_MARK, APLOG_ERR, 0, ap_server_conf, + "Parent: child process exited with status %u -- Aborting.", exitcode); + } + else { + int i; + restart_pending = 1; + ap_log_error(APLOG_MARK, APLOG_NOTICE, APR_SUCCESS, ap_server_conf, + "Parent: child process exited with status %u -- Restarting.", exitcode); + for (i = 0; i < ap_threads_per_child; i++) { + ap_update_child_status_from_indexes(0, i, SERVER_DEAD, NULL); + } + } + CloseHandle(event_handles[CHILD_HANDLE]); + event_handles[CHILD_HANDLE] = NULL; + } + if (restart_pending) { + ++ap_my_generation; + ap_scoreboard_image->global->running_generation = ap_my_generation; + } +die_now: + if (shutdown_pending) + { + int timeout = 30000; /* Timeout is milliseconds */ + winnt_mpm_state = AP_MPMQ_STOPPING; + + /* This shutdown is only marginally graceful. We will give the + * child a bit of time to exit gracefully. If the time expires, + * the child will be wacked. + */ + if (!strcasecmp(signal_arg, "runservice")) { + mpm_service_stopping(); + } + /* Signal the child processes to exit */ + if (SetEvent(child_exit_event) == 0) { + ap_log_error(APLOG_MARK,APLOG_ERR, apr_get_os_error(), ap_server_conf, + "Parent: SetEvent for child process %d failed", event_handles[CHILD_HANDLE]); + } + if (event_handles[CHILD_HANDLE]) { + rv = WaitForSingleObject(event_handles[CHILD_HANDLE], timeout); + if (rv == WAIT_OBJECT_0) { + ap_log_error(APLOG_MARK,APLOG_NOTICE, APR_SUCCESS, ap_server_conf, + "Parent: Child process exited successfully."); + CloseHandle(event_handles[CHILD_HANDLE]); + event_handles[CHILD_HANDLE] = NULL; + } + else { + ap_log_error(APLOG_MARK,APLOG_NOTICE, APR_SUCCESS, ap_server_conf, + "Parent: Forcing termination of child process %d ", event_handles[CHILD_HANDLE]); + TerminateProcess(event_handles[CHILD_HANDLE], 1); + CloseHandle(event_handles[CHILD_HANDLE]); + event_handles[CHILD_HANDLE] = NULL; + } + } + CloseHandle(child_exit_event); + return 0; /* Tell the caller we do not want to restart */ + } + winnt_mpm_state = AP_MPMQ_STARTING; + CloseHandle(child_exit_event); + return 1; /* Tell the caller we want a restart */ +} + +/* service_nt_main_fn needs to append the StartService() args + * outside of our call stack and thread as the service starts... + */ +apr_array_header_t *mpm_new_argv; + +/* Remember service_to_start failures to log and fail in pre_config. + * Remember inst_argc and inst_argv for installing or starting the + * service after we preflight the config. + */ + +AP_DECLARE(apr_status_t) ap_mpm_query(int query_code, int *result) +{ + switch(query_code){ + case AP_MPMQ_MAX_DAEMON_USED: + *result = MAXIMUM_WAIT_OBJECTS; + return APR_SUCCESS; + case AP_MPMQ_IS_THREADED: + *result = AP_MPMQ_STATIC; + return APR_SUCCESS; + case AP_MPMQ_IS_FORKED: + *result = AP_MPMQ_NOT_SUPPORTED; + return APR_SUCCESS; + case AP_MPMQ_HARD_LIMIT_DAEMONS: + *result = HARD_SERVER_LIMIT; + return APR_SUCCESS; + case AP_MPMQ_HARD_LIMIT_THREADS: + *result = thread_limit; + return APR_SUCCESS; + case AP_MPMQ_MAX_THREADS: + *result = ap_threads_per_child; + return APR_SUCCESS; + case AP_MPMQ_MIN_SPARE_DAEMONS: + *result = 0; + return APR_SUCCESS; + case AP_MPMQ_MIN_SPARE_THREADS: + *result = 0; + return APR_SUCCESS; + case AP_MPMQ_MAX_SPARE_DAEMONS: + *result = 0; + return APR_SUCCESS; + case AP_MPMQ_MAX_SPARE_THREADS: + *result = 0; + return APR_SUCCESS; + case AP_MPMQ_MAX_REQUESTS_DAEMON: + *result = ap_max_requests_per_child; + return APR_SUCCESS; + case AP_MPMQ_MAX_DAEMONS: + *result = 0; + return APR_SUCCESS; + case AP_MPMQ_MPM_STATE: + *result = winnt_mpm_state; + return APR_SUCCESS; + } + return APR_ENOTIMPL; +} + +#define SERVICE_UNSET (-1) +static apr_status_t service_set = SERVICE_UNSET; +static apr_status_t service_to_start_success; +static int inst_argc; +static const char * const *inst_argv; +static char *service_name = NULL; + +void winnt_rewrite_args(process_rec *process) +{ + /* Handle the following SCM aspects in this phase: + * + * -k runservice [transition for WinNT, nothing for Win9x] + * -k (!)install [error out if name is not installed] + * -k uninstall + * -k stop + * -k shutdown (same as -k stop). Maintained for backward compatability. + * + * We can't leave this phase until we know our identity + * and modify the command arguments appropriately. + * + * We do not care if the .conf file exists or is parsable when + * attempting to stop or uninstall a service. + */ + apr_status_t rv; + char *def_server_root; + char *binpath; + char optbuf[3]; + const char *optarg; + int fixed_args; + char *pid; + apr_getopt_t *opt; + int running_as_service = 1; + int errout = 0; + + pconf = process->pconf; + + osver.dwOSVersionInfoSize = sizeof(OSVERSIONINFO); + GetVersionEx(&osver); + + /* AP_PARENT_PID is only valid in the child */ + pid = getenv("AP_PARENT_PID"); + if (pid) + { + HANDLE filehand; + HANDLE hproc = GetCurrentProcess(); + + /* This is the child */ + my_pid = GetCurrentProcessId(); + parent_pid = (DWORD) atol(pid); + + /* Prevent holding open the (nonexistant) console */ + real_exit_code = 0; + + /* The parent gave us stdin, we need to remember this + * handle, and no longer inherit it at our children + * (we can't slurp it up now, we just aren't ready yet). + * The original handle is closed below, at apr_file_dup2() + */ + pipe = GetStdHandle(STD_INPUT_HANDLE); + if (DuplicateHandle(hproc, pipe, + hproc, &filehand, 0, FALSE, + DUPLICATE_SAME_ACCESS)) { + pipe = filehand; + } + + /* The parent gave us stdout of the NUL device, + * and expects us to suck up stdin of all of our + * shared handles and data from the parent. + * Don't infect child processes with our stdin + * handle, use another handle to NUL! + */ + { + apr_file_t *infile, *outfile; + if ((apr_file_open_stdout(&outfile, process->pool) == APR_SUCCESS) + && (apr_file_open_stdin(&infile, process->pool) == APR_SUCCESS)) + apr_file_dup2(infile, outfile, process->pool); + } + + /* This child needs the existing stderr opened for logging, + * already + */ + + /* The parent is responsible for providing the + * COMPLETE ARGUMENTS REQUIRED to the child. + * + * No further argument parsing is needed, but + * for good measure we will provide a simple + * signal string for later testing. + */ + signal_arg = "runchild"; + return; + } + + /* This is the parent, we have a long way to go :-) */ + parent_pid = my_pid = GetCurrentProcessId(); + + /* This behavior is voided by setting real_exit_code to 0 */ + atexit(hold_console_open_on_error); + + /* Rewrite process->argv[]; + * + * strip out -k signal into signal_arg + * strip out -n servicename and set the names + * add default -d serverroot from the path of this executable + * + * The end result will look like: + * + * The invocation command (%0) + * The -d serverroot default from the running executable + * The requested service's (-n) registry ConfigArgs + * The WinNT SCM's StartService() args + */ + if ((rv = ap_os_proc_filepath(&binpath, process->pconf)) + != APR_SUCCESS) { + ap_log_error(APLOG_MARK,APLOG_CRIT, rv, NULL, + "Failed to get the full path of %s", process->argv[0]); + exit(APEXIT_INIT); + } + /* WARNING: There is an implict assumption here that the + * executable resides in ServerRoot or ServerRoot\bin + */ + def_server_root = (char *) apr_filename_of_pathname(binpath); + if (def_server_root > binpath) { + *(def_server_root - 1) = '\0'; + def_server_root = (char *) apr_filename_of_pathname(binpath); + if (!strcasecmp(def_server_root, "bin")) + *(def_server_root - 1) = '\0'; + } + apr_filepath_merge(&def_server_root, NULL, binpath, + APR_FILEPATH_TRUENAME, process->pool); + + /* Use process->pool so that the rewritten argv + * lasts for the lifetime of the server process, + * because pconf will be destroyed after the + * initial pre-flight of the config parser. + */ + mpm_new_argv = apr_array_make(process->pool, process->argc + 2, + sizeof(const char *)); + *(const char **)apr_array_push(mpm_new_argv) = process->argv[0]; + *(const char **)apr_array_push(mpm_new_argv) = "-d"; + *(const char **)apr_array_push(mpm_new_argv) = def_server_root; + + fixed_args = mpm_new_argv->nelts; + + optbuf[0] = '-'; + optbuf[2] = '\0'; + apr_getopt_init(&opt, process->pool, process->argc, (char**) process->argv); + opt->errfn = NULL; + while ((rv = apr_getopt(opt, "wn:k:" AP_SERVER_BASEARGS, + optbuf + 1, &optarg)) == APR_SUCCESS) { + switch (optbuf[1]) { + + /* Shortcuts; include the -w option to hold the window open on error. + * This must not be toggled once we reset real_exit_code to 0! + */ + case 'w': + if (real_exit_code) + real_exit_code = 2; + break; + + case 'n': + service_set = mpm_service_set_name(process->pool, &service_name, + optarg); + break; + + case 'k': + signal_arg = optarg; + break; + + case 'E': + errout = 1; + /* Fall through so the Apache main() handles the 'E' arg */ + default: + *(const char **)apr_array_push(mpm_new_argv) = + apr_pstrdup(process->pool, optbuf); + + if (optarg) { + *(const char **)apr_array_push(mpm_new_argv) = optarg; + } + break; + } + } + + /* back up to capture the bad argument */ + if (rv == APR_BADCH || rv == APR_BADARG) { + opt->ind--; + } + + while (opt->ind < opt->argc) { + *(const char **)apr_array_push(mpm_new_argv) = + apr_pstrdup(process->pool, opt->argv[opt->ind++]); + } + + /* Track the number of args actually entered by the user */ + inst_argc = mpm_new_argv->nelts - fixed_args; + + /* Provide a default 'run' -k arg to simplify signal_arg tests */ + if (!signal_arg) + { + signal_arg = "run"; + running_as_service = 0; + } + + if (!strcasecmp(signal_arg, "runservice")) + { + /* Start the NT Service _NOW_ because the WinNT SCM is + * expecting us to rapidly assume control of our own + * process, the SCM will tell us our service name, and + * may have extra StartService() command arguments to + * add for us. + * + * The SCM will generally invoke the executable with + * the c:\win\system32 default directory. This is very + * lethal if folks use ServerRoot /foopath on windows + * without a drive letter. Change to the default root + * (path to apache root, above /bin) for safety. + */ + apr_filepath_set(def_server_root, process->pool); + + /* Any other process has a console, so we don't to begin + * a Win9x service until the configuration is parsed and + * any command line errors are reported. + * + * We hold the return value so that we can die in pre_config + * after logging begins, and the failure can land in the log. + */ + if (osver.dwPlatformId == VER_PLATFORM_WIN32_NT) + { + apr_file_t *nullfile; + + if (!errout) { + mpm_nt_eventlog_stderr_open(service_name, process->pool); + } + service_to_start_success = mpm_service_to_start(&service_name, + process->pool); + if (service_to_start_success == APR_SUCCESS) { + service_set = APR_SUCCESS; + } + + /* Open a null handle to soak stdout in this process. + * Windows service processes are missing any file handle + * usable for stdin/out/err. This was the cause of later + * trouble with invocations of apr_file_open_stdout() + */ + if ((rv = apr_file_open(&nullfile, "NUL", + APR_READ | APR_WRITE, APR_OS_DEFAULT, + process->pool)) == APR_SUCCESS) { + apr_file_t *nullstdout; + if (apr_file_open_stdout(&nullstdout, process->pool) + == APR_SUCCESS) + apr_file_dup2(nullstdout, nullfile, process->pool); + apr_file_close(nullfile); + } + } + } + + /* Get the default for any -k option, except run */ + if (service_set == SERVICE_UNSET && strcasecmp(signal_arg, "run")) { + service_set = mpm_service_set_name(process->pool, &service_name, + AP_DEFAULT_SERVICE_NAME); + } + + if (!strcasecmp(signal_arg, "install")) /* -k install */ + { + if (service_set == APR_SUCCESS) + { + ap_log_error(APLOG_MARK,APLOG_ERR, 0, NULL, + "%s: Service is already installed.", service_name); + exit(APEXIT_INIT); + } + } + else if (running_as_service) + { + if (service_set == APR_SUCCESS) + { + /* Attempt to Uninstall, or stop, before + * we can read the arguments or .conf files + */ + if (!strcasecmp(signal_arg, "uninstall")) { + rv = mpm_service_uninstall(); + exit(rv); + } + + if ((!strcasecmp(signal_arg, "stop")) || + (!strcasecmp(signal_arg, "shutdown"))) { + mpm_signal_service(process->pool, 0); + exit(0); + } + + rv = mpm_merge_service_args(process->pool, mpm_new_argv, + fixed_args); + if (rv == APR_SUCCESS) { + ap_log_error(APLOG_MARK,APLOG_INFO, 0, NULL, + "Using ConfigArgs of the installed service " + "\"%s\".", service_name); + } + else { + ap_log_error(APLOG_MARK,APLOG_WARNING, rv, NULL, + "No installed ConfigArgs for the service " + "\"%s\", using Apache defaults.", service_name); + } + } + else + { + ap_log_error(APLOG_MARK,APLOG_ERR, service_set, NULL, + "No installed service named \"%s\".", service_name); + exit(APEXIT_INIT); + } + } + if (strcasecmp(signal_arg, "install") && service_set && service_set != SERVICE_UNSET) + { + ap_log_error(APLOG_MARK,APLOG_ERR, service_set, NULL, + "No installed service named \"%s\".", service_name); + exit(APEXIT_INIT); + } + + /* Track the args actually entered by the user. + * These will be used for the -k install parameters, as well as + * for the -k start service override arguments. + */ + inst_argv = (const char * const *)mpm_new_argv->elts + + mpm_new_argv->nelts - inst_argc; + + process->argc = mpm_new_argv->nelts; + process->argv = (const char * const *) mpm_new_argv->elts; +} + + +static int winnt_pre_config(apr_pool_t *pconf_, apr_pool_t *plog, apr_pool_t *ptemp) +{ + /* Handle the following SCM aspects in this phase: + * + * -k runservice [WinNT errors logged from rewrite_args] + */ + + /* Initialize shared static objects. + */ + pconf = pconf_; + + if (ap_exists_config_define("ONE_PROCESS") || + ap_exists_config_define("DEBUG")) + one_process = -1; + + if (!strcasecmp(signal_arg, "runservice") + && (osver.dwPlatformId == VER_PLATFORM_WIN32_NT) + && (service_to_start_success != APR_SUCCESS)) { + ap_log_error(APLOG_MARK,APLOG_CRIT, service_to_start_success, NULL, + "%s: Unable to start the service manager.", + service_name); + exit(APEXIT_INIT); + } + + /* Win9x: disable AcceptEx */ + if (osver.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS) { + use_acceptex = 0; + } + + ap_listen_pre_config(); + ap_threads_per_child = DEFAULT_THREADS_PER_CHILD; + ap_pid_fname = DEFAULT_PIDLOG; + ap_max_requests_per_child = DEFAULT_MAX_REQUESTS_PER_CHILD; +#ifdef AP_MPM_WANT_SET_MAX_MEM_FREE + ap_max_mem_free = APR_ALLOCATOR_MAX_FREE_UNLIMITED; +#endif + + apr_cpystrn(ap_coredump_dir, ap_server_root, sizeof(ap_coredump_dir)); + + return OK; +} + +static int winnt_post_config(apr_pool_t *pconf, apr_pool_t *plog, apr_pool_t *ptemp, server_rec* s) +{ + static int restart_num = 0; + apr_status_t rv = 0; + + /* Handle the following SCM aspects in this phase: + * + * -k install + * -k config + * -k start + * -k restart + * -k runservice [Win95, only once - after we parsed the config] + * + * because all of these signals are useful _only_ if there + * is a valid conf\httpd.conf environment to start. + * + * We reached this phase by avoiding errors that would cause + * these options to fail unexpectedly in another process. + */ + + if (!strcasecmp(signal_arg, "install")) { + rv = mpm_service_install(ptemp, inst_argc, inst_argv, 0); + apr_pool_destroy(s->process->pool); + apr_terminate(); + exit(rv); + } + if (!strcasecmp(signal_arg, "config")) { + rv = mpm_service_install(ptemp, inst_argc, inst_argv, 1); + apr_pool_destroy(s->process->pool); + apr_terminate(); + exit(rv); + } + + if (!strcasecmp(signal_arg, "start")) { + ap_listen_rec *lr; + + /* Close the listening sockets. */ + for (lr = ap_listeners; lr; lr = lr->next) { + apr_socket_close(lr->sd); + lr->active = 0; + } + rv = mpm_service_start(ptemp, inst_argc, inst_argv); + apr_pool_destroy(s->process->pool); + apr_terminate(); + exit(rv); + } + + if (!strcasecmp(signal_arg, "restart")) { + mpm_signal_service(ptemp, 1); + apr_pool_destroy(s->process->pool); + apr_terminate(); + exit(rv); + } + + if (parent_pid == my_pid) + { + if (restart_num++ == 1) + { + /* This code should be run once in the parent and not run + * across a restart + */ + PSECURITY_ATTRIBUTES sa = GetNullACL(); /* returns NULL if invalid (Win95?) */ + setup_signal_names(apr_psprintf(pconf,"ap%d", parent_pid)); + + ap_log_pid(pconf, ap_pid_fname); + + /* Create shutdown event, apPID_shutdown, where PID is the parent + * Apache process ID. Shutdown is signaled by 'apache -k shutdown'. + */ + shutdown_event = CreateEvent(sa, FALSE, FALSE, signal_shutdown_name); + if (!shutdown_event) { + ap_log_error(APLOG_MARK, APLOG_CRIT, apr_get_os_error(), ap_server_conf, + "Parent: Cannot create shutdown event %s", signal_shutdown_name); + CleanNullACL((void *)sa); + return HTTP_INTERNAL_SERVER_ERROR; + } + + /* Create restart event, apPID_restart, where PID is the parent + * Apache process ID. Restart is signaled by 'apache -k restart'. + */ + restart_event = CreateEvent(sa, FALSE, FALSE, signal_restart_name); + if (!restart_event) { + CloseHandle(shutdown_event); + ap_log_error(APLOG_MARK, APLOG_CRIT, apr_get_os_error(), ap_server_conf, + "Parent: Cannot create restart event %s", signal_restart_name); + CleanNullACL((void *)sa); + return HTTP_INTERNAL_SERVER_ERROR; + } + CleanNullACL((void *)sa); + + /* Now that we are flying at 15000 feet... + * wipe out the Win95 service console, + * signal the SCM the WinNT service started, or + * if not a service, setup console handlers instead. + */ + if (!strcasecmp(signal_arg, "runservice")) + { + if (osver.dwPlatformId != VER_PLATFORM_WIN32_NT) + { + rv = mpm_service_to_start(&service_name, + s->process->pool); + if (rv != APR_SUCCESS) { + ap_log_error(APLOG_MARK,APLOG_ERR, rv, ap_server_conf, + "%s: Unable to start the service manager.", + service_name); + return HTTP_INTERNAL_SERVER_ERROR; + } + } + } + + /* Create the start mutex, as an unnamed object for security. + * Ths start mutex is used during a restart to prevent more than + * one child process from entering the accept loop at once. + */ + rv = apr_proc_mutex_create(&start_mutex, NULL, + APR_LOCK_DEFAULT, + ap_server_conf->process->pool); + if (rv != APR_SUCCESS) { + ap_log_error(APLOG_MARK,APLOG_ERR, rv, ap_server_conf, + "%s: Unable to create the start_mutex.", + service_name); + return HTTP_INTERNAL_SERVER_ERROR; + } + } + /* Always reset our console handler to be the first, even on a restart + * because some modules (e.g. mod_perl) might have set a console + * handler to terminate the process. + */ + if (strcasecmp(signal_arg, "runservice")) + mpm_start_console_handler(); + } + else /* parent_pid != my_pid */ + { + mpm_start_child_console_handler(); + } + return OK; +} + +/* This really should be a post_config hook, but the error log is already + * redirected by that point, so we need to do this in the open_logs phase. + */ +static int winnt_open_logs(apr_pool_t *p, apr_pool_t *plog, apr_pool_t *ptemp, server_rec *s) +{ + /* Initialize shared static objects. + */ + ap_server_conf = s; + + if (parent_pid != my_pid) { + return OK; + } + + /* We cannot initialize our listeners if we are restarting + * (the parent process already has glomed on to them) + * nor should we do so for service reconfiguration + * (since the service may already be running.) + */ + if (!strcasecmp(signal_arg, "restart") + || !strcasecmp(signal_arg, "config")) { + return OK; + } + + if (ap_setup_listeners(s) < 1) { + ap_log_error(APLOG_MARK, APLOG_ALERT|APLOG_STARTUP, 0, + NULL, "no listening sockets available, shutting down"); + return DONE; + } + + return OK; +} + +static void winnt_child_init(apr_pool_t *pchild, struct server_rec *s) +{ + apr_status_t rv; + + setup_signal_names(apr_psprintf(pchild,"ap%d", parent_pid)); + + /* This is a child process, not in single process mode */ + if (!one_process) { + /* Set up events and the scoreboard */ + get_handles_from_parent(s, &exit_event, &start_mutex, + &ap_scoreboard_shm); + + /* Set up the listeners */ + get_listeners_from_parent(s); + + /* Done reading from the parent, close that channel */ + CloseHandle(pipe); + + ap_my_generation = ap_scoreboard_image->global->running_generation; + } + else { + /* Single process mode - this lock doesn't even need to exist */ + rv = apr_proc_mutex_create(&start_mutex, signal_name_prefix, + APR_LOCK_DEFAULT, s->process->pool); + if (rv != APR_SUCCESS) { + ap_log_error(APLOG_MARK,APLOG_ERR, rv, ap_server_conf, + "%s child %d: Unable to init the start_mutex.", + service_name, my_pid); + exit(APEXIT_CHILDINIT); + } + + /* Borrow the shutdown_even as our _child_ loop exit event */ + exit_event = shutdown_event; + } +} + + +AP_DECLARE(int) ap_mpm_run(apr_pool_t *_pconf, apr_pool_t *plog, server_rec *s ) +{ + static int restart = 0; /* Default is "not a restart" */ + + if (!restart) { + first_thread_limit = thread_limit; + } + + if (changed_limit_at_restart) { + ap_log_error(APLOG_MARK, APLOG_WARNING, APR_SUCCESS, ap_server_conf, + "WARNING: Attempt to change ThreadLimit ignored " + "during restart"); + changed_limit_at_restart = 0; + } + + /* ### If non-graceful restarts are ever introduced - we need to rerun + * the pre_mpm hook on subsequent non-graceful restarts. But Win32 + * has only graceful style restarts - and we need this hook to act + * the same on Win32 as on Unix. + */ + if (!restart && ((parent_pid == my_pid) || one_process)) { + /* Set up the scoreboard. */ + if (ap_run_pre_mpm(s->process->pool, SB_SHARED) != OK) { + return 1; + } + } + + if ((parent_pid != my_pid) || one_process) + { + /* The child process or in one_process (debug) mode + */ + ap_log_error(APLOG_MARK, APLOG_NOTICE, APR_SUCCESS, ap_server_conf, + "Child %d: Child process is running", my_pid); + + child_main(pconf); + + ap_log_error(APLOG_MARK, APLOG_NOTICE, APR_SUCCESS, ap_server_conf, + "Child %d: Child process is exiting", my_pid); + return 1; + } + else + { + /* A real-honest to goodness parent */ + ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, ap_server_conf, + "%s configured -- resuming normal operations", + ap_get_server_version()); + ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, ap_server_conf, + "Server built: %s", ap_get_server_built()); + + restart = master_main(ap_server_conf, shutdown_event, restart_event); + + if (!restart) + { + /* Shutting down. Clean up... */ + const char *pidfile = ap_server_root_relative (pconf, ap_pid_fname); + + if (pidfile != NULL && unlink(pidfile) == 0) { + ap_log_error(APLOG_MARK, APLOG_INFO, APR_SUCCESS, + ap_server_conf, "removed PID file %s (pid=%ld)", + pidfile, GetCurrentProcessId()); + } + apr_proc_mutex_destroy(start_mutex); + + CloseHandle(restart_event); + CloseHandle(shutdown_event); + + return 1; + } + } + + return 0; /* Restart */ +} + +static void winnt_hooks(apr_pool_t *p) +{ + /* The prefork open_logs phase must run before the core's, or stderr + * will be redirected to a file, and the messages won't print to the + * console. + */ + static const char *const aszSucc[] = {"core.c", NULL}; + + ap_hook_pre_config(winnt_pre_config, NULL, NULL, APR_HOOK_MIDDLE); + ap_hook_post_config(winnt_post_config, NULL, NULL, 0); + ap_hook_child_init(winnt_child_init, NULL, NULL, APR_HOOK_MIDDLE); + ap_hook_open_logs(winnt_open_logs, NULL, aszSucc, APR_HOOK_MIDDLE); +} + +AP_MODULE_DECLARE_DATA module mpm_winnt_module = { + MPM20_MODULE_STUFF, + winnt_rewrite_args, /* hook to run before apache parses args */ + NULL, /* create per-directory config structure */ + NULL, /* merge per-directory config structures */ + NULL, /* create per-server config structure */ + NULL, /* merge per-server config structures */ + winnt_cmds, /* command apr_table_t */ + winnt_hooks /* register_hooks */ +}; + +#endif /* def WIN32 */ diff --git a/rubbos/app/httpd-2.0.64/server/mpm/winnt/mpm_winnt.h b/rubbos/app/httpd-2.0.64/server/mpm/winnt/mpm_winnt.h new file mode 100644 index 00000000..8eb04303 --- /dev/null +++ b/rubbos/app/httpd-2.0.64/server/mpm/winnt/mpm_winnt.h @@ -0,0 +1,114 @@ +/* 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. + */ + +#ifndef APACHE_MPM_WINNT_H +#define APACHE_MPM_WINNT_H + +#include "ap_listen.h" + +/* From service.c: */ + +#define SERVICE_APACHE_RESTART 128 + +#ifndef AP_DEFAULT_SERVICE_NAME +#define AP_DEFAULT_SERVICE_NAME "Apache2" +#endif + +#define SERVICECONFIG9X "Software\\Microsoft\\Windows\\CurrentVersion\\RunServices" +#define SERVICECONFIG "System\\CurrentControlSet\\Services\\%s" +#define SERVICEPARAMS "System\\CurrentControlSet\\Services\\%s\\Parameters" + +apr_status_t mpm_service_set_name(apr_pool_t *p, const char **display_name, + const char *set_name); +apr_status_t mpm_merge_service_args(apr_pool_t *p, apr_array_header_t *args, + int fixed_args); + +apr_status_t mpm_service_to_start(const char **display_name, apr_pool_t *p); +apr_status_t mpm_service_started(void); +apr_status_t mpm_service_install(apr_pool_t *ptemp, int argc, + char const* const* argv, int reconfig); +apr_status_t mpm_service_uninstall(void); + +apr_status_t mpm_service_start(apr_pool_t *ptemp, int argc, + char const* const* argv); + +void mpm_signal_service(apr_pool_t *ptemp, int signal); + +void mpm_service_stopping(void); + +void mpm_start_console_handler(void); +void mpm_start_child_console_handler(void); + +/* From nt_eventlog.c: */ + +void mpm_nt_eventlog_stderr_open(char *display_name, apr_pool_t *p); +void mpm_nt_eventlog_stderr_flush(void); + +/* From winnt.c: */ +extern int use_acceptex; +extern int winnt_mpm_state; +extern OSVERSIONINFO osver; +extern void clean_child_exit(int); + +void setup_signal_names(char *prefix); + +typedef enum { + SIGNAL_PARENT_SHUTDOWN, + SIGNAL_PARENT_RESTART, + SIGNAL_PARENT_RESTART_GRACEFUL +} ap_signal_parent_e; +AP_DECLARE(void) ap_signal_parent(ap_signal_parent_e type); + +/* + * The Windoes MPM uses a queue of completion contexts that it passes + * between the accept threads and the worker threads. Declare the + * functions to access the queue and the structures passed on the + * queue in the header file to enable modules to access them + * if necessary. The queue resides in the MPM. + */ +#ifdef CONTAINING_RECORD +#undef CONTAINING_RECORD +#endif +#define CONTAINING_RECORD(address, type, field) ((type *)( \ + (PCHAR)(address) - \ + (PCHAR)(&((type *)0)->field))) +#define PADDED_ADDR_SIZE sizeof(SOCKADDR_IN)+16 +typedef struct CompContext { + struct CompContext *next; + OVERLAPPED Overlapped; + apr_socket_t *sock; + SOCKET accept_socket; + char buff[2*PADDED_ADDR_SIZE]; + struct sockaddr *sa_server; + int sa_server_len; + struct sockaddr *sa_client; + int sa_client_len; + apr_pool_t *ptrans; + apr_bucket_alloc_t *ba; +} COMP_CONTEXT, *PCOMP_CONTEXT; + +typedef enum { + IOCP_CONNECTION_ACCEPTED = 1, + IOCP_WAIT_FOR_RECEIVE = 2, + IOCP_WAIT_FOR_TRANSMITFILE = 3, + IOCP_SHUTDOWN = 4 +} io_state_e; + +AP_DECLARE(PCOMP_CONTEXT) mpm_get_completion_context(void); +AP_DECLARE(void) mpm_recycle_completion_context(PCOMP_CONTEXT pCompContext); +AP_DECLARE(apr_status_t) mpm_post_completion_context(PCOMP_CONTEXT pCompContext, io_state_e state); +void hold_console_open_on_error(void); +#endif /* APACHE_MPM_WINNT_H */ diff --git a/rubbos/app/httpd-2.0.64/server/mpm/winnt/nt_eventlog.c b/rubbos/app/httpd-2.0.64/server/mpm/winnt/nt_eventlog.c new file mode 100644 index 00000000..37a349e8 --- /dev/null +++ b/rubbos/app/httpd-2.0.64/server/mpm/winnt/nt_eventlog.c @@ -0,0 +1,175 @@ +/* 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. + */ + +#define CORE_PRIVATE + +#include "httpd.h" +#include "http_log.h" +#include "mpm_winnt.h" +#include "apr_strings.h" +#include "apr_lib.h" +#include "apr_portable.h" +#include "ap_regkey.h" + +static char *display_name = NULL; +static HANDLE stderr_thread = NULL; +static HANDLE stderr_ready; + +static DWORD WINAPI service_stderr_thread(LPVOID hPipe) +{ + HANDLE hPipeRead = (HANDLE) hPipe; + HANDLE hEventSource; + char errbuf[256]; + char *errmsg = errbuf; + const char *errarg[9]; + DWORD errres; + ap_regkey_t *regkey; + apr_status_t rv; + apr_pool_t *p; + + apr_pool_sub_make(&p, NULL, NULL); + + errarg[0] = "The Apache service named"; + errarg[1] = display_name; + errarg[2] = "reported the following error:\r\n>>>"; + errarg[3] = errbuf; + errarg[4] = NULL; + errarg[5] = NULL; + errarg[6] = NULL; + errarg[7] = NULL; + errarg[8] = NULL; + + /* What are we going to do in here, bail on the user? not. */ + if ((rv = ap_regkey_open(®key, AP_REGKEY_LOCAL_MACHINE, + "SYSTEM\\CurrentControlSet\\Services\\" + "EventLog\\Application\\Apache Service", + APR_READ | APR_WRITE | APR_CREATE, p)) + == APR_SUCCESS) + { + DWORD dwData = EVENTLOG_ERROR_TYPE | EVENTLOG_WARNING_TYPE | + EVENTLOG_INFORMATION_TYPE; + + /* The stock message file */ + ap_regkey_value_set(regkey, "EventMessageFile", + "%SystemRoot%\\System32\\netmsg.dll", + AP_REGKEY_EXPAND, p); + + ap_regkey_value_raw_set(regkey, "TypesSupported", &dwData, + sizeof(dwData), REG_DWORD, p); + ap_regkey_close(regkey); + } + + hEventSource = RegisterEventSourceW(NULL, L"Apache Service"); + + SetEvent(stderr_ready); + + while (ReadFile(hPipeRead, errmsg, 1, &errres, NULL) && (errres == 1)) + { + if ((errmsg > errbuf) || !apr_isspace(*errmsg)) + { + ++errmsg; + if ((*(errmsg - 1) == '\n') + || (errmsg >= errbuf + sizeof(errbuf) - 1)) + { + while ((errmsg > errbuf) && apr_isspace(*(errmsg - 1))) { + --errmsg; + } + *errmsg = '\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; + } + } + } + + if ((errres = GetLastError()) != ERROR_BROKEN_PIPE) { + apr_snprintf(errbuf, sizeof(errbuf), + "Win32 error %d reading stderr pipe stream\r\n", + GetLastError()); + + ReportEvent(hEventSource, EVENTLOG_ERROR_TYPE, 0, + 3299, NULL, 9, 0, errarg, NULL); + } + + CloseHandle(hPipeRead); + DeregisterEventSource(hEventSource); + CloseHandle(stderr_thread); + stderr_thread = NULL; + apr_pool_destroy(p); + return 0; +} + + +void mpm_nt_eventlog_stderr_flush(void) +{ + HANDLE cleanup_thread = stderr_thread; + + if (cleanup_thread) { + HANDLE hErr = GetStdHandle(STD_ERROR_HANDLE); + fclose(stderr); + CloseHandle(hErr); + WaitForSingleObject(cleanup_thread, 30000); + CloseHandle(cleanup_thread); + } +} + + +void mpm_nt_eventlog_stderr_open(char *argv0, apr_pool_t *p) +{ + SECURITY_ATTRIBUTES sa; + HANDLE hProc = GetCurrentProcess(); + HANDLE hPipeRead = NULL; + HANDLE hPipeWrite = NULL; + HANDLE hDup = NULL; + DWORD threadid; + apr_file_t *eventlog_file; + apr_file_t *stderr_file; + + display_name = argv0; + + /* Create a pipe to send stderr messages to the system error log. + * + * _dup2() duplicates the write handle inheritable for us. + */ + sa.nLength = sizeof(sa); + sa.lpSecurityDescriptor = NULL; + sa.bInheritHandle = FALSE; + CreatePipe(&hPipeRead, &hPipeWrite, NULL, 0); + ap_assert(hPipeRead && hPipeWrite); + + stderr_ready = CreateEvent(NULL, FALSE, FALSE, NULL); + stderr_thread = CreateThread(NULL, 0, service_stderr_thread, + (LPVOID) hPipeRead, 0, &threadid); + ap_assert(stderr_ready && stderr_thread); + + WaitForSingleObject(stderr_ready, INFINITE); + + if ((apr_file_open_stderr(&stderr_file, p) + == APR_SUCCESS) + && (apr_os_file_put(&eventlog_file, &hPipeWrite, APR_WRITE, p) + == APR_SUCCESS)) + apr_file_dup2(stderr_file, eventlog_file, p); + + /* The code above _will_ corrupt the StdHandle... + * and we must do so anyways. We set this up only + * after we initialized the posix stderr API. + */ + ap_open_stderr_log(p); +} diff --git a/rubbos/app/httpd-2.0.64/server/mpm/winnt/service.c b/rubbos/app/httpd-2.0.64/server/mpm/winnt/service.c new file mode 100644 index 00000000..8739dc08 --- /dev/null +++ b/rubbos/app/httpd-2.0.64/server/mpm/winnt/service.c @@ -0,0 +1,1346 @@ +/* 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. + */ + +/* This module ALONE requires the window message API from user.h + * and the default APR include of windows.h will omit it, so + * preload the API symbols now... + */ + +#define CORE_PRIVATE +#define _WINUSER_ + +#include "httpd.h" +#include "http_log.h" +#include "mpm_winnt.h" +#include "apr_strings.h" +#include "apr_lib.h" +#include "ap_regkey.h" + +#ifdef NOUSER +#undef NOUSER +#endif +#undef _WINUSER_ +#include <winuser.h> + +static char *mpm_service_name = NULL; +static char *mpm_display_name = NULL; + +static struct +{ + HANDLE mpm_thread; /* primary thread handle of the apache server */ + HANDLE service_thread; /* thread service/monitor handle */ + DWORD service_thread_id;/* thread service/monitor ID */ + HANDLE service_init; /* controller thread init mutex */ + HANDLE service_term; /* NT service thread kill signal */ + SERVICE_STATUS ssStatus; + SERVICE_STATUS_HANDLE hServiceStatus; +} globdat; + +static int ReportStatusToSCMgr(int currentState, int exitCode, int waitHint); + + +#define PRODREGKEY "SOFTWARE\\" AP_SERVER_BASEVENDOR "\\" \ + AP_SERVER_BASEPRODUCT "\\" AP_SERVER_BASEREVISION + +/* + * Get the server root from the registry into 'dir' which is + * size bytes long. Returns 0 if the server root was found + * or if the serverroot key does not exist (in which case + * dir will contain an empty string), or -1 if there was + * an error getting the key. + */ +apr_status_t ap_registry_get_server_root(apr_pool_t *p, char **buf) +{ + apr_status_t rv; + ap_regkey_t *key; + + if ((rv = ap_regkey_open(&key, AP_REGKEY_LOCAL_MACHINE, PRODREGKEY, + APR_READ, p)) == APR_SUCCESS) { + rv = ap_regkey_value_get(buf, key, "ServerRoot", p); + ap_regkey_close(key); + if (rv == APR_SUCCESS) + return rv; + } + + if ((rv = ap_regkey_open(&key, AP_REGKEY_CURRENT_USER, PRODREGKEY, + APR_READ, p)) == APR_SUCCESS) { + rv = ap_regkey_value_get(buf, key, "ServerRoot", p); + ap_regkey_close(key); + if (rv == APR_SUCCESS) + return rv; + } + + *buf = NULL; + return rv; +} + + +/* The service configuration's is stored under the following trees: + * + * HKLM\System\CurrentControlSet\Services\[service name] + * + * \DisplayName + * \ImagePath + * \Parameters\ConfigArgs + * + * For Win9x, the launch service command is stored under: + * + * HKLM\Software\Microsoft\Windows\CurrentVersion\RunServices\[service name] + */ + + +/* 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 reset to 0, 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. + */ +AP_DECLARE_DATA int real_exit_code = 1; + +void hold_console_open_on_error(void) +{ + HANDLE hConIn; + HANDLE hConErr; + DWORD result; + 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]; + + 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 (!SetConsoleMode(hConIn, ENABLE_MOUSE_INPUT | 0x80)) + 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); +} + +static BOOL die_on_logoff = FALSE; + +static LRESULT CALLBACK monitor_service_9x_proc(HWND hWnd, UINT msg, + WPARAM wParam, LPARAM lParam) +{ +/* This is the WndProc procedure for our invisible window. + * When the user shuts down the system, this window is sent + * a signal WM_ENDSESSION. We clean up by signaling Apache + * to shut down, and idle until Apache's primary thread quits. + */ + if ((msg == WM_ENDSESSION) + && (die_on_logoff || (lParam != ENDSESSION_LOGOFF))) + { + ap_signal_parent(SIGNAL_PARENT_SHUTDOWN); + if (wParam) + /* Don't leave this message until we are dead! */ + WaitForSingleObject(globdat.mpm_thread, 30000); + return 0; + } + return (DefWindowProc(hWnd, msg, wParam, lParam)); +} + +static DWORD WINAPI monitor_service_9x_thread(void *service_name) +{ + /* When running as a service under Windows 9x, there is no console + * window present, and no ConsoleCtrlHandler to call when the system + * is shutdown. If the WatchWindow thread is created with a NULL + * service_name argument, then the ...SystemMonitor window class is + * used to create the "Apache" window to watch for logoff and shutdown. + * If the service_name is provided, the ...ServiceMonitor window class + * is used to create the window named by the service_name argument, + * and the logoff message is ignored. + */ + WNDCLASS wc; + HWND hwndMain; + MSG msg; + + wc.style = CS_GLOBALCLASS; + wc.lpfnWndProc = monitor_service_9x_proc; + wc.cbClsExtra = 0; + wc.cbWndExtra = 0; + wc.hInstance = NULL; + wc.hIcon = NULL; + wc.hCursor = NULL; + wc.hbrBackground = NULL; + wc.lpszMenuName = NULL; + if (service_name) + wc.lpszClassName = "ApacheWin95ServiceMonitor"; + else + wc.lpszClassName = "ApacheWin95SystemMonitor"; + + die_on_logoff = service_name ? FALSE : TRUE; + + if (!RegisterClass(&wc)) + { + ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, apr_get_os_error(), + NULL, "Could not register window class for WatchWindow"); + globdat.service_thread_id = 0; + return 0; + } + + /* Create an invisible window */ + hwndMain = CreateWindow(wc.lpszClassName, + service_name ? (char *) service_name : "Apache", + WS_OVERLAPPEDWINDOW & ~WS_VISIBLE, + CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, + CW_USEDEFAULT, NULL, NULL, NULL, NULL); + + if (!hwndMain) + { + ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, apr_get_os_error(), + NULL, "Could not create WatchWindow"); + globdat.service_thread_id = 0; + return 0; + } + + /* If we succeed, eliminate the console window. + * Signal the parent we are all set up, and + * watch the message queue while the window lives. + */ + FreeConsole(); + SetEvent(globdat.service_init); + + while (GetMessage(&msg, NULL, 0, 0)) + { + if (msg.message == WM_CLOSE) + DestroyWindow(hwndMain); + else { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + } + globdat.service_thread_id = 0; + return 0; +} + + +static BOOL CALLBACK console_control_handler(DWORD ctrl_type) +{ + switch (ctrl_type) + { + case CTRL_BREAK_EVENT: + fprintf(stderr, "Apache server restarting...\n"); + ap_signal_parent(SIGNAL_PARENT_RESTART); + return TRUE; + case CTRL_C_EVENT: + fprintf(stderr, "Apache server interrupted...\n"); + /* for Interrupt signals, shut down the server. + * Tell the system we have dealt with the signal + * without waiting for Apache to terminate. + */ + ap_signal_parent(SIGNAL_PARENT_SHUTDOWN); + return TRUE; + + case CTRL_CLOSE_EVENT: + case CTRL_LOGOFF_EVENT: + case CTRL_SHUTDOWN_EVENT: + /* for Terminate signals, shut down the server. + * Wait for Apache to terminate, but respond + * after a reasonable time to tell the system + * that we did attempt to shut ourself down. + * THESE EVENTS WILL NOT OCCUR UNDER WIN9x! + */ + fprintf(stderr, "Apache server shutdown initiated...\n"); + ap_signal_parent(SIGNAL_PARENT_SHUTDOWN); + Sleep(30000); + return TRUE; + } + + /* We should never get here, but this is (mostly) harmless */ + return FALSE; +} + + +static void stop_console_handler(void) +{ + SetConsoleCtrlHandler(console_control_handler, FALSE); +} + + +void mpm_start_console_handler(void) +{ + SetConsoleCtrlHandler(console_control_handler, TRUE); + atexit(stop_console_handler); +} + + +/* Special situation - children of services need to mind their + * P's & Q's and wait quietly, ignoring the mean OS signaling + * shutdown and other horrors, to kill them gracefully... + */ + +static BOOL CALLBACK child_control_handler(DWORD ctrl_type) +{ + switch (ctrl_type) + { + case CTRL_C_EVENT: + case CTRL_BREAK_EVENT: + /* for Interrupt signals, ignore them. + * The system will also signal the parent process, + * which will terminate Apache. + */ + return TRUE; + + case CTRL_CLOSE_EVENT: + case CTRL_LOGOFF_EVENT: + case CTRL_SHUTDOWN_EVENT: + /* for Shutdown signals, ignore them, but... . + * The system will also signal the parent process, + * which will terminate Apache, so we need to wait. + */ + Sleep(30000); + return TRUE; + } + + /* We should never get here, but this is (mostly) harmless */ + return FALSE; +} + + +static void stop_child_console_handler(void) +{ + SetConsoleCtrlHandler(child_control_handler, FALSE); +} + + +void mpm_start_child_console_handler(void) +{ + if (osver.dwPlatformId == VER_PLATFORM_WIN32_NT) { + FreeConsole(); + } + else + { + SetConsoleCtrlHandler(child_control_handler, TRUE); + atexit(stop_child_console_handler); + } +} + + +/********************************** + WinNT service control management + **********************************/ + +static int ReportStatusToSCMgr(int currentState, int exitCode, int waitHint) +{ + static int checkPoint = 1; + int rv = APR_SUCCESS; + + if (globdat.hServiceStatus) + { + if (currentState == SERVICE_RUNNING) { + globdat.ssStatus.dwWaitHint = 0; + globdat.ssStatus.dwCheckPoint = 0; + globdat.ssStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP; + } + else if (currentState == SERVICE_STOPPED) { + globdat.ssStatus.dwWaitHint = 0; + globdat.ssStatus.dwCheckPoint = 0; + if (!exitCode && globdat.ssStatus.dwCurrentState + != SERVICE_STOP_PENDING) { + /* An unexpected exit? Better to error! */ + exitCode = 1; + } + if (exitCode) { + globdat.ssStatus.dwWin32ExitCode =ERROR_SERVICE_SPECIFIC_ERROR; + globdat.ssStatus.dwServiceSpecificExitCode = exitCode; + } + } + else { + globdat.ssStatus.dwCheckPoint = ++checkPoint; + globdat.ssStatus.dwControlsAccepted = 0; + if(waitHint) + globdat.ssStatus.dwWaitHint = waitHint; + } + + globdat.ssStatus.dwCurrentState = currentState; + + rv = SetServiceStatus(globdat.hServiceStatus, &globdat.ssStatus); + } + return(rv); +} + +/* Set the service description regardless of platform. + * We revert to set_service_description 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. + */ + +/* borrowed from mpm_winnt.c */ +extern apr_pool_t *pconf; + +/* 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. + */ +static void set_service_description(void) +{ + const char *full_description; + SC_HANDLE schSCManager; + BOOL ret = 0; + + /* Nothing to do if we are a console + */ + if (!mpm_service_name) + return; + + /* Time to fix up the description, upon each successful restart + */ + full_description = ap_get_server_version(); + + if ((osver.dwPlatformId == VER_PLATFORM_WIN32_NT) + && (osver.dwMajorVersion > 4) + && (ChangeServiceConfig2) + && (schSCManager = OpenSCManager(NULL, NULL, SC_MANAGER_CONNECT))) + { + SC_HANDLE schService = OpenService(schSCManager, mpm_service_name, + SERVICE_CHANGE_CONFIG); + if (schService) { + /* Cast is necessary, ChangeServiceConfig2 handles multiple + * object types, some volatile, some not. + */ + /* ###: utf-ize */ + if (ChangeServiceConfig2(schService, + 1 /* SERVICE_CONFIG_DESCRIPTION */, + (LPVOID) &full_description)) { + full_description = NULL; + } + CloseServiceHandle(schService); + } + CloseServiceHandle(schSCManager); + } + + if (full_description) + { + char szPath[MAX_PATH]; + ap_regkey_t *svckey; + apr_status_t rv; + + /* Find the Service key that Monitor Applications iterate */ + apr_snprintf(szPath, sizeof(szPath), + "SYSTEM\\CurrentControlSet\\Services\\%s", + mpm_service_name); + rv = ap_regkey_open(&svckey, AP_REGKEY_LOCAL_MACHINE, szPath, + APR_READ | APR_WRITE, pconf); + if (rv != APR_SUCCESS) { + return; + } + /* Attempt to set the Description value for our service */ + ap_regkey_value_set(svckey, "Description", full_description, 0, pconf); + ap_regkey_close(svckey); + } +} + +/* handle the SCM's ControlService() callbacks to our service */ + +static VOID WINAPI service_nt_ctrl(DWORD dwCtrlCode) +{ + if (dwCtrlCode == SERVICE_CONTROL_STOP) + { + ap_signal_parent(SIGNAL_PARENT_SHUTDOWN); + ReportStatusToSCMgr(SERVICE_STOP_PENDING, NO_ERROR, 30000); + return; + } + if (dwCtrlCode == SERVICE_APACHE_RESTART) + { + ap_signal_parent(SIGNAL_PARENT_RESTART); + ReportStatusToSCMgr(SERVICE_START_PENDING, NO_ERROR, 30000); + return; + } + + ReportStatusToSCMgr(globdat.ssStatus.dwCurrentState, NO_ERROR, 0); +} + + +/* service_nt_main_fn is outside of the call stack and outside of the + * primary server thread... so now we _really_ need a placeholder! + * The winnt_rewrite_args has created and shared mpm_new_argv with us. + */ +extern apr_array_header_t *mpm_new_argv; + +/* ###: utf-ize */ +static void __stdcall service_nt_main_fn(DWORD argc, LPTSTR *argv) +{ + const char *ignored; + + /* args and service names live in the same pool */ + mpm_service_set_name(mpm_new_argv->pool, &ignored, argv[0]); + + memset(&globdat.ssStatus, 0, sizeof(globdat.ssStatus)); + globdat.ssStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS; + globdat.ssStatus.dwCurrentState = SERVICE_START_PENDING; + globdat.ssStatus.dwCheckPoint = 1; + + /* ###: utf-ize */ + if (!(globdat.hServiceStatus = RegisterServiceCtrlHandler(argv[0], service_nt_ctrl))) + { + ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, apr_get_os_error(), + NULL, "Failure registering service handler"); + return; + } + + /* Report status, no errors, and buy 3 more seconds */ + ReportStatusToSCMgr(SERVICE_START_PENDING, NO_ERROR, 30000); + + /* We need to append all the command arguments passed via StartService() + * to our running service... which just got here via the SCM... + * but we hvae no interest in argv[0] for the mpm_new_argv list. + */ + if (argc > 1) + { + char **cmb_data; + + mpm_new_argv->nalloc = mpm_new_argv->nelts + argc - 1; + cmb_data = malloc(mpm_new_argv->nalloc * sizeof(const char *)); + + /* mpm_new_argv remains first (of lower significance) */ + memcpy (cmb_data, mpm_new_argv->elts, + mpm_new_argv->elt_size * mpm_new_argv->nelts); + + /* Service args follow from StartService() invocation */ + memcpy (cmb_data + mpm_new_argv->nelts, argv + 1, + mpm_new_argv->elt_size * (argc - 1)); + + /* The replacement arg list is complete */ + mpm_new_argv->elts = (char *)cmb_data; + mpm_new_argv->nelts = mpm_new_argv->nalloc; + } + + /* Let the main thread continue now... but hang on to the + * signal_monitor event so we can take further action + */ + SetEvent(globdat.service_init); + + WaitForSingleObject(globdat.service_term, INFINITE); +} + + +DWORD WINAPI service_nt_dispatch_thread(LPVOID nada) +{ + apr_status_t rv = APR_SUCCESS; + + SERVICE_TABLE_ENTRY dispatchTable[] = + { + { "", service_nt_main_fn }, + { NULL, NULL } + }; + + /* ###: utf-ize */ + if (!StartServiceCtrlDispatcher(dispatchTable)) + { + /* This is a genuine failure of the SCM. */ + rv = apr_get_os_error(); + ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, rv, NULL, + "Error starting service control dispatcher"); + } + + return (rv); +} + + +apr_status_t mpm_service_set_name(apr_pool_t *p, const char **display_name, + const char *set_name) +{ + char key_name[MAX_PATH]; + ap_regkey_t *key; + apr_status_t rv; + + /* ### Needs improvement, on Win2K the user can _easily_ + * change the display name to a string that doesn't reflect + * the internal service name + whitespace! + */ + mpm_service_name = apr_palloc(p, strlen(set_name) + 1); + apr_collapse_spaces((char*) mpm_service_name, set_name); + apr_snprintf(key_name, sizeof(key_name), SERVICECONFIG, mpm_service_name); + rv = ap_regkey_open(&key, AP_REGKEY_LOCAL_MACHINE, key_name, APR_READ, pconf); + if (rv == APR_SUCCESS) { + rv = ap_regkey_value_get(&mpm_display_name, key, "DisplayName", pconf); + ap_regkey_close(key); + } + if (rv != APR_SUCCESS) { + /* Take the given literal name if there is no service entry */ + mpm_display_name = apr_pstrdup(p, set_name); + } + *display_name = mpm_display_name; + return rv; +} + + +apr_status_t mpm_merge_service_args(apr_pool_t *p, + apr_array_header_t *args, + int fixed_args) +{ + apr_array_header_t *svc_args = NULL; + char conf_key[MAX_PATH]; + char **cmb_data; + apr_status_t rv; + ap_regkey_t *key; + + apr_snprintf(conf_key, sizeof(conf_key), SERVICEPARAMS, mpm_service_name); + rv = ap_regkey_open(&key, AP_REGKEY_LOCAL_MACHINE, conf_key, APR_READ, p); + if (rv == APR_SUCCESS) { + rv = ap_regkey_value_array_get(&svc_args, key, "ConfigArgs", p); + ap_regkey_close(key); + } + if (rv != APR_SUCCESS) { + if (rv == ERROR_FILE_NOT_FOUND) { + ap_log_error(APLOG_MARK, APLOG_INFO, 0, NULL, + "No ConfigArgs registered for %s, perhaps " + "this service is not installed?", + mpm_service_name); + return APR_SUCCESS; + } + else + return (rv); + } + + if (!svc_args || svc_args->nelts == 0) { + return (APR_SUCCESS); + } + + /* Now we have the mpm_service_name arg, and the mpm_runservice_nt() + * call appended the arguments passed by StartService(), so it's + * time to _prepend_ the default arguments for the server from + * the service's default arguments (all others override them)... + */ + args->nalloc = args->nelts + svc_args->nelts; + cmb_data = malloc(args->nalloc * sizeof(const char *)); + + /* First three args (argv[0], -f, path) remain first */ + memcpy(cmb_data, args->elts, args->elt_size * fixed_args); + + /* Service args follow from service registry array */ + memcpy(cmb_data + fixed_args, svc_args->elts, + svc_args->elt_size * svc_args->nelts); + + /* Remaining new args follow */ + memcpy(cmb_data + fixed_args + svc_args->nelts, + (const char **)args->elts + fixed_args, + args->elt_size * (args->nelts - fixed_args)); + + args->elts = (char *)cmb_data; + args->nelts = args->nalloc; + + return APR_SUCCESS; +} + + +void service_stopped(void) +{ + /* Still have a thread & window to clean up, so signal now */ + if (globdat.service_thread) + { + if (osver.dwPlatformId == VER_PLATFORM_WIN32_NT) + { + /* Stop logging to the event log */ + mpm_nt_eventlog_stderr_flush(); + + /* Cause the service_nt_main_fn to complete */ + ReleaseMutex(globdat.service_term); + + ReportStatusToSCMgr(SERVICE_STOPPED, // service state + NO_ERROR, // exit code + 0); // wait hint + } + else /* osver.dwPlatformId != VER_PLATFORM_WIN32_NT */ + { + RegisterServiceProcess(0, 0); + PostThreadMessage(globdat.service_thread_id, WM_CLOSE, 0, 0); + } + + WaitForSingleObject(globdat.service_thread, 5000); + CloseHandle(globdat.service_thread); + } +} + + +apr_status_t mpm_service_to_start(const char **display_name, apr_pool_t *p) +{ + HANDLE hProc = GetCurrentProcess(); + HANDLE hThread = GetCurrentThread(); + HANDLE waitfor[2]; + + /* Prevent holding open the (hidden) console */ + real_exit_code = 0; + + /* GetCurrentThread returns a psuedo-handle, we need + * a real handle for another thread to wait upon. + */ + if (!DuplicateHandle(hProc, hThread, hProc, &(globdat.mpm_thread), + 0, FALSE, DUPLICATE_SAME_ACCESS)) { + return APR_ENOTHREAD; + } + + if (osver.dwPlatformId == VER_PLATFORM_WIN32_NT) + { + globdat.service_init = CreateEvent(NULL, FALSE, FALSE, NULL); + globdat.service_term = CreateMutex(NULL, TRUE, NULL); + if (!globdat.service_init || !globdat.service_term) { + return APR_EGENERAL; + } + + globdat.service_thread = CreateThread(NULL, 0, service_nt_dispatch_thread, + NULL, 0, &globdat.service_thread_id); + } + else /* osver.dwPlatformId != VER_PLATFORM_WIN32_NT */ + { + if (!RegisterServiceProcess(0, 1)) + return GetLastError(); + + globdat.service_init = CreateEvent(NULL, FALSE, FALSE, NULL); + if (!globdat.service_init) { + return APR_EGENERAL; + } + + globdat.service_thread = CreateThread(NULL, 0, monitor_service_9x_thread, + (LPVOID) mpm_service_name, 0, + &globdat.service_thread_id); + } + + if (!globdat.service_thread) { + return APR_ENOTHREAD; + } + + waitfor[0] = globdat.service_init; + waitfor[1] = globdat.service_thread; + + /* Wait for controlling thread init or termination */ + if (WaitForMultipleObjects(2, waitfor, FALSE, 10000) != WAIT_OBJECT_0) { + return APR_ENOTHREAD; + } + + atexit(service_stopped); + *display_name = mpm_display_name; + return APR_SUCCESS; +} + + +apr_status_t mpm_service_started(void) +{ + set_service_description(); + if (osver.dwPlatformId == VER_PLATFORM_WIN32_NT) + { + ReportStatusToSCMgr(SERVICE_RUNNING, // service state + NO_ERROR, // exit code + 0); // wait hint + } + return APR_SUCCESS; +} + + +void mpm_service_stopping(void) +{ + if (osver.dwPlatformId == VER_PLATFORM_WIN32_NT) + ReportStatusToSCMgr(SERVICE_STOP_PENDING, // service state + NO_ERROR, // exit code + 30000); // wait hint +} + + +apr_status_t mpm_service_install(apr_pool_t *ptemp, int argc, + const char * const * argv, int reconfig) +{ + char key_name[MAX_PATH]; + char exe_path[MAX_PATH]; + char *launch_cmd; + ap_regkey_t *key; + apr_status_t rv; + + fprintf(stderr,reconfig ? "Reconfiguring the %s service\n" + : "Installing the %s service\n", mpm_display_name); + + /* ###: utf-ize */ + if (GetModuleFileName(NULL, exe_path, sizeof(exe_path)) == 0) + { + apr_status_t rv = apr_get_os_error(); + ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, rv, NULL, + "GetModuleFileName failed"); + return rv; + } + + if (osver.dwPlatformId == VER_PLATFORM_WIN32_NT) + { + SC_HANDLE schService; + SC_HANDLE schSCManager; + + schSCManager = OpenSCManager(NULL, NULL, /* local, default database */ + SC_MANAGER_CREATE_SERVICE); + if (!schSCManager) { + rv = apr_get_os_error(); + ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, rv, NULL, + "Failed to open the WinNT service manager"); + return (rv); + } + + launch_cmd = apr_psprintf(ptemp, "\"%s\" -k runservice", exe_path); + + if (reconfig) { + /* ###: utf-ize */ + schService = OpenService(schSCManager, mpm_service_name, + SERVICE_CHANGE_CONFIG); + if (!schService) { + ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_ERR, + apr_get_os_error(), NULL, + "OpenService failed"); + } + /* ###: utf-ize */ + else if (!ChangeServiceConfig(schService, + SERVICE_WIN32_OWN_PROCESS, + SERVICE_AUTO_START, + SERVICE_ERROR_NORMAL, + launch_cmd, NULL, NULL, + "Tcpip\0Afd\0", NULL, NULL, + mpm_display_name)) { + ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_ERR, + apr_get_os_error(), NULL, + "ChangeServiceConfig failed"); + /* !schService aborts configuration below */ + CloseServiceHandle(schService); + schService = NULL; + } + } + else { + /* RPCSS is the Remote Procedure Call (RPC) Locator required + * for DCOM communication pipes. I am far from convinced we + * should add this to the default service dependencies, but + * be warned that future apache modules or ISAPI dll's may + * depend on it. + */ + /* ###: utf-ize */ + schService = CreateService(schSCManager, // SCManager database + mpm_service_name, // name of service + mpm_display_name, // name to display + SERVICE_ALL_ACCESS, // access required + SERVICE_WIN32_OWN_PROCESS, // service type + SERVICE_AUTO_START, // start type + SERVICE_ERROR_NORMAL, // error control type + launch_cmd, // service's binary + NULL, // no load svc group + NULL, // no tag identifier + "Tcpip\0Afd\0", // dependencies + NULL, // use SYSTEM account + NULL); // no password + + if (!schService) + { + rv = apr_get_os_error(); + ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, rv, NULL, + "Failed to create WinNT Service Profile"); + CloseServiceHandle(schSCManager); + return (rv); + } + } + + CloseServiceHandle(schService); + CloseServiceHandle(schSCManager); + } + else /* osver.dwPlatformId != VER_PLATFORM_WIN32_NT */ + { + /* Store the launch command in the registry */ + launch_cmd = apr_psprintf(ptemp, "\"%s\" -n %s -k runservice", + exe_path, mpm_service_name); + rv = ap_regkey_open(&key, AP_REGKEY_LOCAL_MACHINE, SERVICECONFIG9X, + APR_READ | APR_WRITE | APR_CREATE, pconf); + if (rv == APR_SUCCESS) { + rv = ap_regkey_value_set(key, mpm_service_name, + launch_cmd, 0, pconf); + ap_regkey_close(key); + } + if (rv != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, rv, NULL, + "%s: Failed to add the RunServices registry entry.", + mpm_display_name); + return (rv); + } + + apr_snprintf(key_name, sizeof(key_name), SERVICECONFIG, mpm_service_name); + rv = ap_regkey_open(&key, AP_REGKEY_LOCAL_MACHINE, key_name, + APR_READ | APR_WRITE | APR_CREATE, pconf); + if (rv != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, rv, NULL, + "%s: Failed to create the registry service key.", + mpm_display_name); + return (rv); + } + rv = ap_regkey_value_set(key, "ImagePath", launch_cmd, 0, pconf); + if (rv != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, rv, NULL, + "%s: Failed to store ImagePath in the registry.", + mpm_display_name); + ap_regkey_close(key); + return (rv); + } + rv = ap_regkey_value_set(key, "DisplayName", + mpm_display_name, 0, pconf); + ap_regkey_close(key); + if (rv != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, rv, NULL, + "%s: Failed to store DisplayName in the registry.", + mpm_display_name); + return (rv); + } + } + + set_service_description(); + + /* For both WinNT & Win9x store the service ConfigArgs in the registry... + */ + apr_snprintf(key_name, sizeof(key_name), SERVICEPARAMS, mpm_service_name); + rv = ap_regkey_open(&key, AP_REGKEY_LOCAL_MACHINE, key_name, + APR_READ | APR_WRITE | APR_CREATE, pconf); + if (rv == APR_SUCCESS) { + rv = ap_regkey_value_array_set(key, "ConfigArgs", argc, argv, pconf); + ap_regkey_close(key); + } + if (rv != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, rv, NULL, + "%s: Failed to store the ConfigArgs in the registry.", + mpm_display_name); + return (rv); + } + fprintf(stderr,"The %s service is successfully installed.\n", mpm_display_name); + return APR_SUCCESS; +} + + +apr_status_t mpm_service_uninstall(void) +{ + char key_name[MAX_PATH]; + apr_status_t rv; + + if (osver.dwPlatformId == VER_PLATFORM_WIN32_NT) + { + SC_HANDLE schService; + SC_HANDLE schSCManager; + + fprintf(stderr,"Removing the %s service\n", mpm_display_name); + + schSCManager = OpenSCManager(NULL, NULL, /* local, default database */ + SC_MANAGER_CONNECT); + if (!schSCManager) { + rv = apr_get_os_error(); + ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, rv, NULL, + "Failed to open the WinNT service manager."); + return (rv); + } + + /* ###: utf-ize */ + schService = OpenService(schSCManager, mpm_service_name, DELETE); + + if (!schService) { + rv = apr_get_os_error(); + ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, rv, NULL, + "%s: OpenService failed", mpm_display_name); + return (rv); + } + + /* assure the service is stopped before continuing + * + * This may be out of order... we might not be able to be + * granted all access if the service is running anyway. + * + * And do we want to make it *this easy* for them + * to uninstall their service unintentionally? + */ + // ap_stop_service(schService); + + if (DeleteService(schService) == 0) { + rv = apr_get_os_error(); + ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, rv, NULL, + "%s: Failed to delete the service.", mpm_display_name); + return (rv); + } + + CloseServiceHandle(schService); + CloseServiceHandle(schSCManager); + } + else /* osver.dwPlatformId != VER_PLATFORM_WIN32_NT */ + { + apr_status_t rv2, rv3; + ap_regkey_t *key; + fprintf(stderr,"Removing the %s service\n", mpm_display_name); + + /* TODO: assure the service is stopped before continuing */ + + rv = ap_regkey_open(&key, AP_REGKEY_LOCAL_MACHINE, SERVICECONFIG9X, + APR_READ | APR_WRITE | APR_CREATE, pconf); + if (rv == APR_SUCCESS) { + rv = ap_regkey_value_remove(key, mpm_service_name, pconf); + ap_regkey_close(key); + } + if (rv != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, rv, NULL, + "%s: Failed to remove the RunServices registry " + "entry.", mpm_display_name); + } + + /* we blast Services/us, not just the Services/us/Parameters branch */ + apr_snprintf(key_name, sizeof(key_name), SERVICEPARAMS, mpm_service_name); + rv2 = ap_regkey_remove(AP_REGKEY_LOCAL_MACHINE, key_name, pconf); + apr_snprintf(key_name, sizeof(key_name), SERVICECONFIG, mpm_service_name); + rv3 = ap_regkey_remove(AP_REGKEY_LOCAL_MACHINE, key_name, pconf); + rv2 = (rv2 != APR_SUCCESS) ? rv2 : rv3; + if (rv2 != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, rv2, NULL, + "%s: Failed to remove the service config from the " + "registry.", mpm_display_name); + } + rv = (rv != APR_SUCCESS) ? rv : rv2; + if (rv != APR_SUCCESS) + return rv; + } + fprintf(stderr,"The %s service has been removed successfully.\n", mpm_display_name); + return APR_SUCCESS; +} + + +/* signal_service_transition is a simple thunk to signal the service + * and monitor its successful transition. If the signal passed is 0, + * then the caller is assumed to already have performed some service + * operation to be monitored (such as StartService), and no actual + * ControlService signal is sent. + */ + +static int signal_service_transition(SC_HANDLE schService, DWORD signal, DWORD pending, DWORD complete) +{ + if (signal && !ControlService(schService, signal, &globdat.ssStatus)) + return FALSE; + + do { + Sleep(1000); + if (!QueryServiceStatus(schService, &globdat.ssStatus)) + return FALSE; + } while (globdat.ssStatus.dwCurrentState == pending); + + return (globdat.ssStatus.dwCurrentState == complete); +} + + +apr_status_t mpm_service_start(apr_pool_t *ptemp, int argc, + const char * const * argv) +{ + apr_status_t rv; + + fprintf(stderr,"Starting the %s service\n", mpm_display_name); + + if (osver.dwPlatformId == VER_PLATFORM_WIN32_NT) + { + char **start_argv; + SC_HANDLE schService; + SC_HANDLE schSCManager; + + schSCManager = OpenSCManager(NULL, NULL, /* local, default database */ + SC_MANAGER_CONNECT); + if (!schSCManager) { + rv = apr_get_os_error(); + ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, rv, NULL, + "Failed to open the WinNT service manager"); + return (rv); + } + + /* ###: utf-ize */ + schService = OpenService(schSCManager, mpm_service_name, + SERVICE_START | SERVICE_QUERY_STATUS); + if (!schService) { + rv = apr_get_os_error(); + ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, rv, NULL, + "%s: Failed to open the service.", mpm_display_name); + CloseServiceHandle(schSCManager); + return (rv); + } + + if (QueryServiceStatus(schService, &globdat.ssStatus) + && (globdat.ssStatus.dwCurrentState == SERVICE_RUNNING)) { + ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, 0, NULL, + "Service %s is already started!", mpm_display_name); + CloseServiceHandle(schService); + CloseServiceHandle(schSCManager); + return 0; + } + + start_argv = malloc((argc + 1) * sizeof(const char **)); + memcpy(start_argv, argv, argc * sizeof(const char **)); + start_argv[argc] = NULL; + + rv = APR_EINIT; + /* ###: utf-ize */ + if (StartService(schService, argc, start_argv) + && signal_service_transition(schService, 0, /* test only */ + SERVICE_START_PENDING, + SERVICE_RUNNING)) + rv = APR_SUCCESS; + + if (rv != APR_SUCCESS) + rv = apr_get_os_error(); + + CloseServiceHandle(schService); + CloseServiceHandle(schSCManager); + } + else /* osver.dwPlatformId != VER_PLATFORM_WIN32_NT */ + { + STARTUPINFO si; /* Filled in prior to call to CreateProcess */ + PROCESS_INFORMATION pi; /* filled in on call to CreateProcess */ + char exe_path[MAX_PATH]; + char exe_cmd[MAX_PATH * 4]; + char *next_arg; + int i; + + /* Locate the active top level window named service_name + * provided the class is ApacheWin95ServiceMonitor + */ + if (FindWindow("ApacheWin95ServiceMonitor", mpm_service_name)) { + ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, 0, NULL, + "Service %s is already started!", mpm_display_name); + return 0; + } + + /* This may not appear intuitive, but Win9x will not allow a process + * to detach from the console without releasing the entire console. + * Ergo, we must spawn a new process for the service to get back our + * console window. + * The config is pre-flighted, so there should be no danger of failure. + */ + + if (GetModuleFileName(NULL, exe_path, sizeof(exe_path)) == 0) + { + apr_status_t rv = apr_get_os_error(); + ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, rv, NULL, + "GetModuleFileName failed"); + return rv; + } + + apr_snprintf(exe_cmd, sizeof(exe_cmd), + "\"%s\" -n %s -k runservice", + exe_path, mpm_service_name); + next_arg = strchr(exe_cmd, '\0'); + for (i = 0; i < argc; ++i) { + apr_snprintf(next_arg, sizeof(exe_cmd) - (next_arg - exe_cmd), + " \"%s\"", argv[i]); + next_arg = strchr(exe_cmd, '\0'); + } + + memset(&si, 0, sizeof(si)); + memset(&pi, 0, sizeof(pi)); + si.cb = sizeof(si); + si.dwFlags = STARTF_USESHOWWINDOW; + si.wShowWindow = SW_HIDE; /* This might be redundant */ + + rv = APR_EINIT; + if (CreateProcess(NULL, exe_cmd, NULL, NULL, FALSE, + DETACHED_PROCESS, /* Creation flags */ + NULL, NULL, &si, &pi)) + { + DWORD code; + while (GetExitCodeProcess(pi.hProcess, &code) == STILL_ACTIVE) { + if (FindWindow("ApacheWin95ServiceMonitor", mpm_service_name)) { + rv = APR_SUCCESS; + break; + } + Sleep (1000); + } + } + + if (rv != APR_SUCCESS) + rv = apr_get_os_error(); + + CloseHandle(pi.hProcess); + CloseHandle(pi.hThread); + } + + if (rv == APR_SUCCESS) + fprintf(stderr,"The %s service is running.\n", mpm_display_name); + else + ap_log_error(APLOG_MARK, APLOG_CRIT, rv, NULL, + "%s: Failed to start the service process.", + mpm_display_name); + + return rv; +} + + +/* signal is zero to stop, non-zero for restart */ + +void mpm_signal_service(apr_pool_t *ptemp, int signal) +{ + int success = FALSE; + + if (osver.dwPlatformId == VER_PLATFORM_WIN32_NT) + { + SC_HANDLE schService; + SC_HANDLE schSCManager; + + schSCManager = OpenSCManager(NULL, NULL, // default machine & database + SC_MANAGER_CONNECT); + + if (!schSCManager) { + ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, apr_get_os_error(), NULL, + "Failed to open the NT Service Manager"); + return; + } + + /* ###: utf-ize */ + schService = OpenService(schSCManager, mpm_service_name, + SERVICE_INTERROGATE | SERVICE_QUERY_STATUS | + SERVICE_USER_DEFINED_CONTROL | + SERVICE_START | SERVICE_STOP); + + if (schService == NULL) { + /* Could not open the service */ + ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, apr_get_os_error(), NULL, + "Failed to open the %s Service", mpm_display_name); + CloseServiceHandle(schSCManager); + return; + } + + if (!QueryServiceStatus(schService, &globdat.ssStatus)) { + ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_STARTUP, apr_get_os_error(), NULL, + "Query of Service %s failed", mpm_display_name); + CloseServiceHandle(schService); + CloseServiceHandle(schSCManager); + return; + } + + if (!signal && (globdat.ssStatus.dwCurrentState == SERVICE_STOPPED)) { + fprintf(stderr,"The %s service is not started.\n", mpm_display_name); + CloseServiceHandle(schService); + CloseServiceHandle(schSCManager); + return; + } + + fprintf(stderr,"The %s service is %s.\n", mpm_display_name, + signal ? "restarting" : "stopping"); + + if (!signal) + success = signal_service_transition(schService, + SERVICE_CONTROL_STOP, + SERVICE_STOP_PENDING, + SERVICE_STOPPED); + else if (globdat.ssStatus.dwCurrentState == SERVICE_STOPPED) { + mpm_service_start(ptemp, 0, NULL); + CloseServiceHandle(schService); + CloseServiceHandle(schSCManager); + return; + } + else + success = signal_service_transition(schService, + SERVICE_APACHE_RESTART, + SERVICE_START_PENDING, + SERVICE_RUNNING); + + CloseServiceHandle(schService); + CloseServiceHandle(schSCManager); + } + else /* !isWindowsNT() */ + { + DWORD service_pid; + HANDLE hwnd; + char prefix[20]; + /* Locate the active top level window named service_name + * provided the class is ApacheWin95ServiceMonitor + */ + hwnd = FindWindow("ApacheWin95ServiceMonitor", mpm_service_name); + if (hwnd && GetWindowThreadProcessId(hwnd, &service_pid)) + globdat.ssStatus.dwCurrentState = SERVICE_RUNNING; + else + { + globdat.ssStatus.dwCurrentState = SERVICE_STOPPED; + if (!signal) { + fprintf(stderr,"The %s service is not started.\n", mpm_display_name); + return; + } + } + + fprintf(stderr,"The %s service is %s.\n", mpm_display_name, + signal ? "restarting" : "stopping"); + + apr_snprintf(prefix, sizeof(prefix), "ap%ld", (long)service_pid); + setup_signal_names(prefix); + + if (!signal) + { + int ticks = 60; + ap_signal_parent(SIGNAL_PARENT_SHUTDOWN); + while (--ticks) + { + if (!IsWindow(hwnd)) { + success = TRUE; + break; + } + Sleep(1000); + } + } + else /* !stop */ + { + /* TODO: Aught to add a little test to the restart logic, and + * store the restart counter in the window's user dword. + * Then we can hang on and report a successful restart. But + * that's a project for another day. + */ + if (globdat.ssStatus.dwCurrentState == SERVICE_STOPPED) { + mpm_service_start(ptemp, 0, NULL); + return; + } + else { + success = TRUE; + ap_signal_parent(SIGNAL_PARENT_RESTART); + } + } + } + + if (success) + fprintf(stderr,"The %s service has %s.\n", mpm_display_name, + signal ? "restarted" : "stopped"); + else + fprintf(stderr,"Failed to %s the %s service.\n", + signal ? "restart" : "stop", mpm_display_name); +} |