/* * svcproc.cxx * * Service process implementation for Win95 and WinNT * * Portable Windows Library * * Copyright (c) 1993-1998 Equivalence Pty. Ltd. * * The contents of this file are subject to the Mozilla Public License * Version 1.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.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See * the License for the specific language governing rights and limitations * under the License. * * The Original Code is Portable Windows Library. * * The Initial Developer of the Original Code is Equivalence Pty. Ltd. * * Portions are Copyright (C) 1993 Free Software Foundation, Inc. * All Rights Reserved. * * Contributor(s): ______________________________________. * * $Log: svcproc.cxx,v $ * Revision 1.80 2005/11/30 12:47:42 csoutheren * Removed tabs, reformatted some code, and changed tags for Doxygen * * Revision 1.79 2005/10/21 06:03:51 csoutheren * Fixed warning on VS.NET 2005 * * Revision 1.78 2004/06/01 05:22:44 csoutheren * Restored memory check functionality * * Revision 1.77 2004/04/03 08:22:22 csoutheren * Remove pseudo-RTTI and replaced with real RTTI * * Revision 1.76 2004/04/03 06:54:30 rjongbloed * Many and various changes to support new Visual C++ 2003 * * Revision 1.75 2004/03/20 09:08:15 rjongbloed * Changed interaction between PTrace and PSystemLog so that the tracing code does * not need to know about the system log, thus reducing the code footprint for most apps. * * Revision 1.74 2003/09/17 09:02:14 csoutheren * Removed memory leak detection code * * Revision 1.73 2003/09/17 05:45:10 csoutheren * Removed recursive includes * * Revision 1.72 2003/02/11 07:58:40 robertj * Added ignore allocations so don't get meaningless memory leak at end of run * cause by system log stream not being deleted, thanks Sebastian Meyer * * Revision 1.71 2002/11/01 05:16:41 robertj * Fixed additional debug levels in PSystemLog to text file. * * Revision 1.70 2002/09/23 07:17:24 robertj * Changes to allow winsock2 to be included. * * Revision 1.69 2002/03/10 15:37:35 robertj * Added milliseconds to timestamp output * * Revision 1.68 2001/12/13 09:20:57 robertj * Fixed system log so does not crash if current thread not created by PWLib. * * Revision 1.67 2001/09/13 00:23:33 robertj * Fixed problem where system log output can occur with no current thread. * * Revision 1.66 2001/06/02 01:33:29 robertj * Thread name is always presend now so always use it in system log * output and make it wider in text output and tab area in window. * * Revision 1.65 2001/04/18 00:19:07 robertj * Removed newline from error code string in NT event. * * Revision 1.64 2001/04/16 23:04:25 craigs * Fixed problem with unknown command causing page fault due to missing comma in command list * * Revision 1.63 2001/04/12 01:34:02 robertj * Added threads to NT event log. * * Revision 1.62 2001/03/24 05:53:12 robertj * Added Windows 98 and ME to GetOSName() * * Revision 1.61 2001/03/24 05:37:01 robertj * Changed default directory of log file to same as executable. * Change tray icon to wait for service stop before displaying message. * Changed tray icon message display to not have date and thread. * * Revision 1.60 2001/03/23 05:35:34 robertj * Added ability for a service to output trace/system log to file while in debug mode. * Added saving of debug window position. * * Revision 1.59 2001/02/15 01:12:15 robertj * Moved some often repeated HTTP service code into PHTTPServiceProcess. * * Revision 1.58 2001/02/13 03:30:22 robertj * Added function to do heap validation. * * Revision 1.57 2000/05/02 03:16:46 robertj * Added display of thread name in SystemLog, thanks Ashley Unitt. * * Revision 1.56 1999/09/13 13:15:08 robertj * Changed PTRACE so will output to system log in PServiceProcess applications. * * Revision 1.55 1999/08/07 01:43:41 robertj * Added "NoWin" option to prevent display of window in command line commands. * * Revision 1.54 1999/07/16 03:22:16 robertj * Fixed tray icon version command so does not ask question. * * Revision 1.53 1999/06/14 07:59:39 robertj * Enhanced tracing again to add options to trace output (timestamps etc). * * Revision 1.52 1999/04/21 01:57:09 robertj * Added confirmation dialog to menu commands. * * Revision 1.51 1999/03/09 10:30:19 robertj * Fixed ability to have PMEMORY_CHECK on/off on both debug/release versions. * * Revision 1.50 1999/02/16 08:08:07 robertj * MSVC 6.0 compatibility changes. * * Revision 1.49 1999/01/29 12:20:19 robertj * Changed service process to output trace info to the Win32 debug output. * * Revision 1.48 1998/12/04 10:10:45 robertj * Added virtual for determining if process is a service. Fixes linkage problem. * * Revision 1.47 1998/11/30 04:50:17 robertj * New directory structure * * Revision 1.46 1998/10/18 14:28:34 robertj * Renamed argv/argc to eliminate accidental usage. * Fixed strange problem withs etting debug window tabstops in optimised version. * * Revision 1.45 1998/10/13 14:14:09 robertj * Added thread ID to log. * Added heap debug display to service menus. * * Revision 1.44 1998/09/24 03:30:57 robertj * Added open software license. * * Revision 1.43 1998/08/20 06:06:03 robertj * Fixed bug where web page can be asked for when service not running. * * Revision 1.42 1998/05/21 04:29:44 robertj * Fixed "Proxies stopped" dialog appearing when shutting down windows. * * Revision 1.41 1998/05/07 05:21:38 robertj * Improved formatting of debug window, adding tabs and tab stops. * * Revision 1.40 1998/04/07 13:32:14 robertj * Changed startup code to support PApplication class. * * Revision 1.39 1998/04/01 01:52:53 robertj * Fixed problem with NoAutoDelete threads. * * Revision 1.38 1998/03/29 06:16:53 robertj * Rearranged initialisation sequence so PProcess descendent constructors can do "things". * * Revision 1.37 1998/03/20 03:20:45 robertj * Lined up debug output. * * Revision 1.36 1998/03/05 12:49:55 robertj * MemCheck fixes. * * Revision 1.35 1998/02/20 23:01:10 robertj * Fixed bug where application exits on log out in win95. * * Revision 1.34 1998/02/16 01:43:57 robertj * Really fixed spurious error display on install/start/stop etc * * Revision 1.33 1998/02/16 00:12:22 robertj * Added tray icon support. * Fixed problem with services and directory paths with spaces in them. * * Revision 1.32 1998/02/03 06:16:31 robertj * Added extra log levels. * Fixed bug where window disappears after debug service termination. * * Revision 1.31 1998/01/26 00:56:11 robertj * Changed ServiceProcess to exclusively use named event to detect running process. * * Revision 1.30 1997/12/18 05:05:45 robertj * Added Edit menu. * * Revision 1.29 1997/11/04 06:01:45 robertj * Fix of "service hung at startup" message for NT service. * * Revision 1.28 1997/10/30 10:17:10 robertj * Fixed bug in detection of running service. * * Revision 1.27 1997/10/03 15:14:17 robertj * Fixed crash on exit. * * Revision 1.26 1997/08/28 12:50:32 robertj * Fixed race condition in cleaning up threads on application termination. * * Revision 1.25 1997/07/17 12:43:29 robertj * Fixed bug for auto-start of service under '95. * * Revision 1.24 1997/07/14 11:47:20 robertj * Added "const" to numerous variables. * * Revision 1.23 1997/07/08 13:00:30 robertj * DLL support. * Fixed '95 support so service runs without logging in. * * Revision 1.22 1997/04/27 05:50:27 robertj * DLL support. * * Revision 1.21 1997/03/18 21:23:27 robertj * Fix service manager falsely accusing app of crashing if OnStart() is slow. * * Revision 1.20 1997/02/05 11:50:40 robertj * Changed current process function to return reference and validate objects descendancy. * Changed log file name calculation to occur only once. * Added some MSVC memory debugging functions. * * Revision 1.19 1996/12/05 11:53:49 craigs * Fixed failure to output PError to debug window if CRLF pairs used * * Revision 1.18 1996/11/30 12:07:19 robertj * Changed service creation for NT so is auto-start, * * Revision 1.17 1996/11/18 11:32:04 robertj * Fixed bug in doing a "stop" command closing ALL instances of service. * * Revision 1.16 1996/11/12 10:15:16 robertj * Fixed bug in NT 3.51 locking up when needs to output to window. * * Revision 1.15 1996/11/10 21:04:32 robertj * Added category names to event log. * Fixed menu enables for debug and command modes. * * Revision 1.14 1996/11/04 03:39:13 robertj * Improved detection of running service so debug mode cannot run. * * Revision 1.13 1996/10/31 12:54:01 robertj * Fixed bug in window not being displayed when command line used. * * Revision 1.12 1996/10/18 11:22:14 robertj * Fixed problems with window not being shown under NT. * * Revision 1.11 1996/10/14 03:09:58 robertj * Fixed major bug in debug outpuit locking up (infinite loop) * Changed menus so cannot start service if in debug mode * * Revision 1.10 1996/10/08 13:04:43 robertj * Rewrite to use standard window isntead of console window. * * Revision 1.9 1996/09/16 12:56:27 robertj * DLL support * * Revision 1.8 1996/09/14 12:34:23 robertj * Fixed problem with spontaneous exit from app under Win95. * * Revision 1.7 1996/08/19 13:36:03 robertj * Added "Debug" level to system log. * * Revision 1.6 1996/07/30 12:23:32 robertj * Added better service running test. * Changed SIGINTR handler to just set termination event. * * Revision 1.5 1996/07/27 04:07:57 robertj * Changed thread creation to use C library function instead of direct WIN32. * Changed SystemLog to be stream based rather than printf based. * Fixed Win95 support for service start/stop and prevent multiple starts. * * Revision 1.4 1996/06/10 09:54:08 robertj * Fixed Win95 service install bug (typo!) * * Revision 1.3 1996/05/30 11:49:10 robertj * Fixed crash on exit bug. * * Revision 1.2 1996/05/23 10:03:21 robertj * Windows 95 support. * * Revision 1.1 1996/05/15 21:11:51 robertj * Initial revision * */ #include #include #include #include #include #include #include #include #include #ifdef __USE_STL__ #include #else #include #endif #include #include #define UWM_SYSTRAY (WM_USER + 1) #define ICON_RESID 1 #define SYSTRAY_ICON_ID 1 static HINSTANCE hInstance; #define DATE_WIDTH 72 #define THREAD_WIDTH 80 #define LEVEL_WIDTH 32 #define PROTO_WIDTH 40 #define ACTION_WIDTH 48 enum { SvcCmdTray, SvcCmdNoTray, SvcCmdVersion, SvcCmdInstall, SvcCmdRemove, SvcCmdStart, SvcCmdStop, SvcCmdPause, SvcCmdResume, SvcCmdDeinstall, SvcCmdNoWindow, NumSvcCmds }; static const char * const ServiceCommandNames[NumSvcCmds] = { "Tray", "NoTray", "Version", "Install", "Remove", "Start", "Stop", "Pause", "Resume", "Deinstall", "NoWin" }; class PNotifyIconData : public NOTIFYICONDATA { public: PNotifyIconData(HWND hWnd, UINT flags, const char * tip = NULL); void Add() { Shell_NotifyIcon(NIM_ADD, this); } void Delete() { Shell_NotifyIcon(NIM_DELETE, this); } void Modify() { Shell_NotifyIcon(NIM_MODIFY, this); } }; PNotifyIconData::PNotifyIconData(HWND window, UINT flags, const char * tip) { cbSize = sizeof(NOTIFYICONDATA); hWnd = window; uID = SYSTRAY_ICON_ID; uFlags = flags; if (tip != NULL) { strncpy(szTip, tip, sizeof(szTip)-1); szTip[sizeof(szTip)-1] = '\0'; uFlags |= NIF_TIP; } } enum TrayIconRegistryCommand { AddTrayIcon, DelTrayIcon, CheckTrayIcon }; static BOOL TrayIconRegistry(PServiceProcess * svc, TrayIconRegistryCommand cmd) { HKEY key; if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run", 0, KEY_ALL_ACCESS, &key) != ERROR_SUCCESS) return FALSE; DWORD err = 1; DWORD type; DWORD len; PString str; switch (cmd) { case CheckTrayIcon : err = RegQueryValueEx(key, svc->GetName(), 0, &type, NULL, &len); break; case AddTrayIcon : str = "\"" + svc->GetFile() + "\" Tray"; err = RegSetValueEx(key, svc->GetName(), 0, REG_SZ, (LPBYTE)(const char *)str, str.GetLength() + 1); break; case DelTrayIcon : err = RegDeleteValue(key, (char *)(const char *)svc->GetName()); } RegCloseKey(key); return err == ERROR_SUCCESS; } /////////////////////////////////////////////////////////////////////////////// // PSystemLog void PSystemLog::Output(Level level, const char * msg) { PServiceProcess & process = PServiceProcess::Current(); if (level > process.GetLogLevel()) return; DWORD err = ::GetLastError(); if (process.isWin95 || process.controlWindow != NULL) { static HANDLE mutex = CreateMutex(NULL, FALSE, NULL); WaitForSingleObject(mutex, INFINITE); ostream * out; if (!process.systemLogFileName) out = new ofstream(process.systemLogFileName, ios::app); else out = new PStringStream; PTime now; *out << now.AsString("yyyy/MM/dd hh:mm:ss.uuu\t"); PThread * thread = PThread::Current(); if (thread == NULL) *out << "ThreadID=0x" << setfill('0') << hex << setw(8) << GetCurrentThreadId() << setfill(' ') << dec; else { PString threadName = thread->GetThreadName(); if (threadName.GetLength() <= 23) *out << setw(23) << threadName; else *out << threadName.Left(10) << "..." << threadName.Right(10); } *out << '\t'; if (level < 0) *out << "Message"; else { static const char * const levelName[4] = { "Fatal error", "Error", "Warning", "Info" }; if (level < PARRAYSIZE(levelName)) *out << levelName[level]; else *out << "Debug" << (level-Info); } *out << '\t' << msg; if (level < Info && err != 0) *out << " - error = " << err << endl; else if (msg[0] == '\0' || msg[strlen(msg)-1] != '\n') *out << endl; if (process.systemLogFileName.IsEmpty()) process.DebugOutput(*(PStringStream*)out); delete out; ReleaseMutex(mutex); SetLastError(0); } else { // Use event logging to log the error. HANDLE hEventSource = RegisterEventSource(NULL, process.GetName()); if (hEventSource == NULL) return; PString threadName; PThread * thread = PThread::Current(); if (thread != NULL) threadName = thread->GetThreadName(); else threadName.sprintf("%u", GetCurrentThreadId()); char thrdbuf[16]; if (threadName.IsEmpty()) sprintf(thrdbuf, "0x%08X", thread); else { strncpy(thrdbuf, threadName, sizeof(thrdbuf)-1); thrdbuf[sizeof(thrdbuf)-1] = '\0'; } char errbuf[25]; if (level > StdError && level < Info && err != 0) ::sprintf(errbuf, "Error code = %d", err); else errbuf[0] = '\0'; LPCTSTR strings[4]; strings[0] = thrdbuf; strings[1] = msg; strings[2] = errbuf; strings[3] = level != Fatal ? "" : " Program aborted."; static const WORD levelType[Info+1] = { EVENTLOG_INFORMATION_TYPE, EVENTLOG_ERROR_TYPE, EVENTLOG_ERROR_TYPE, EVENTLOG_WARNING_TYPE }; ReportEvent(hEventSource, // handle of event source (WORD)(level < Info ? levelType[level+1] : EVENTLOG_INFORMATION_TYPE), // event type (WORD)(level+1), // event category 0x1000, // event ID NULL, // current user's SID PARRAYSIZE(strings), // number of strings 0, // no bytes of raw data strings, // array of error strings NULL); // no raw data DeregisterEventSource(hEventSource); } } int PSystemLog::Buffer::overflow(int c) { if (pptr() >= epptr()) { #if PMEMORY_CHECK BOOL previousIgnoreAllocations = PMemoryHeap::SetIgnoreAllocations(TRUE); #endif int ppos = pptr() - pbase(); char * newptr = string.GetPointer(string.GetSize() + 10); setp(newptr, newptr + string.GetSize() - 1); pbump(ppos); #if PMEMORY_CHECK PMemoryHeap::SetIgnoreAllocations(previousIgnoreAllocations); #endif } if (c != EOF) { *pptr() = (char)c; pbump(1); } return 0; } int PSystemLog::Buffer::underflow() { return EOF; } int PSystemLog::Buffer::sync() { Level logLevel; if (log->width() == 0 || (PTrace::GetOptions()&PTrace::SystemLogStream) == 0) logLevel = log->logLevel; else { // Trace system sets the ios stream width as the last thing it does before // doing a flush, which gets us here. SO now we can get a PTRACE looking // exactly like a PSYSTEMLOG of appropriate level. unsigned traceLevel = log->width() -1 + PSystemLog::Warning; log->width(0); if (traceLevel >= PSystemLog::NumLogLevels) traceLevel = PSystemLog::NumLogLevels-1; logLevel = (Level)traceLevel; } PSystemLog::Output(logLevel, string); #if PMEMORY_CHECK BOOL previousIgnoreAllocations = PMemoryHeap::SetIgnoreAllocations(TRUE); #endif string.SetSize(10); char * base = string.GetPointer(); *base = '\0'; setp(base, base + string.GetSize() - 1); #if PMEMORY_CHECK PMemoryHeap::SetIgnoreAllocations(previousIgnoreAllocations); #endif return 0; } /////////////////////////////////////////////////////////////////////////////// // PServiceProcess PServiceProcess::PServiceProcess(const char * manuf, const char * name, WORD major, WORD minor, CodeStatus stat, WORD build) : PProcess(manuf, name, major, minor, stat, build), systemLogFileName(GetFile().GetDirectory() + GetName() + " Log.TXT") { controlWindow = debugWindow = NULL; currentLogLevel = PSystemLog::Warning; } PServiceProcess & PServiceProcess::Current() { PServiceProcess & process = (PServiceProcess &)PProcess::Current(); PAssert(PIsDescendant(&process, PServiceProcess), "Not a service!"); return process; } const char * PServiceProcess::GetServiceDependencies() const { return "EventLog\0"; } BOOL PServiceProcess::IsServiceProcess() const { return TRUE; } static BOOL IsServiceRunning(PServiceProcess * svc) { HANDLE hEvent = OpenEvent(EVENT_MODIFY_STATE, FALSE, svc->GetName()); if (hEvent == NULL) return ::GetLastError() == ERROR_ACCESS_DENIED; CloseHandle(hEvent); return TRUE; } int PServiceProcess::_main(void * arg) { #if PMEMORY_CHECK PMemoryHeap::SetIgnoreAllocations(TRUE); #endif PSetErrorStream(new PSystemLog(PSystemLog::StdError)); PTrace::SetStream(new PSystemLog(PSystemLog::Debug3)); PTrace::ClearOptions(PTrace::FileAndLine); PTrace::SetOptions(PTrace::SystemLogStream); PTrace::SetLevel(4); #if PMEMORY_CHECK PMemoryHeap::SetIgnoreAllocations(FALSE); #endif hInstance = (HINSTANCE)arg; OSVERSIONINFO verinfo; verinfo.dwOSVersionInfoSize = sizeof(verinfo); GetVersionEx(&verinfo); switch (verinfo.dwPlatformId) { case VER_PLATFORM_WIN32_NT : isWin95 = FALSE; break; case VER_PLATFORM_WIN32_WINDOWS : isWin95 = TRUE; break; default : PError << "Unsupported Win32 platform type!" << endl; return 1; } debugMode = arguments.GetCount() > 0 && stricmp(arguments[0], "Debug") == 0; currentLogLevel = debugMode ? PSystemLog::Info : PSystemLog::Warning; if (!debugMode && arguments.GetCount() > 0) { for (PINDEX a = 0; a < arguments.GetCount(); a++) ProcessCommand(arguments[a]); if (controlWindow == NULL || controlWindow == (HWND)-1) return GetTerminationValue(); if (debugWindow != NULL && debugWindow != (HWND)-1) { ::SetLastError(0); PError << "Close window or select another command from the Control menu.\n" << endl; } MSG msg; while (GetMessage(&msg, NULL, 0, 0) != 0) { TranslateMessage(&msg); DispatchMessage(&msg); } return GetTerminationValue(); } if (!debugMode && !isWin95) { static SERVICE_TABLE_ENTRY dispatchTable[] = { { "", PServiceProcess::StaticMainEntry }, { NULL, NULL } }; dispatchTable[0].lpServiceName = (char *)(const char *)GetName(); if (StartServiceCtrlDispatcher(dispatchTable)) return GetTerminationValue(); PSystemLog::Output(PSystemLog::Fatal, "StartServiceCtrlDispatcher failed."); MessageBox(NULL, "Not run as a service!", GetName(), MB_TASKMODAL); return 1; } if (!CreateControlWindow(debugMode)) return 1; if (IsServiceRunning(this)) { MessageBox(NULL, "Service already running", GetName(), MB_TASKMODAL); return 3; } if (debugMode) { ::SetLastError(0); PError << "Service simulation started for \"" << GetName() << "\" version " << GetVersion(TRUE) << "\n" "Close window to terminate.\n" << endl; } terminationEvent = CreateEvent(NULL, TRUE, FALSE, GetName()); PAssertOS(terminationEvent != NULL); threadHandle = (HANDLE)_beginthread(StaticThreadEntry, 0, this); PAssertOS(threadHandle != (HANDLE)-1); SetTerminationValue(0); MSG msg; msg.message = WM_QUIT+1; //Want somethingthat is not WM_QUIT do { switch (MsgWaitForMultipleObjects(1, &terminationEvent, FALSE, INFINITE, QS_ALLINPUT)) { case WAIT_OBJECT_0+1 : while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) { if (msg.message != WM_QUIT) { TranslateMessage(&msg); DispatchMessage(&msg); } } break; default : // This is a work around for '95 coming up with an erroneous error if (::GetLastError() == ERROR_INVALID_HANDLE && WaitForSingleObject(terminationEvent, 0) == WAIT_TIMEOUT) break; // Else fall into next case case WAIT_OBJECT_0 : if (!debugMode || controlWindow == NULL) msg.message = WM_QUIT; else { PError << "nService simulation stopped for \"" << GetName() << "\".\n\n" "Close window to terminate.\n" << endl; ResetEvent(terminationEvent); } } } while (msg.message != WM_QUIT); if (controlWindow != NULL) DestroyWindow(controlWindow); // Set thread ID for process to this thread activeThreadMutex.Wait(); activeThreads.SetAt(threadId, NULL); threadId = GetCurrentThreadId(); threadHandle = GetCurrentThread(); activeThreads.SetAt(threadId, this); activeThreadMutex.Signal(); OnStop(); return GetTerminationValue(); } enum { ExitMenuID = 100, HideMenuID, ControlMenuID, CopyMenuID, CutMenuID, DeleteMenuID, SelectAllMenuID, #if PMEMORY_CHECK MarkMenuID, DumpMenuID, StatsMenuID, ValidateMenuID, #endif OutputToMenuID, WindowOutputMenuID, SvcCmdBaseMenuID = 1000, LogLevelBaseMenuID = 2000 }; static const char ServiceSimulationSectionName[] = "Service Simulation Parameters"; static const char WindowLeftKey[] = "Window Left"; static const char WindowTopKey[] = "Window Top"; static const char WindowRightKey[] = "Window Right"; static const char WindowBottomKey[] = "Window Bottom"; static const char SystemLogFileNameKey[] = "System Log File Name"; BOOL PServiceProcess::CreateControlWindow(BOOL createDebugWindow) { if (controlWindow != NULL) return TRUE; WNDCLASS wclass; wclass.style = CS_HREDRAW|CS_VREDRAW; wclass.lpfnWndProc = (WNDPROC)StaticWndProc; wclass.cbClsExtra = 0; wclass.cbWndExtra = 0; wclass.hInstance = hInstance; wclass.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(ICON_RESID)); wclass.hCursor = NULL; wclass.hbrBackground = (HBRUSH)(COLOR_WINDOW+1); wclass.lpszMenuName = NULL; wclass.lpszClassName = GetName(); if (RegisterClass(&wclass) == 0) return FALSE; HMENU menubar = CreateMenu(); HMENU menu = CreatePopupMenu(); AppendMenu(menu, MF_STRING, OutputToMenuID, "&Output To..."); AppendMenu(menu, MF_STRING, WindowOutputMenuID, "&Output To Window"); AppendMenu(menu, MF_SEPARATOR, 0, NULL); AppendMenu(menu, MF_STRING, ControlMenuID, "&Control"); AppendMenu(menu, MF_STRING, HideMenuID, "&Hide"); AppendMenu(menu, MF_STRING, SvcCmdBaseMenuID+SvcCmdVersion, "&Version"); AppendMenu(menu, MF_SEPARATOR, 0, NULL); #if PMEMORY_CHECK AppendMenu(menu, MF_STRING, MarkMenuID, "&Mark Memory"); AppendMenu(menu, MF_STRING, DumpMenuID, "&Dump Memory"); AppendMenu(menu, MF_STRING, StatsMenuID, "&Statistics"); AppendMenu(menu, MF_STRING, ValidateMenuID, "&Validate Heap"); AppendMenu(menu, MF_SEPARATOR, 0, NULL); #endif AppendMenu(menu, MF_STRING, ExitMenuID, "E&xit"); AppendMenu(menubar, MF_POPUP, (UINT)menu, "&File"); menu = CreatePopupMenu(); AppendMenu(menu, MF_STRING, CopyMenuID, "&Copy"); AppendMenu(menu, MF_STRING, CutMenuID, "C&ut"); AppendMenu(menu, MF_STRING, DeleteMenuID, "&Delete"); AppendMenu(menu, MF_SEPARATOR, 0, NULL); AppendMenu(menu, MF_STRING, SelectAllMenuID, "&Select All"); AppendMenu(menubar, MF_POPUP, (UINT)menu, "&Edit"); menu = CreatePopupMenu(); AppendMenu(menu, MF_STRING, SvcCmdBaseMenuID+SvcCmdInstall, "&Install"); AppendMenu(menu, MF_STRING, SvcCmdBaseMenuID+SvcCmdRemove, "&Remove"); AppendMenu(menu, MF_STRING, SvcCmdBaseMenuID+SvcCmdDeinstall, "&Deinstall"); AppendMenu(menu, MF_STRING, SvcCmdBaseMenuID+SvcCmdStart, "&Start"); AppendMenu(menu, MF_STRING, SvcCmdBaseMenuID+SvcCmdStop, "S&top"); AppendMenu(menu, MF_STRING, SvcCmdBaseMenuID+SvcCmdPause, "&Pause"); AppendMenu(menu, MF_STRING, SvcCmdBaseMenuID+SvcCmdResume, "R&esume"); AppendMenu(menubar, MF_POPUP, (UINT)menu, "&Control"); menu = CreatePopupMenu(); AppendMenu(menu, MF_STRING, LogLevelBaseMenuID+PSystemLog::Fatal, "&Fatal Error"); AppendMenu(menu, MF_STRING, LogLevelBaseMenuID+PSystemLog::Error, "&Error"); AppendMenu(menu, MF_STRING, LogLevelBaseMenuID+PSystemLog::Warning, "&Warning"); AppendMenu(menu, MF_STRING, LogLevelBaseMenuID+PSystemLog::Info, "&Information"); AppendMenu(menu, MF_STRING, LogLevelBaseMenuID+PSystemLog::Debug, "&Debug"); AppendMenu(menu, MF_STRING, LogLevelBaseMenuID+PSystemLog::Debug2, "Debug &2"); AppendMenu(menu, MF_STRING, LogLevelBaseMenuID+PSystemLog::Debug3, "Debug &3"); AppendMenu(menubar, MF_POPUP, (UINT)menu, "&Log Level"); if (CreateWindow(GetName(), GetName(), WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, menubar, hInstance, NULL) == NULL) return FALSE; if (createDebugWindow && debugWindow == NULL) { PConfig cfg(ServiceSimulationSectionName); int l = cfg.GetInteger(WindowLeftKey, -1); int t = cfg.GetInteger(WindowTopKey, -1); int r = cfg.GetInteger(WindowRightKey, -1); int b = cfg.GetInteger(WindowBottomKey, -1); if (l > 0 && t > 0 && r > 0 && b > 0) SetWindowPos(controlWindow, NULL, l, t, r-l, b-t, 0); debugWindow = CreateWindow("edit", "", WS_CHILD|WS_HSCROLL|WS_VSCROLL|WS_VISIBLE|WS_BORDER| ES_MULTILINE|ES_READONLY, 0, 0, 0, 0, controlWindow, (HMENU)10, hInstance, NULL); SendMessage(debugWindow, EM_SETLIMITTEXT, isWin95 ? 32000 : 128000, 0); DWORD TabStops[] = { DATE_WIDTH, DATE_WIDTH+THREAD_WIDTH, DATE_WIDTH+THREAD_WIDTH+LEVEL_WIDTH, DATE_WIDTH+THREAD_WIDTH+LEVEL_WIDTH+PROTO_WIDTH, DATE_WIDTH+THREAD_WIDTH+LEVEL_WIDTH+PROTO_WIDTH+ACTION_WIDTH, DATE_WIDTH+THREAD_WIDTH+LEVEL_WIDTH+PROTO_WIDTH+ACTION_WIDTH+32 // Standard tab width }; SendMessage(debugWindow, EM_SETTABSTOPS, PARRAYSIZE(TabStops), (LPARAM)(LPDWORD)TabStops); systemLogFileName = cfg.GetString(SystemLogFileNameKey); if (!systemLogFileName) { PFile::Remove(systemLogFileName); DebugOutput("Sending all system log output to \"" + systemLogFileName + "\".\n"); } } return TRUE; } LPARAM WINAPI PServiceProcess::StaticWndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) { return Current().WndProc(hWnd, msg, wParam, lParam); } static void SaveWindowPosition(HWND hWnd) { RECT r; GetWindowRect(hWnd, &r); PConfig cfg(ServiceSimulationSectionName); cfg.SetInteger(WindowLeftKey, r.left); cfg.SetInteger(WindowTopKey, r.top); cfg.SetInteger(WindowRightKey, r.right); cfg.SetInteger(WindowBottomKey, r.bottom); } LPARAM PServiceProcess::WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) { #ifdef _DEBUG static DWORD allocationNumber; #endif switch (msg) { case WM_CREATE : controlWindow = hWnd; break; case WM_DESTROY : if (debugWindow == (HWND)-1) { PNotifyIconData nid(hWnd, NIF_TIP); nid.Delete(); // This removes the systray icon } controlWindow = debugWindow = NULL; PostQuitMessage(0); break; case WM_ENDSESSION : if (wParam && (debugMode || lParam != ENDSESSION_LOGOFF) && debugWindow != (HWND)-1) OnStop(); return 0; case WM_MOVE : if (debugWindow != NULL) SaveWindowPosition(hWnd); break; case WM_SIZE : if (debugWindow != NULL && debugWindow != (HWND)-1) { SaveWindowPosition(hWnd); MoveWindow(debugWindow, 0, 0, LOWORD(lParam), HIWORD(lParam), TRUE); } break; case WM_INITMENUPOPUP : { int enableItems = MF_BYCOMMAND|(debugMode ? MF_ENABLED : MF_GRAYED); for (int i = PSystemLog::Fatal; i < PSystemLog::NumLogLevels; i++) { CheckMenuItem((HMENU)wParam, LogLevelBaseMenuID+i, MF_BYCOMMAND|MF_UNCHECKED); EnableMenuItem((HMENU)wParam, LogLevelBaseMenuID+i, enableItems); } CheckMenuItem((HMENU)wParam, LogLevelBaseMenuID+GetLogLevel(), MF_BYCOMMAND|MF_CHECKED); enableItems = MF_BYCOMMAND|(debugMode ? MF_GRAYED : MF_ENABLED); EnableMenuItem((HMENU)wParam, SvcCmdBaseMenuID+SvcCmdStart, enableItems); EnableMenuItem((HMENU)wParam, SvcCmdBaseMenuID+SvcCmdStop, enableItems); EnableMenuItem((HMENU)wParam, SvcCmdBaseMenuID+SvcCmdPause, enableItems); EnableMenuItem((HMENU)wParam, SvcCmdBaseMenuID+SvcCmdResume, enableItems); DWORD start, finish; if (debugWindow != NULL && debugWindow != (HWND)-1) SendMessage(debugWindow, EM_GETSEL, (WPARAM)&start, (LPARAM)&finish); else start = finish = 0; enableItems = MF_BYCOMMAND|(start == finish ? MF_GRAYED : MF_ENABLED); EnableMenuItem((HMENU)wParam, CopyMenuID, enableItems); EnableMenuItem((HMENU)wParam, CutMenuID, enableItems); EnableMenuItem((HMENU)wParam, DeleteMenuID, enableItems); enableItems = MF_BYCOMMAND|(IsServiceRunning(this) ? MF_ENABLED : MF_GRAYED); EnableMenuItem((HMENU)wParam, ControlMenuID, enableItems); break; } case WM_COMMAND : switch (wParam) { case ExitMenuID : DestroyWindow(hWnd); break; case ControlMenuID : if (IsServiceRunning(this)) OnControl(); break; case HideMenuID : ShowWindow(hWnd, SW_HIDE); break; #if PMEMORY_CHECK case MarkMenuID : allocationNumber = PMemoryHeap::GetAllocationRequest(); break; case DumpMenuID : PMemoryHeap::DumpObjectsSince(allocationNumber); break; case StatsMenuID : PMemoryHeap::DumpStatistics(); break; case ValidateMenuID : PMemoryHeap::ValidateHeap(); break; #endif case CopyMenuID : if (debugWindow != NULL && debugWindow != (HWND)-1) SendMessage(debugWindow, WM_COPY, 0, 0); break; case CutMenuID : if (debugWindow != NULL && debugWindow != (HWND)-1) SendMessage(debugWindow, WM_CUT, 0, 0); break; case DeleteMenuID : if (debugWindow != NULL && debugWindow != (HWND)-1) SendMessage(debugWindow, WM_CLEAR, 0, 0); break; case SelectAllMenuID : if (debugWindow != NULL && debugWindow != (HWND)-1) SendMessage(debugWindow, EM_SETSEL, 0, -1); break; case OutputToMenuID : if (debugWindow != NULL && debugWindow != (HWND)-1) { char fileBuffer[_MAX_PATH]; OPENFILENAME fileDlgInfo; memset(&fileDlgInfo, 0, sizeof(fileDlgInfo)); fileDlgInfo.lStructSize = sizeof(fileDlgInfo); fileDlgInfo.hwndOwner = hWnd; fileDlgInfo.hInstance = hInstance; fileBuffer[0] = '\0'; fileDlgInfo.lpstrFile = fileBuffer; char customFilter[100]; strcpy(customFilter, "All Files"); memcpy(&customFilter[strlen(customFilter)+1], "*.*\0", 5); fileDlgInfo.lpstrCustomFilter = customFilter; fileDlgInfo.nMaxCustFilter = sizeof(customFilter); fileDlgInfo.nMaxFile = sizeof(fileBuffer); fileDlgInfo.Flags = OFN_ENABLEHOOK|OFN_HIDEREADONLY|OFN_NOVALIDATE|OFN_EXPLORER|OFN_CREATEPROMPT; fileDlgInfo.lCustData = (DWORD)this; if (GetSaveFileName(&fileDlgInfo)) { if (systemLogFileName != fileBuffer) { systemLogFileName = fileBuffer; PFile::Remove(systemLogFileName); PConfig cfg(ServiceSimulationSectionName); cfg.SetString(SystemLogFileNameKey, systemLogFileName); DebugOutput("Sending all system log output to \"" + systemLogFileName + "\".\n"); PError << "Logging started for \"" << GetName() << "\" version " << GetVersion(TRUE) << endl; } } } break; case WindowOutputMenuID : if (!systemLogFileName) { PError << "Logging stopped." << endl; DebugOutput("System log output to \"" + systemLogFileName + "\" stopped.\n"); systemLogFileName = PString(); PConfig cfg(ServiceSimulationSectionName); cfg.SetString(SystemLogFileNameKey, ""); } break; default : if (wParam >= LogLevelBaseMenuID+PSystemLog::Fatal && wParam < LogLevelBaseMenuID+PSystemLog::NumLogLevels) { SetLogLevel((PSystemLog::Level)(wParam-LogLevelBaseMenuID)); #if PTRACING PTrace::SetLevel(wParam-LogLevelBaseMenuID-PSystemLog::Warning); #endif } else if (wParam >= SvcCmdBaseMenuID && wParam < SvcCmdBaseMenuID+NumSvcCmds) { const char * cmdname = ServiceCommandNames[wParam-SvcCmdBaseMenuID]; if (wParam == SvcCmdBaseMenuID+SvcCmdVersion || MessageBox(hWnd, cmdname & GetName() & "?", GetName(), MB_ICONQUESTION|MB_YESNO) == IDYES) ProcessCommand(cmdname); } } break; // Notification of event over sysTray icon case UWM_SYSTRAY : switch (lParam) { case WM_MOUSEMOVE : // update status of process for tool tips if no buttons down if (wParam == SYSTRAY_ICON_ID) { PNotifyIconData nid(hWnd, NIF_TIP, GetName() & (IsServiceRunning(this) ? "is" : "not") & "running."); nid.Modify(); // Modify tooltip } break; // Click on icon - display message case WM_LBUTTONDBLCLK : if (IsServiceRunning(this)) OnControl(); else { SetForegroundWindow(hWnd); // Our MessageBox pops up in front MessageBox(hWnd, "Service is not running!", GetName(), MB_TASKMODAL); } break; // Popup menu case WM_RBUTTONUP : POINT pt; GetCursorPos(&pt); HMENU menu = CreatePopupMenu(); AppendMenu(menu, MF_STRING, ControlMenuID, "&Open Properties"); AppendMenu(menu, MF_SEPARATOR, 0, NULL); AppendMenu(menu, MF_STRING, SvcCmdBaseMenuID+SvcCmdVersion, "&Version"); if (IsServiceRunning(this)) { MENUITEMINFO inf; inf.cbSize = sizeof(inf); inf.fMask = MIIM_STATE; inf.fState = MFS_DEFAULT; SetMenuItemInfo(menu, ControlMenuID, FALSE, &inf); AppendMenu(menu, MF_STRING, SvcCmdBaseMenuID+SvcCmdStop, "&Stop Service"); } else { EnableMenuItem(menu, ControlMenuID, MF_GRAYED); AppendMenu(menu, MF_STRING, SvcCmdBaseMenuID+SvcCmdStart, "&Start Service"); } AppendMenu(menu, MF_STRING, SvcCmdBaseMenuID+SvcCmdNoTray, "&Tray Icon"); CheckMenuItem(menu, SvcCmdBaseMenuID+SvcCmdNoTray, TrayIconRegistry(this, CheckTrayIcon) ? MF_CHECKED : MF_UNCHECKED); AppendMenu(menu, MF_SEPARATOR, 0, NULL); AppendMenu(menu, MF_STRING, ExitMenuID, "&Close"); /* SetForegroundWindow and the ensuing null PostMessage is a workaround for a Windows 95 bug (see MSKB article Q135788, http://www.microsoft.com/kb/articles/q135/7/88.htm, I think). In typical Microsoft style this bug is listed as "by design". SetForegroundWindow also causes our MessageBox to pop up in front of any other application's windows. */ SetForegroundWindow(hWnd); /* We specifiy TPM_RETURNCMD, so TrackPopupMenu returns the menu selection instead of returning immediately and our getting a WM_COMMAND with the selection. You don't have to do it this way. */ WndProc(hWnd, WM_COMMAND, TrackPopupMenu(menu, // Popup menu to track TPM_RETURNCMD | // Return menu code TPM_RIGHTBUTTON, // Track right mouse button? pt.x, pt.y, // screen coordinates 0, // reserved hWnd, // owner NULL), // LPRECT user can click in without dismissing menu 0); PostMessage(hWnd, 0, 0, 0); // see above DestroyMenu(menu); // Delete loaded menu and reclaim its resources break; } } return DefWindowProc(hWnd, msg, wParam, lParam); } void PServiceProcess::DebugOutput(const char * out) { if (controlWindow == NULL) return; if (debugWindow == NULL || debugWindow == (HWND)-1) { for (PINDEX i = 0; i < 3; i++) { const char * tab = strchr(out, '\t'); if (tab == NULL) break; out = tab+1; } MessageBox(controlWindow, out, GetName(), MB_TASKMODAL); return; } if (!IsWindowVisible(controlWindow)) ShowWindow(controlWindow, SW_SHOWDEFAULT); int len = strlen(out); int max = isWin95 ? 32000 : 128000; while (GetWindowTextLength(debugWindow)+len >= max) { SendMessage(debugWindow, WM_SETREDRAW, FALSE, 0); DWORD start, finish; SendMessage(debugWindow, EM_GETSEL, (WPARAM)&start, (LPARAM)&finish); SendMessage(debugWindow, EM_SETSEL, 0, SendMessage(debugWindow, EM_LINEINDEX, 1, 0)); SendMessage(debugWindow, EM_REPLACESEL, FALSE, (DWORD)""); SendMessage(debugWindow, EM_SETSEL, start, finish); SendMessage(debugWindow, WM_SETREDRAW, TRUE, 0); } SendMessage(debugWindow, EM_SETSEL, max, max); char * lf; char * prev = (char *)out; while ((lf = strchr(prev, '\n')) != NULL) { if (*(lf-1) == '\r') prev = lf+1; else { *lf++ = '\0'; SendMessage(debugWindow, EM_REPLACESEL, FALSE, (DWORD)out); SendMessage(debugWindow, EM_REPLACESEL, FALSE, (DWORD)"\r\n"); out = (const char *)lf; prev = lf; } } if (*out != '\0') SendMessage(debugWindow, EM_REPLACESEL, FALSE, (DWORD)out); } void PServiceProcess::StaticMainEntry(DWORD argc, LPTSTR * argv) { Current().MainEntry(argc, argv); } void PServiceProcess::MainEntry(DWORD argc, LPTSTR * argv) { // SERVICE_STATUS members that don't change status.dwServiceType = SERVICE_WIN32_OWN_PROCESS; status.dwServiceSpecificExitCode = 0; // register our service control handler: statusHandle = RegisterServiceCtrlHandler(GetName(), StaticControlEntry); if (statusHandle == NULL) return; // report the status to Service Control Manager. if (!ReportStatus(SERVICE_START_PENDING, NO_ERROR, 1, 20000)) return; // create the stop event object. The control handler function signals // this event when it receives the "stop" control code. terminationEvent = CreateEvent(NULL, TRUE, FALSE, (const char *)GetName()); if (terminationEvent == NULL) return; startedEvent = CreateEvent(NULL, TRUE, FALSE, NULL); if (startedEvent == NULL) return; GetArguments().SetArgs(argc, argv); // start the thread that performs the work of the service. threadHandle = (HANDLE)_beginthread(StaticThreadEntry, 0, this); if (threadHandle != (HANDLE)-1) { while (WaitForSingleObject(startedEvent, 10000) == WAIT_TIMEOUT) { if (!ReportStatus(SERVICE_START_PENDING, NO_ERROR, 1, 20000)) return; } // Wait here for the end WaitForSingleObject(terminationEvent, INFINITE); } CloseHandle(startedEvent); CloseHandle(terminationEvent); ReportStatus(SERVICE_STOPPED, 0); } void PServiceProcess::StaticThreadEntry(void * arg) { ((PServiceProcess *)arg)->ThreadEntry(); } void PServiceProcess::ThreadEntry() { activeThreadMutex.Wait(); threadId = GetCurrentThreadId(); threadHandle = GetCurrentThread(); activeThreads.SetAt(threadId, this); activeThreadMutex.Signal(); SetTerminationValue(1); if (OnStart()) { if (!debugMode) SetEvent(startedEvent); ReportStatus(SERVICE_RUNNING); SetTerminationValue(0); Main(); ReportStatus(SERVICE_STOP_PENDING, NO_ERROR, 1, 30000); } SetEvent(terminationEvent); } void PServiceProcess::StaticControlEntry(DWORD code) { Current().ControlEntry(code); } void PServiceProcess::ControlEntry(DWORD code) { switch (code) { case SERVICE_CONTROL_PAUSE : // Pause the service if it is running. if (status.dwCurrentState != SERVICE_RUNNING) ReportStatus(status.dwCurrentState); else { if (OnPause()) ReportStatus(SERVICE_PAUSED); } break; case SERVICE_CONTROL_CONTINUE : // Resume the paused service. if (status.dwCurrentState == SERVICE_PAUSED) OnContinue(); ReportStatus(status.dwCurrentState); break; case SERVICE_CONTROL_STOP : // Stop the service. // Report the status, specifying the checkpoint and waithint, before // setting the termination event. ReportStatus(SERVICE_STOP_PENDING, NO_ERROR, 1, 30000); OnStop(); SetEvent(terminationEvent); break; case SERVICE_CONTROL_INTERROGATE : // Update the service status. default : ReportStatus(status.dwCurrentState); } } BOOL PServiceProcess::ReportStatus(DWORD dwCurrentState, DWORD dwWin32ExitCode, DWORD dwCheckPoint, DWORD dwWaitHint) { // Disable control requests until the service is started. if (dwCurrentState == SERVICE_START_PENDING) status.dwControlsAccepted = 0; else status.dwControlsAccepted = SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_PAUSE_CONTINUE; // These SERVICE_STATUS members are set from parameters. status.dwCurrentState = dwCurrentState; status.dwWin32ExitCode = dwWin32ExitCode; status.dwCheckPoint = dwCheckPoint; status.dwWaitHint = dwWaitHint; if (debugMode || isWin95) return TRUE; // Report the status of the service to the service control manager. if (SetServiceStatus(statusHandle, &status)) return TRUE; // If an error occurs, stop the service. PSystemLog::Output(PSystemLog::Error, "SetServiceStatus failed"); return FALSE; } void PServiceProcess::OnStop() { } BOOL PServiceProcess::OnPause() { SuspendThread(threadHandle); return TRUE; } void PServiceProcess::OnContinue() { ResumeThread(threadHandle); } void PServiceProcess::OnControl() { } class ServiceManager { public: ServiceManager() { error = 0; } virtual BOOL Create(PServiceProcess * svc) = 0; virtual BOOL Delete(PServiceProcess * svc) = 0; virtual BOOL Start(PServiceProcess * svc) = 0; virtual BOOL Stop(PServiceProcess * svc) = 0; virtual BOOL Pause(PServiceProcess * svc) = 0; virtual BOOL Resume(PServiceProcess * svc) = 0; DWORD GetError() const { return error; } protected: DWORD error; }; class Win95_ServiceManager : public ServiceManager { public: virtual BOOL Create(PServiceProcess * svc); virtual BOOL Delete(PServiceProcess * svc); virtual BOOL Start(PServiceProcess * svc); virtual BOOL Stop(PServiceProcess * svc); virtual BOOL Pause(PServiceProcess * svc); virtual BOOL Resume(PServiceProcess * svc); }; BOOL Win95_ServiceManager::Create(PServiceProcess * svc) { HKEY key; if (RegCreateKey(HKEY_LOCAL_MACHINE, "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run", &key) == ERROR_SUCCESS) { RegDeleteValue(key, (char *)(const char *)svc->GetName()); RegCloseKey(key); } if ((error = RegCreateKey(HKEY_LOCAL_MACHINE, "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\RunServices", &key)) != ERROR_SUCCESS) return FALSE; PString cmd = "\"" + svc->GetFile() + "\""; error = RegSetValueEx(key, svc->GetName(), 0, REG_SZ, (LPBYTE)(const char *)cmd, cmd.GetLength() + 1); RegCloseKey(key); return error == ERROR_SUCCESS; } BOOL Win95_ServiceManager::Delete(PServiceProcess * svc) { HKEY key; if (RegCreateKey(HKEY_LOCAL_MACHINE, "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run", &key) == ERROR_SUCCESS) { RegDeleteValue(key, (char *)(const char *)svc->GetName()); RegCloseKey(key); } if ((error = RegCreateKey(HKEY_LOCAL_MACHINE, "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\RunServices", &key)) != ERROR_SUCCESS) return FALSE; error = RegDeleteValue(key, (char *)(const char *)svc->GetName()); RegCloseKey(key); return error == ERROR_SUCCESS; } BOOL Win95_ServiceManager::Start(PServiceProcess * service) { if (IsServiceRunning(service)) { PError << "Service already running" << endl; error = 1; return FALSE; } BOOL ok = _spawnl(_P_DETACH, service->GetFile(), service->GetFile(), NULL) >= 0; error = errno; return ok; } BOOL Win95_ServiceManager::Stop(PServiceProcess * service) { HANDLE hEvent = OpenEvent(EVENT_MODIFY_STATE, FALSE, service->GetName()); if (hEvent == NULL) { error = ::GetLastError(); PError << "Service is not running" << endl; return FALSE; } SetEvent(hEvent); CloseHandle(hEvent); // Wait for process to go away. for (PINDEX i = 0; i < 20; i++) { hEvent = OpenEvent(EVENT_MODIFY_STATE, FALSE, service->GetName()); if (hEvent == NULL) return TRUE; CloseHandle(hEvent); ::Sleep(500); } error = 0x10000000; return FALSE; } BOOL Win95_ServiceManager::Pause(PServiceProcess *) { PError << "Cannot pause service under Windows 95" << endl; error = 1; return FALSE; } BOOL Win95_ServiceManager::Resume(PServiceProcess *) { PError << "Cannot resume service under Windows 95" << endl; error = 1; return FALSE; } class NT_ServiceManager : public ServiceManager { public: NT_ServiceManager() { schSCManager = schService = NULL; } ~NT_ServiceManager(); BOOL Create(PServiceProcess * svc); BOOL Delete(PServiceProcess * svc); BOOL Start(PServiceProcess * svc); BOOL Stop(PServiceProcess * svc) { return Control(svc, SERVICE_CONTROL_STOP); } BOOL Pause(PServiceProcess * svc) { return Control(svc, SERVICE_CONTROL_PAUSE); } BOOL Resume(PServiceProcess * svc) { return Control(svc, SERVICE_CONTROL_CONTINUE); } DWORD GetError() const { return error; } private: BOOL OpenManager(); BOOL Open(PServiceProcess * svc); BOOL Control(PServiceProcess * svc, DWORD command); SC_HANDLE schSCManager, schService; }; NT_ServiceManager::~NT_ServiceManager() { if (schService != NULL) CloseServiceHandle(schService); if (schSCManager != NULL) CloseServiceHandle(schSCManager); } BOOL NT_ServiceManager::OpenManager() { schSCManager = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS); if (schSCManager != NULL) return TRUE; error = ::GetLastError(); PError << "Could not open Service Manager." << endl; return FALSE; } BOOL NT_ServiceManager::Open(PServiceProcess * svc) { if (!OpenManager()) return FALSE; schService = OpenService(schSCManager, svc->GetName(), SERVICE_ALL_ACCESS); if (schService != NULL) return TRUE; error = ::GetLastError(); PError << "Service is not installed." << endl; return FALSE; } BOOL NT_ServiceManager::Create(PServiceProcess * svc) { if (!OpenManager()) return FALSE; schService = OpenService(schSCManager, svc->GetName(), SERVICE_ALL_ACCESS); if (schService != NULL) { PError << "Service is already installed." << endl; return FALSE; } PString binaryFilename; GetShortPathName(svc->GetFile(), binaryFilename.GetPointer(_MAX_PATH), _MAX_PATH); schService = CreateService( schSCManager, // SCManager database svc->GetName(), // name of service svc->GetName(), // name to display SERVICE_ALL_ACCESS, // desired access SERVICE_WIN32_OWN_PROCESS, // service type SERVICE_AUTO_START, // start type SERVICE_ERROR_NORMAL, // error control type binaryFilename, // service's binary NULL, // no load ordering group NULL, // no tag identifier svc->GetServiceDependencies(), // no dependencies NULL, // LocalSystem account NULL); // no password if (schService == NULL) { error = ::GetLastError(); return FALSE; } HKEY key; if ((error = RegCreateKey(HKEY_LOCAL_MACHINE, "SYSTEM\\CurrentControlSet\\Services\\EventLog\\Application\\" + svc->GetName(), &key)) != ERROR_SUCCESS) return FALSE; LPBYTE fn = (LPBYTE)(const char *)binaryFilename; PINDEX fnlen = binaryFilename.GetLength()+1; if ((error = RegSetValueEx(key, "EventMessageFile", 0, REG_EXPAND_SZ, fn, fnlen)) == ERROR_SUCCESS && (error = RegSetValueEx(key, "CategoryMessageFile", 0, REG_EXPAND_SZ, fn, fnlen)) == ERROR_SUCCESS) { DWORD dwData = EVENTLOG_ERROR_TYPE | EVENTLOG_WARNING_TYPE | EVENTLOG_INFORMATION_TYPE; if ((error = RegSetValueEx(key, "TypesSupported", 0, REG_DWORD, (LPBYTE)&dwData, sizeof(DWORD))) == ERROR_SUCCESS) { dwData = PSystemLog::NumLogLevels; error = RegSetValueEx(key, "CategoryCount", 0, REG_DWORD, (LPBYTE)&dwData, sizeof(DWORD)); } } RegCloseKey(key); return error == ERROR_SUCCESS; } BOOL NT_ServiceManager::Delete(PServiceProcess * svc) { if (!Open(svc)) return FALSE; PString name = "SYSTEM\\CurrentControlSet\\Services\\EventLog\\Application\\" + svc->GetName(); error = ::RegDeleteKey(HKEY_LOCAL_MACHINE, (char *)(const char *)name); if (!::DeleteService(schService)) error = ::GetLastError(); return error == ERROR_SUCCESS; } BOOL NT_ServiceManager::Start(PServiceProcess * svc) { if (!Open(svc)) return FALSE; BOOL ok = ::StartService(schService, 0, NULL); error = ::GetLastError(); return ok; } BOOL NT_ServiceManager::Control(PServiceProcess * svc, DWORD command) { if (!Open(svc)) return FALSE; SERVICE_STATUS status; BOOL ok = ::ControlService(schService, command, &status); error = ::GetLastError(); return ok; } BOOL PServiceProcess::ProcessCommand(const char * cmd) { PINDEX cmdNum = 0; while (stricmp(cmd, ServiceCommandNames[cmdNum]) != 0) { if (++cmdNum >= NumSvcCmds) { if (!CreateControlWindow(TRUE)) return FALSE; if (*cmd != '\0') PError << "Unknown command \"" << cmd << "\".\n"; else PError << "Could not start service.\n"; PError << "usage: " << GetName() << " [ "; for (cmdNum = 0; cmdNum < NumSvcCmds-1; cmdNum++) PError << ServiceCommandNames[cmdNum] << " | "; PError << ServiceCommandNames[cmdNum] << " ]" << endl; return FALSE; } } NT_ServiceManager nt; Win95_ServiceManager win95; ServiceManager * svcManager; if (isWin95) svcManager = &win95; else svcManager = &nt; BOOL good = FALSE; switch (cmdNum) { case SvcCmdNoWindow : if (controlWindow == NULL) controlWindow = (HWND)-1; break; case SvcCmdTray : if (CreateControlWindow(FALSE)) { PNotifyIconData nid(controlWindow, NIF_MESSAGE|NIF_ICON, GetName()); nid.hIcon = (HICON)LoadImage(hInstance, MAKEINTRESOURCE(ICON_RESID), IMAGE_ICON, // 16x16 icon GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON), 0); nid.uCallbackMessage = UWM_SYSTRAY; // message sent to nid.hWnd nid.Add(); // This adds the icon debugWindow = (HWND)-1; systemLogFileName = PString(); return TRUE; } return FALSE; case SvcCmdNoTray : if (TrayIconRegistry(this, CheckTrayIcon)) { TrayIconRegistry(this, DelTrayIcon); PError << "Tray icon removed."; } else { TrayIconRegistry(this, AddTrayIcon); PError << "Tray icon installed."; } return TRUE; case SvcCmdVersion : // Version command ::SetLastError(0); PError << GetName() << " Version " << GetVersion(TRUE) << " by " << GetManufacturer() << " on " << GetOSClass() << ' ' << GetOSName() << " (" << GetOSVersion() << '-' << GetOSHardware() << ')' << endl; return TRUE; case SvcCmdInstall : // install good = svcManager->Create(this); TrayIconRegistry(this, AddTrayIcon); break; case SvcCmdRemove : // remove good = svcManager->Delete(this); TrayIconRegistry(this, DelTrayIcon); break; case SvcCmdStart : // start good = svcManager->Start(this); break; case SvcCmdStop : // stop good = svcManager->Stop(this); break; case SvcCmdPause : // pause good = svcManager->Pause(this); break; case SvcCmdResume : // resume good = svcManager->Resume(this); break; case SvcCmdDeinstall : // deinstall svcManager->Delete(this); TrayIconRegistry(this, DelTrayIcon); PConfig cfg; PStringList sections = cfg.GetSections(); PINDEX i; for (i = 0; i < sections.GetSize(); i++) cfg.DeleteSection(sections[i]); good = TRUE; break; } SetLastError(0); PError << "Service command \"" << ServiceCommandNames[cmdNum] << "\" "; if (good) PError << "successful."; else { PError << "failed - "; switch (svcManager->GetError()) { case ERROR_ACCESS_DENIED : PError << "Access denied"; break; case 0x10000000 : PError << "process still running."; break; default : PError << "error code = " << svcManager->GetError(); } } PError << endl; return TRUE; } // End Of File ///////////////////////////////////////////////////////////////