From: Iain Patterson Date: Fri, 30 May 2003 18:32:00 +0000 (-0400) Subject: NSSM 1.0. X-Git-Tag: v1.0^0 X-Git-Url: http://git.iain.cx/?a=commitdiff_plain;h=d007ff22cecf2ce85cc68a1de81a010d4a65badc;p=nssm.git NSSM 1.0. --- diff --git a/README.txt b/README.txt new file mode 100644 index 0000000..ae081d2 --- /dev/null +++ b/README.txt @@ -0,0 +1,51 @@ +NSSM: The Non-Sucking Service Manager +Version 1.0, 2003-05-30 + +NSSM is a service helper program similar to srvany and cygrunsrv. It can +start any application as an NT service and will restart the service if it +fails for any reason. + +NSSM also has a graphical service installer and remover. + + +Installation +------------ +To install a service, run + + nssm install servicename + +You will be prompted to enter the full path to the application you wish +to run and any commandline options to pass to that application. + +Use the system service manager (services.msc) to control advanced service +properties such as startup method and desktop interaction. NSSM may +support these options at a later time... + + +Managing the service +-------------------- +NSSM will launch the application listed in the registry when you send it a +start signal and will terminate it when you send a stop signal. So far, so +much like srvany. But NSSM is the Non-Sucking service manager and will take +action if/when the application dies. + +NSSM will try to restart itself if it notices that the application died but +you didn't send it a stop signal. NSSM will keep trying, pausing 30 seconds +between each attempt, until the service is successfully started or you send +it a stop signal. + + +Removing services +----------------- +NSSM can also remove services. Run + + nssm remove servicename + +to remove a service. You will prompted for confirmation before the service +is removed. Try not to remove essential system services... + + +Licence +------- +NSSM is public domain. You may unconditionally use it and/or its source code +for any purpose you wish. diff --git a/event.cpp b/event.cpp new file mode 100644 index 0000000..7f2b9f4 --- /dev/null +++ b/event.cpp @@ -0,0 +1,41 @@ +#include "nssm.h" + +/* Convert error code to error string - must call LocalFree() on return value */ +char *error_string(unsigned long error) { + char *message; + if (! FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, 0, error, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (char *) &message, 0, 0)) return 0; + return message; +} + +/* Log a message to the Event Log */ +void eventprintf(unsigned short type, char *format, ...) { + char message[4096]; + char *strings[2]; + int n, size; + va_list arg; + + /* Construct the message */ + size = sizeof(message); + va_start(arg, format); + n = _vsnprintf(message, size, format, arg); + va_end(arg); + + /* Check success */ + if (n < 0 || n >= size) return; + + /* Construct strings array */ + strings[0] = message; + strings[1] = 0; + + /* Open event log */ + HANDLE handle = RegisterEventSource(0, TEXT(NSSM)); + if (! handle) return; + + /* Log it */ + if (! ReportEvent(handle, type, 0, 0, 0, 1, 0, (const char **) strings, 0)) { + printf("ReportEvent(): %s\n", error_string(GetLastError())); + } + + /* Close event log */ + DeregisterEventSource(handle); +} diff --git a/event.h b/event.h new file mode 100644 index 0000000..8bc7777 --- /dev/null +++ b/event.h @@ -0,0 +1,7 @@ +#ifndef EVENT_H +#define EVENT_H + +char *error_string(unsigned long); +void eventprintf(unsigned short, char *, ...); + +#endif diff --git a/gui.cpp b/gui.cpp new file mode 100644 index 0000000..c094657 --- /dev/null +++ b/gui.cpp @@ -0,0 +1,262 @@ +#include "nssm.h" + +int nssm_gui(int resource, char *name) { + char blurb[256]; + + /* Create window */ + HWND dlg = CreateDialog(0, MAKEINTRESOURCE(resource), 0, install_dlg); + if (! dlg) { + snprintf(blurb, sizeof(blurb), "CreateDialog() failed with error code %d", GetLastError()); + MessageBox(0, blurb, NSSM, MB_OK); + return 1; + } + + /* Display the window */ + centre_window(dlg); + ShowWindow(dlg, SW_SHOW); + + /* Set service name if given */ + if (name) { + SetDlgItemText(dlg, IDC_NAME, name); + /* No point making user click remove if the name is already entered */ + if (resource == IDD_REMOVE) { + HWND button = GetDlgItem(dlg, IDC_REMOVE); + if (button) { + SendMessage(button, WM_LBUTTONDOWN, 0, 0); + SendMessage(button, WM_LBUTTONUP, 0, 0); + } + } + } + + /* Go! */ + MSG message; + while (GetMessage(&message, 0, 0, 0)) { + TranslateMessage(&message); + DispatchMessage(&message); + } + + return message.wParam; +} + +void centre_window(HWND window) { + HWND desktop; + RECT size, desktop_size; + unsigned long x, y; + + if (! window) return; + + /* Find window size */ + if (! GetWindowRect(window, &size)) return; + + /* Find desktop window */ + desktop = GetDesktopWindow(); + if (! desktop) return; + + /* Find desktop window size */ + if (! GetWindowRect(desktop, &desktop_size)) return; + + /* Centre window */ + x = (desktop_size.right - size.right) / 2; + y = (desktop_size.bottom - size.bottom) / 2; + MoveWindow(window, x, y, size.right, size.bottom, 0); +} + +/* Install the service */ +int install(HWND window) { + if (! window) return 1; + + /* Check parameters in the window */ + char name[STRING_SIZE]; + char exe[MAX_PATH]; + char flags[STRING_SIZE]; + char dir[MAX_PATH]; + + /* Get service name */ + if (! GetDlgItemText(window, IDC_NAME, name, sizeof(name))) { + MessageBox(0, "No valid service name was specified!", NSSM, MB_OK); + return 2; + } + + /* Get executable name */ + if (! GetDlgItemText(window, IDC_PATH, exe, sizeof(exe))) { + MessageBox(0, "No valid executable path was specified!", NSSM, MB_OK); + return 3; + } + + /* Get flags */ + if (SendMessage(GetDlgItem(window, IDC_FLAGS), WM_GETTEXTLENGTH, 0, 0)) { + if (! GetDlgItemText(window, IDC_FLAGS, flags, sizeof(flags))) { + MessageBox(0, "No valid options were specified!", NSSM, MB_OK); + return 4; + } + } + else ZeroMemory(&flags, sizeof(flags)); + + /* Work out directory name */ + unsigned int len = strlen(exe); + unsigned int i; + for (i = len; i && exe[i] != '\\' && exe[i] != '/'; i--); + memmove(dir, exe, i); + dir[i] = '\0'; + + /* Open service manager */ + SC_HANDLE services = open_service_manager(); + if (! services) { + MessageBox(0, "Can't open service manager!\nPerhaps you need to be an administrator...", NSSM, MB_OK); + return 2; + } + + /* Get path of this program */ + char path[MAX_PATH]; + GetModuleFileName(0, path, MAX_PATH); + + /* Construct command */ + char command[MAX_PATH]; + int runlen = strlen(NSSM_RUN); + int pathlen = strlen(path); + if (pathlen + runlen + 2 >= MAX_PATH) { + MessageBox(0, "Path too long!\nThe full path to " NSSM " is too long.\nPlease install " NSSM " somewhere else...\n", NSSM, MB_OK); + return 3; + } + if (snprintf(command, sizeof(command), "%s %s", path, NSSM_RUN) < 0) { + MessageBox(0, "Error constructing ImagePath!\nThis really shouldn't happen. You could be out of memory\nor the world may be about to end or something equally bad.", NSSM, MB_OK); + return 4; + } + + /* Create the service */ + SC_HANDLE service = CreateService(services, name, name, SC_MANAGER_ALL_ACCESS, SERVICE_WIN32_OWN_PROCESS, SERVICE_AUTO_START, SERVICE_ERROR_NORMAL, command, 0, 0, 0, 0, 0); + if (! service) { + MessageBox(0, "Couldn't create service!\nPerhaps it is already installed...", NSSM, MB_OK); + CloseServiceHandle(services); + return 5; + } + + /* Now we need to put the parameters into the registry */ + if (create_parameters(name, exe, flags, dir)) { + MessageBox(0, "Couldn't set startup parameters for the service!\nDeleting the service...", NSSM, MB_OK); + DeleteService(service); + CloseServiceHandle(services); + return 6; + } + + /* Cleanup */ + CloseServiceHandle(service); + CloseServiceHandle(services); + + MessageBox(0, "Service successfully installed!", NSSM, MB_OK); + return 0; +} + +/* Remove the service */ +int remove(HWND window) { + if (! window) return 1; + + /* Check parameters in the window */ + char name[STRING_SIZE]; + + /* Get service name */ + if (! GetDlgItemText(window, IDC_NAME, name, sizeof(name))) { + MessageBox(0, "No valid service name was specified!", NSSM, MB_OK); + return 2; + } + + /* Confirm */ + char blurb[MAX_PATH]; + if (snprintf(blurb, sizeof(blurb), "Remove the \"%s\" service?", name) < 0) { + if (MessageBox(0, "Remove the service?", NSSM, MB_YESNO) != IDYES) return 0; + } + else if (MessageBox(0, blurb, NSSM, MB_YESNO) != IDYES) return 0; + + /* Open service manager */ + SC_HANDLE services = open_service_manager(); + if (! services) { + MessageBox(0, "Can't open service manager!\nPerhaps you need to be an administrator...", NSSM, MB_OK); + return 2; + } + + /* Try to open the service */ + SC_HANDLE service = OpenService(services, name, SC_MANAGER_ALL_ACCESS); + if (! service) { + MessageBox(0, "Can't open service!\nPerhaps it isn't installed...", NSSM, MB_OK); + CloseServiceHandle(services); + return 3; + } + + /* Try to delete the service */ + if (! DeleteService(service)) { + MessageBox(0, "Can't delete service! Make sure the service is stopped and try again.\nIf this error persists, you may need to set the service NOT to start\nautomatically, reboot your computer and try removing it again.", NSSM, MB_OK); + CloseServiceHandle(service); + CloseServiceHandle(services); + return 4; + } + + /* Cleanup */ + CloseServiceHandle(service); + CloseServiceHandle(services); + + MessageBox(0, "Service successfully removed!", NSSM, MB_OK); + return 0; +} + +/* Browse for game */ +void browse(HWND window) { + if (! window) return; + + OPENFILENAME ofn; + ZeroMemory(&ofn, sizeof(ofn)); + ofn.lStructSize = sizeof(ofn); + ofn.lpstrFilter = "Applications\0*.exe\0All files\0*.*\0\0"; + ofn.lpstrFile = new char[MAX_PATH]; + ofn.lpstrFile[0] = '\0'; + ofn.lpstrTitle = "Locate application file"; + ofn.nMaxFile = MAX_PATH; + ofn.Flags = OFN_EXPLORER | OFN_PATHMUSTEXIST | OFN_HIDEREADONLY; + + if (GetOpenFileName(&ofn)) { + SendMessage(window, WM_SETTEXT, 0, (LPARAM) ofn.lpstrFile); + } + + delete[] ofn.lpstrFile; +} + +/* Install/remove dialogue callback */ +int CALLBACK install_dlg(HWND window, UINT message, WPARAM w, LPARAM l) { + switch (message) { + /* Creating the dialogue */ + case WM_INITDIALOG: + return 1; + + /* Button was pressed or control was controlled */ + case WM_COMMAND: + switch (LOWORD(w)) { + /* OK button */ + case IDC_OK: + PostQuitMessage(install(window)); + break; + + /* Cancel button */ + case IDC_CANCEL: + DestroyWindow(window); + break; + + /* Browse button */ + case IDC_BROWSE: + browse(GetDlgItem(window, IDC_PATH)); + break; + + /* Remove button */ + case IDC_REMOVE: + PostQuitMessage(remove(window)); + break; + } + return 1; + + /* Window closing */ + case WM_CLOSE: + DestroyWindow(window); + return 0; + case WM_DESTROY: + PostQuitMessage(0); + } + return 0; +} diff --git a/gui.h b/gui.h new file mode 100644 index 0000000..0e589f4 --- /dev/null +++ b/gui.h @@ -0,0 +1,23 @@ +#ifndef GUI_H +#define GUI_H + +#include +#include +#include +#include "resource.h" + +#define GUI +#ifndef snprintf +#define snprintf _snprintf +#endif + +#define STRING_SIZE 256 + +int nssm_gui(int, char *); +void centre_window(HWND); +int install(HWND); +int remove(HWND); +void browse(HWND); +int CALLBACK install_dlg(HWND, UINT, WPARAM, LPARAM); + +#endif diff --git a/nssm.cpp b/nssm.cpp new file mode 100644 index 0000000..225d038 --- /dev/null +++ b/nssm.cpp @@ -0,0 +1,43 @@ +#include "nssm.h" + +/* String function */ +int str_equiv(const char *a, const char *b) { + int i; + for (i = 0; ; i++) { + if (tolower(b[i]) != tolower(a[i])) return 0; + if (! a[i]) return 1; + } +} + +/* How to use me correctly */ +int usage(int ret) { + fprintf(stderr, "NSSM: The non-sucking service manager\n"); + fprintf(stderr, "Version %s, %s\n", NSSM_VERSION, NSSM_DATE); + fprintf(stderr, "Usage: nssm option [args]\n"); + fprintf(stderr, "To install a service: nssm install [servicename]\n"); + fprintf(stderr, "To remove a service: nssm remove [servicename]\n"); + exit(ret); +} + +int main(int argc, char **argv) { + /* Require an argument since users may try to run nssm directly */ + if (argc == 1) exit(usage(1)); + + /* Valid commands are install or remove */ + if (str_equiv(argv[1], "install")) exit(install_service(argv[2])); + if (str_equiv(argv[1], "remove")) exit(remove_service(argv[2])); + /* Undocumented: "run" is used to actually do service stuff */ + if (! str_equiv(argv[1], NSSM_RUN)) exit(usage(2)); + + /* Start service magic */ + SERVICE_TABLE_ENTRY table[] = { { NSSM, service_main }, { 0, 0 } }; + if (! StartServiceCtrlDispatcher(table)) { + char *message = error_string(GetLastError()); + eventprintf(EVENTLOG_ERROR_TYPE, "StartServiceCtrlDispatcher() failed: %s", message); + if (message) LocalFree(message); + return 100; + } + + /* And nothing more to do */ + return 0; +} diff --git a/nssm.dsp b/nssm.dsp new file mode 100644 index 0000000..f521ea9 --- /dev/null +++ b/nssm.dsp @@ -0,0 +1,147 @@ +# Microsoft Developer Studio Project File - Name="nssm" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Console Application" 0x0103 + +CFG=nssm - Win32 Debug +!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 "nssm.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 "nssm.mak" CFG="nssm - Win32 Debug" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "nssm - Win32 Release" (based on "Win32 (x86) Console Application") +!MESSAGE "nssm - Win32 Debug" (based on "Win32 (x86) Console Application") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName "" +# PROP Scc_LocalPath "" +CPP=cl.exe +RSC=rc.exe + +!IF "$(CFG)" == "nssm - 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 /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /c +# ADD CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /c +# 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 winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /machine:I386 +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /machine:I386 /out:"nssm.exe" + +!ELSEIF "$(CFG)" == "nssm - 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 /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /GZ /c +# ADD CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "_MBCS" /FD /GZ /c +# SUBTRACT CPP /YX +# 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 winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /debug /machine:I386 /pdbtype:sept +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /debug /machine:I386 /out:"nssm_debug.exe" /pdbtype:sept + +!ENDIF + +# Begin Target + +# Name "nssm - Win32 Release" +# Name "nssm - Win32 Debug" +# Begin Group "Source Files" + +# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat" +# Begin Source File + +SOURCE=.\event.cpp +# End Source File +# Begin Source File + +SOURCE=.\gui.cpp +# End Source File +# Begin Source File + +SOURCE=.\nssm.cpp +# End Source File +# Begin Source File + +SOURCE=.\registry.cpp +# End Source File +# Begin Source File + +SOURCE=.\service.cpp +# End Source File +# End Group +# Begin Group "Header Files" + +# PROP Default_Filter "h;hpp;hxx;hm;inl" +# Begin Source File + +SOURCE=.\event.h +# End Source File +# Begin Source File + +SOURCE=.\gui.h +# End Source File +# Begin Source File + +SOURCE=.\nssm.h +# End Source File +# Begin Source File + +SOURCE=.\registry.h +# End Source File +# Begin Source File + +SOURCE=.\service.h +# End Source File +# End Group +# Begin Group "Resource Files" + +# PROP Default_Filter "ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe" +# Begin Source File + +SOURCE=.\nssm.ico +# End Source File +# Begin Source File + +SOURCE=.\nssm.rc +# End Source File +# End Group +# End Target +# End Project diff --git a/nssm.dsw b/nssm.dsw new file mode 100644 index 0000000..7f16af1 --- /dev/null +++ b/nssm.dsw @@ -0,0 +1,29 @@ +Microsoft Developer Studio Workspace File, Format Version 6.00 +# WARNING: DO NOT EDIT OR DELETE THIS WORKSPACE FILE! + +############################################################################### + +Project: "nssm"=.\nssm.dsp - Package Owner=<4> + +Package=<5> +{{{ +}}} + +Package=<4> +{{{ +}}} + +############################################################################### + +Global: + +Package=<5> +{{{ +}}} + +Package=<3> +{{{ +}}} + +############################################################################### + diff --git a/nssm.h b/nssm.h new file mode 100644 index 0000000..ab81239 --- /dev/null +++ b/nssm.h @@ -0,0 +1,18 @@ +#ifndef NSSM_H +#define NSSM_H + +#define _WIN32_WINNT 0x0500 +#include +#include +#include +#include "event.h" +#include "registry.h" +#include "service.h" +#include "gui.h" + +#define NSSM "nssm" +#define NSSM_VERSION "1.0" +#define NSSM_DATE "2003-05-30" +#define NSSM_RUN "run" + +#endif diff --git a/nssm.ico b/nssm.ico new file mode 100644 index 0000000..21eb957 Binary files /dev/null and b/nssm.ico differ diff --git a/nssm.rc b/nssm.rc new file mode 100644 index 0000000..aa1134d --- /dev/null +++ b/nssm.rc @@ -0,0 +1,125 @@ +//Microsoft Developer Studio generated resource script. +// +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "afxres.h" + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (U.K.) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENG) +#ifdef _WIN32 +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_UK +#pragma code_page(1252) +#endif //_WIN32 + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE DISCARDABLE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE DISCARDABLE +BEGIN + "#include ""afxres.h""\r\n" + "\0" +END + +3 TEXTINCLUDE DISCARDABLE +BEGIN + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Icon +// + +// Icon with lowest ID value placed first to ensure application icon +// remains consistent on all systems. +IDI_NSSM ICON DISCARDABLE "nssm.ico" + +///////////////////////////////////////////////////////////////////////////// +// +// Dialog +// + +IDD_INSTALL DIALOG DISCARDABLE 0, 0, 220, 90 +STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "NSSM service installer" +FONT 8, "MS Sans Serif" +BEGIN + DEFPUSHBUTTON "Install service",IDC_OK,55,69,50,14 + PUSHBUTTON "Cancel",IDC_CANCEL,111,69,50,14 + EDITTEXT IDC_PATH,48,7,110,14,ES_AUTOHSCROLL + PUSHBUTTON "Browse",IDC_BROWSE,163,7,50,14 + EDITTEXT IDC_FLAGS,48,28,165,14,ES_AUTOHSCROLL + LTEXT "Options:",IDC_STATIC,7,31,27,8 + LTEXT "Service\nname:",IDC_STATIC,7,49,41,18 + EDITTEXT IDC_NAME,48,49,77,14,ES_AUTOHSCROLL + LTEXT "Application:",IDC_STATIC,7,9,38,8 +END + +IDD_REMOVE DIALOG DISCARDABLE 0, 0, 223, 28 +STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "NSSM service remover" +FONT 8, "MS Sans Serif" +BEGIN + DEFPUSHBUTTON "Remove service",IDC_REMOVE,154,7,62,14 + LTEXT "Service name:",IDC_STATIC,8,9,46,8 + EDITTEXT IDC_NAME,59,7,87,14,ES_AUTOHSCROLL +END + + +///////////////////////////////////////////////////////////////////////////// +// +// DESIGNINFO +// + +#ifdef APSTUDIO_INVOKED +GUIDELINES DESIGNINFO DISCARDABLE +BEGIN + IDD_INSTALL, DIALOG + BEGIN + VERTGUIDE, 7 + VERTGUIDE, 48 + VERTGUIDE, 213 + BOTTOMMARGIN, 83 + HORZGUIDE, 21 + HORZGUIDE, 42 + END +END +#endif // APSTUDIO_INVOKED + +#endif // English (U.K.) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED + diff --git a/registry.cpp b/registry.cpp new file mode 100644 index 0000000..2451933 --- /dev/null +++ b/registry.cpp @@ -0,0 +1,86 @@ +#include "nssm.h" + +int create_parameters(char *service_name, char *exe, char *flags, char *dir) { + /* Get registry */ + char registry[MAX_PATH]; + if (_snprintf(registry, sizeof(registry), NSSM_REGISTRY, service_name) < 0) { + eventprintf(EVENTLOG_ERROR_TYPE, "Out of memory for NSSM_REGISTRY in create_parameters()!"); + return 1; + } + + /* Try to open the registry */ + HKEY key; + if (RegCreateKeyEx(HKEY_LOCAL_MACHINE, registry, 0, 0, REG_OPTION_NON_VOLATILE, KEY_WRITE, 0, &key, 0) != ERROR_SUCCESS) { + eventprintf(EVENTLOG_ERROR_TYPE, "Can't open service registry settings!", NSSM_REGISTRY); + return 2; + } + + /* Try to create the parameters */ + if (RegSetValueEx(key, NSSM_REG_EXE, 0, REG_SZ, (const unsigned char *) exe, strlen(exe) + 1) != ERROR_SUCCESS) { + eventprintf(EVENTLOG_ERROR_TYPE, "Can't add registry value %s: %s", NSSM_REG_EXE, error_string(GetLastError())); + RegDeleteKey(HKEY_LOCAL_MACHINE, NSSM_REGISTRY); + RegCloseKey(key); + return 3; + } + if (RegSetValueEx(key, NSSM_REG_FLAGS, 0, REG_SZ, (const unsigned char *) flags, strlen(flags) + 1) != ERROR_SUCCESS) { + eventprintf(EVENTLOG_ERROR_TYPE, "Can't add registry value %s: %s", NSSM_REG_FLAGS, error_string(GetLastError())); + RegDeleteKey(HKEY_LOCAL_MACHINE, NSSM_REGISTRY); + RegCloseKey(key); + return 4; + } + if (RegSetValueEx(key, NSSM_REG_DIR, 0, REG_SZ, (const unsigned char *) dir, strlen(dir) + 1) != ERROR_SUCCESS) { + eventprintf(EVENTLOG_ERROR_TYPE, "Can't add registry value %s: %s", NSSM_REG_DIR, error_string(GetLastError())); + RegDeleteKey(HKEY_LOCAL_MACHINE, NSSM_REGISTRY); + RegCloseKey(key); + return 5; + } + + /* Close registry */ + RegCloseKey(key); + + return 0; +} + +int get_parameters(char *service_name, char *exe, int exelen, char *flags, int flagslen, char *dir, int dirlen) { + /* Get registry */ + char registry[MAX_PATH]; + if (_snprintf(registry, sizeof(registry), NSSM_REGISTRY, service_name) < 0) { + eventprintf(EVENTLOG_ERROR_TYPE, "Out of memory for NSSM_REGISTRY in get_parameters()!"); + return 1; + } + + /* Try to open the registry */ + HKEY key; + if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, registry, 0, KEY_READ, &key) != ERROR_SUCCESS) { + eventprintf(EVENTLOG_ERROR_TYPE, "Can't open service registry settings!", NSSM_REGISTRY); + return 2; + } + + unsigned long type = REG_SZ; + + /* Try to get executable file - MUST succeed */ + if (RegQueryValueEx(key, NSSM_REG_EXE, 0, &type, (unsigned char *) exe, (unsigned long *) &exelen) != ERROR_SUCCESS) { + eventprintf(EVENTLOG_ERROR_TYPE, "Can't get application path (registry value %s): %s", NSSM_REG_EXE, error_string(GetLastError())); + RegCloseKey(key); + return 3; + } + + /* Try to get flags - may fail */ + if (RegQueryValueEx(key, NSSM_REG_FLAGS, 0, &type, (unsigned char *) flags, (unsigned long *) &flagslen) != ERROR_SUCCESS) { + eventprintf(EVENTLOG_WARNING_TYPE, "Can't get application flags (registry value %s): %s", NSSM_REG_FLAGS, error_string(GetLastError())); + RegCloseKey(key); + return 4; + } + + /* Try to get startup directory - may fail */ + if (RegQueryValueEx(key, NSSM_REG_DIR, 0, &type, (unsigned char *) dir, (unsigned long *) &dirlen) != ERROR_SUCCESS) { + eventprintf(EVENTLOG_WARNING_TYPE, "Can't get application startup directory (registry value %s): %s", NSSM_REG_DIR, error_string(GetLastError())); + RegCloseKey(key); + return 5; + } + + /* Close registry */ + RegCloseKey(key); + + return 0; +} diff --git a/registry.h b/registry.h new file mode 100644 index 0000000..679306d --- /dev/null +++ b/registry.h @@ -0,0 +1,12 @@ +#ifndef REGISTRY_H +#define REGISTRY_H + +#define NSSM_REGISTRY "SYSTEM\\CurrentControlSet\\Services\\%s\\Parameters" +#define NSSM_REG_EXE "Application" +#define NSSM_REG_FLAGS "AppParameters" +#define NSSM_REG_DIR "AppDirectory" + +int create_parameters(char *, char *, char *, char *); +int get_parameters(char *, char *, int, char *, int, char *, int); + +#endif diff --git a/resource.h b/resource.h new file mode 100644 index 0000000..267e7ff --- /dev/null +++ b/resource.h @@ -0,0 +1,25 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Developer Studio generated include file. +// Used by nssm.rc +// +#define IDI_NSSM 101 +#define IDD_INSTALL 102 +#define IDD_REMOVE 103 +#define IDC_PATH 1000 +#define IDC_OK 1001 +#define IDC_CANCEL 1002 +#define IDC_BROWSE 1003 +#define IDC_FLAGS 1004 +#define IDC_NAME 1005 +#define IDC_REMOVE 1007 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 104 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1009 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/service.cpp b/service.cpp new file mode 100644 index 0000000..9dbb310 --- /dev/null +++ b/service.cpp @@ -0,0 +1,184 @@ +#include "nssm.h" + +SERVICE_STATUS service_status; +SERVICE_STATUS_HANDLE service_handle; +HANDLE wait_handle; +HANDLE pid; +char exe[MAX_PATH]; +char flags[MAX_PATH]; +char dir[MAX_PATH]; + +/* Connect to the service manager */ +SC_HANDLE open_service_manager() { + SC_HANDLE ret = OpenSCManager(0, SERVICES_ACTIVE_DATABASE, SC_MANAGER_ALL_ACCESS); + if (! ret) { + eventprintf(EVENTLOG_ERROR_TYPE, "Unable to connect to service manager!\nPerhaps you need to be an administrator..."); + return 0; + } + + return ret; +} + +/* Install the service */ +int install_service(char *name) { +#ifdef GUI + /* Show the dialogue box */ + return nssm_gui(IDD_INSTALL, name); +#else + fprintf(stderr, "Unimplemented\n"); + return 1; +#endif +} + +/* Remove the service */ +int remove_service(char *name) { +#ifdef GUI + return nssm_gui(IDD_REMOVE, name); +#else + fprintf(stderr, "Unimplemented\n"); + return 1; +#endif +} + +/* Service initialisation */ +void WINAPI service_main(unsigned long argc, char **argv) { + /* Initialise status */ + ZeroMemory(&service_status, sizeof(service_status)); + service_status.dwServiceType = SERVICE_WIN32_OWN_PROCESS | SERVICE_INTERACTIVE_PROCESS; + service_status.dwCurrentState = SERVICE_RUNNING; + service_status.dwControlsAccepted = SERVICE_ACCEPT_SHUTDOWN | SERVICE_ACCEPT_STOP; + service_status.dwWin32ExitCode = NO_ERROR; + service_status.dwServiceSpecificExitCode = 0; + service_status.dwCheckPoint = 0; + service_status.dwWaitHint = 1000; + + /* Signal we AREN'T running the server */ + pid = 0; + + /* Get startup parameters */ + int ret = get_parameters(argv[0], exe, sizeof(exe), flags, sizeof(flags), dir, sizeof(dir)); + if (ret) { + eventprintf(EVENTLOG_ERROR_TYPE, "service_main(): Can't get startup parameters: error %d", ret); + return; + } + + /* Register control handler */ + service_handle = RegisterServiceCtrlHandlerEx(NSSM, service_control_handler, 0); + if (! service_handle) { + eventprintf(EVENTLOG_ERROR_TYPE, "service_main(): RegisterServiceCtrlHandlerEx() failed: %s", error_string(GetLastError())); + return; + } + + monitor_service(); +} + +int monitor_service() { + /* Set service status to started */ + int ret = start_service(); + if (ret) { + eventprintf(EVENTLOG_ERROR_TYPE, "Can't start service: error code %d", ret); + return ret; + } + eventprintf(EVENTLOG_INFORMATION_TYPE, "Started process %s %s in %s", exe, flags, dir); + + /* Monitor service service */ + if (! RegisterWaitForSingleObject(&wait_handle, pid, end_service, 0, INFINITE, WT_EXECUTEONLYONCE | WT_EXECUTELONGFUNCTION)) { + eventprintf(EVENTLOG_WARNING_TYPE, "RegisterWaitForSingleObject() returned %s - service may claim to be still running when %s exits ", error_string(GetLastError()), exe); + } + + return 0; +} + +/* Service control handler */ +unsigned long WINAPI service_control_handler(unsigned long control, unsigned long event, void *data, void *context) { + switch (control) { + case SERVICE_CONTROL_SHUTDOWN: + case SERVICE_CONTROL_STOP: + stop_service(0); + return NO_ERROR; + } + + /* Unknown control */ + return ERROR_CALL_NOT_IMPLEMENTED; +} + +/* Start the service */ +int start_service() { + if (pid) return 0; + + /* Allocate a STARTUPINFO structure for a new process */ + STARTUPINFO si; + ZeroMemory(&si, sizeof(si)); + si.cb = sizeof(si); + + /* Allocate a PROCESSINFO structure for the process */ + PROCESS_INFORMATION pi; + ZeroMemory(&pi, sizeof(pi)); + + /* Launch executable with arguments */ + char cmd[MAX_PATH]; + if (_snprintf(cmd, sizeof(cmd), "%s %s", exe, flags) < 0) { + eventprintf(EVENTLOG_ERROR_TYPE, "Error constructing command line"); + return stop_service(2); + } + if (! CreateProcess(0, cmd, 0, 0, 0, 0, 0, dir, &si, &pi)) { + eventprintf(EVENTLOG_ERROR_TYPE, "Can't launch %s. CreateProcess() returned %s", exe, error_string(GetLastError())); + return stop_service(3); + } + pid = pi.hProcess; + + /* Signal successful start */ + service_status.dwCurrentState = SERVICE_RUNNING; + SetServiceStatus(service_handle, &service_status); + + return 0; +} + +/* Stop the service */ +int stop_service(unsigned long exitcode) { + /* Signal we are stopping */ + service_status.dwCurrentState = SERVICE_STOP_PENDING; + SetServiceStatus(service_handle, &service_status); + + /* Nothing to do if server isn't running */ + if (pid) { + /* Shut down server */ + TerminateProcess(pid, 0); + pid = 0; + } + + /* Signal we stopped */ + service_status.dwCurrentState = SERVICE_STOPPED; + if (exitcode) { + service_status.dwWin32ExitCode = ERROR_SERVICE_SPECIFIC_ERROR; + service_status.dwServiceSpecificExitCode = exitcode; + } + else { + service_status.dwWin32ExitCode = NO_ERROR; + service_status.dwServiceSpecificExitCode = 0; + } + SetServiceStatus(service_handle, &service_status); + + return exitcode; +} + +/* Callback function triggered when the server exits */ +void CALLBACK end_service(void *arg, unsigned char why) { + /* Check exit code */ + unsigned long ret = 0; + GetExitCodeProcess(pid, &ret); + + /* Force an error code if none given, so system can restart this service */ + /*if (! ret) { + eventprintf(EVENTLOG_INFORMATION_TYPE, "Process exited with return code 0 - overriding with return code 111 so the service manager will notice the failure"); + ret = 111; + } + else */eventprintf(EVENTLOG_INFORMATION_TYPE, "Process %s exited with return code %u", exe, ret); + + /* Try to restart the service or return failure code to service manager */ + pid = 0; + while (monitor_service()) { + eventprintf(EVENTLOG_INFORMATION_TYPE, "Failed to restart %s - sleeping ", exe, ret); + Sleep(30000); + } +} diff --git a/service.h b/service.h new file mode 100644 index 0000000..4fee550 --- /dev/null +++ b/service.h @@ -0,0 +1,15 @@ +#ifndef SERVICE_H +#define SERVICE_H + +void WINAPI service_main(unsigned long, char **); +unsigned long WINAPI service_control_handler(unsigned long, unsigned long, void *, void *); + +SC_HANDLE open_service_manager(); +int install_service(char *); +int remove_service(char *); +int monitor_service(); +int start_service(); +int stop_service(unsigned long); +void CALLBACK end_service(void *, unsigned char); + +#endif