service-win32/service.c
1bda73a7
 /*---------------------------------------------------------------------------
 THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
 ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED
 TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
 PARTICULAR PURPOSE.
 
 Copyright (C) 1993 - 2000.  Microsoft Corporation.  All rights reserved.
 
 MODULE:   service.c
 
 PURPOSE:  Implements functions required by all Windows NT services
 
 FUNCTIONS:
   main(int argc, char **argv);
   service_ctrl(DWORD dwCtrlCode);
   service_main(DWORD dwArgc, LPTSTR *lpszArgv);
   CmdInstallService();
   CmdRemoveService();
   CmdStartService();
   CmdDebugService(int argc, char **argv);
   ControlHandler ( DWORD dwCtrlType );
   GetLastErrorText( LPTSTR lpszBuf, DWORD dwSize );
 
 ---------------------------------------------------------------------------*/
 
 #include <windows.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <process.h>
 #include <tchar.h>
 
 #include "service.h"
 
 // internal variables
 SERVICE_STATUS          ssStatus;       // current status of the service
 SERVICE_STATUS_HANDLE   sshStatusHandle;
 DWORD                   dwErr = 0;
 BOOL                    bDebug = FALSE;
 TCHAR                   szErr[256];
 
 // internal function prototypes
 VOID WINAPI service_ctrl(DWORD dwCtrlCode);
 VOID WINAPI service_main(DWORD dwArgc, LPTSTR *lpszArgv);
 int CmdInstallService();
 int CmdRemoveService();
 int CmdStartService();
 VOID CmdDebugService(int argc, char **argv);
 BOOL WINAPI ControlHandler ( DWORD dwCtrlType );
 LPTSTR GetLastErrorText( LPTSTR lpszBuf, DWORD dwSize );
 
 //
 //  FUNCTION: main
 //
 //  PURPOSE: entrypoint for service
 //
 //  PARAMETERS:
 //    argc - number of command line arguments
 //    argv - array of command line arguments
 //
 //  RETURN VALUE:
 //    none
 //
 //  COMMENTS:
 //    main() either performs the command line task, or
 //    call StartServiceCtrlDispatcher to register the
 //    main service thread.  When the this call returns,
 //    the service has stopped, so exit.
 //
 int __cdecl main(int argc, char **argv)
 {
    SERVICE_TABLE_ENTRY dispatchTable[] =
    {
       { TEXT(SZSERVICENAME), (LPSERVICE_MAIN_FUNCTION)service_main},
       { NULL, NULL}
    };
 
    if ( (argc > 1) &&
         ((*argv[1] == '-') || (*argv[1] == '/')) )
    {
       if ( _stricmp( "install", argv[1]+1 ) == 0 )
       {
          return CmdInstallService();
       }
       else if ( _stricmp( "remove", argv[1]+1 ) == 0 )
       {
          return CmdRemoveService();
       }
 	  else if ( _stricmp( "start", argv[1]+1 ) == 0)
 	  {
 		  return CmdStartService();
       }
       else if ( _stricmp( "debug", argv[1]+1 ) == 0 )
       {
          bDebug = TRUE;
          CmdDebugService(argc, argv);
       }
       else
       {
          goto dispatch;
       }
       return 0;
    }
 
    // if it doesn't match any of the above parameters
    // the service control manager may be starting the service
    // so we must call StartServiceCtrlDispatcher
    dispatch:
    // this is just to be friendly
    printf( "%s -install          to install the service\n", SZAPPNAME );
    printf( "%s -start			 to start the service\n", SZAPPNAME );
    printf( "%s -remove           to remove the service\n", SZAPPNAME );
    printf( "%s -debug <params>   to run as a console app for debugging\n", SZAPPNAME );
    printf( "\nStartServiceCtrlDispatcher being called.\n" );
    printf( "This may take several seconds.  Please wait.\n" );
 
    if (!StartServiceCtrlDispatcher(dispatchTable))
       AddToMessageLog(MSG_FLAGS_ERROR, TEXT("StartServiceCtrlDispatcher failed."));
 
    return 0;
 }
 
 
 
 //
 //  FUNCTION: service_main
 //
 //  PURPOSE: To perform actual initialization of the service
 //
 //  PARAMETERS:
 //    dwArgc   - number of command line arguments
 //    lpszArgv - array of command line arguments
 //
 //  RETURN VALUE:
 //    none
 //
 //  COMMENTS:
 //    This routine performs the service initialization and then calls
 //    the user defined ServiceStart() routine to perform majority
 //    of the work.
 //
 void WINAPI service_main(DWORD dwArgc, LPTSTR *lpszArgv)
 {
 
    // register our service control handler:
    //
    sshStatusHandle = RegisterServiceCtrlHandler( TEXT(SZSERVICENAME), service_ctrl);
 
    if (!sshStatusHandle)
       goto cleanup;
 
    // SERVICE_STATUS members that don't change in example
    //
    ssStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
    ssStatus.dwServiceSpecificExitCode = 0;
 
 
    // report the status to the service control manager.
    //
    if (!ReportStatusToSCMgr(
                            SERVICE_START_PENDING, // service state
                            NO_ERROR,              // exit code
                            3000))                 // wait hint
       goto cleanup;
 
 
    ServiceStart( dwArgc, lpszArgv );
 
    cleanup:
 
    // try to report the stopped status to the service control manager.
    //
    if (sshStatusHandle)
       (VOID)ReportStatusToSCMgr(
                                SERVICE_STOPPED,
                                dwErr,
                                0);
 
    return;
 }
 
 
 
 //
 //  FUNCTION: service_ctrl
 //
 //  PURPOSE: This function is called by the SCM whenever
 //           ControlService() is called on this service.
 //
 //  PARAMETERS:
 //    dwCtrlCode - type of control requested
 //
 //  RETURN VALUE:
 //    none
 //
 //  COMMENTS:
 //
 VOID WINAPI service_ctrl(DWORD dwCtrlCode)
 {
    // Handle the requested control code.
    //
    switch (dwCtrlCode)
    {
    // Stop the service.
    //
    // SERVICE_STOP_PENDING should be reported before
    // setting the Stop Event - hServerStopEvent - in
    // ServiceStop().  This avoids a race condition
    // which may result in a 1053 - The Service did not respond...
    // error.
    case SERVICE_CONTROL_STOP:
       ReportStatusToSCMgr(SERVICE_STOP_PENDING, NO_ERROR, 0);
       ServiceStop();
       return;
 
       // Update the service status.
       //
    case SERVICE_CONTROL_INTERROGATE:
       break;
 
       // invalid control code
       //
    default:
       break;
 
    }
 
    ReportStatusToSCMgr(ssStatus.dwCurrentState, NO_ERROR, 0);
 }
 
 
 
 //
 //  FUNCTION: ReportStatusToSCMgr()
 //
 //  PURPOSE: Sets the current status of the service and
 //           reports it to the Service Control Manager
 //
 //  PARAMETERS:
 //    dwCurrentState - the state of the service
 //    dwWin32ExitCode - error code to report
 //    dwWaitHint - worst case estimate to next checkpoint
 //
 //  RETURN VALUE:
 //    TRUE  - success
 //    FALSE - failure
 //
 //  COMMENTS:
 //
 BOOL ReportStatusToSCMgr(DWORD dwCurrentState,
                          DWORD dwWin32ExitCode,
                          DWORD dwWaitHint)
 {
    static DWORD dwCheckPoint = 1;
    BOOL fResult = TRUE;
 
 
    if ( !bDebug ) // when debugging we don't report to the SCM
    {
       if (dwCurrentState == SERVICE_START_PENDING)
          ssStatus.dwControlsAccepted = 0;
       else
          ssStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP;
 
       ssStatus.dwCurrentState = dwCurrentState;
       ssStatus.dwWin32ExitCode = dwWin32ExitCode;
       ssStatus.dwWaitHint = dwWaitHint;
 
       if ( ( dwCurrentState == SERVICE_RUNNING ) ||
            ( dwCurrentState == SERVICE_STOPPED ) )
          ssStatus.dwCheckPoint = 0;
       else
          ssStatus.dwCheckPoint = dwCheckPoint++;
 
 
       // Report the status of the service to the service control manager.
       //
       if (!(fResult = SetServiceStatus( sshStatusHandle, &ssStatus)))
       {
          AddToMessageLog(MSG_FLAGS_ERROR, TEXT("SetServiceStatus"));
       }
    }
    return fResult;
 }
 
 
 
 //
 //  FUNCTION: AddToMessageLog(LPTSTR lpszMsg)
 //
 //  PURPOSE: Allows any thread to log an error message
 //
 //  PARAMETERS:
 //    lpszMsg - text for message
 //
 //  RETURN VALUE:
 //    none
 //
 //  COMMENTS:
 //
 void AddToMessageLog(DWORD flags, LPTSTR lpszMsg)
 {
    TCHAR szMsg [(sizeof(SZSERVICENAME) / sizeof(TCHAR)) + 100 ];
    HANDLE  hEventSource;
    LPCSTR  lpszStrings[2];
 
    if ( !bDebug )
    {
      if (flags & MSG_FLAGS_SYS_CODE)
       dwErr = GetLastError();
      else
        dwErr = 0;
 
       // Use event logging to log the error.
       //
       hEventSource = RegisterEventSource(NULL, TEXT(SZSERVICENAME));
 
       _stprintf(szMsg, TEXT("%s error: %d"), TEXT(SZSERVICENAME), (int)dwErr);
       lpszStrings[0] = szMsg;
       lpszStrings[1] = lpszMsg;
 
       if (hEventSource != NULL)
       {
          ReportEvent(hEventSource, // handle of event source
 		     // event type
                      (flags & MSG_FLAGS_ERROR)
 		       ? EVENTLOG_ERROR_TYPE : EVENTLOG_INFORMATION_TYPE,
                      0,                    // event category
                      0,                    // event ID
                      NULL,                 // current user's SID
                      2,                    // strings in lpszStrings
                      0,                    // no bytes of raw data
                      lpszStrings,          // array of error strings
                      NULL);                // no raw data
 
          (VOID) DeregisterEventSource(hEventSource);
       }
    }
 }
 
 void ResetError (void)
 {
   dwErr = 0;
 }
 
 ///////////////////////////////////////////////////////////////////
 //
 //  The following code handles service installation and removal
 //
 
 
 //
 //  FUNCTION: CmdInstallService()
 //
 //  PURPOSE: Installs the service
 //
 //  PARAMETERS:
 //    none
 //
 //  RETURN VALUE:
 //    0 if success
 //
 //  COMMENTS:
 //
 int CmdInstallService()
 {
    SC_HANDLE   schService;
    SC_HANDLE   schSCManager;
 
    TCHAR szPath[512];
 
    int ret = 0;
 
    if ( GetModuleFileName( NULL, szPath, 512 ) == 0 )
    {
       _tprintf(TEXT("Unable to install %s - %s\n"), TEXT(SZSERVICEDISPLAYNAME), GetLastErrorText(szErr, 256));
       return 1;
    }
 
    schSCManager = OpenSCManager(
                                NULL,                   // machine (NULL == local)
                                NULL,                   // database (NULL == default)
                                SC_MANAGER_CONNECT | SC_MANAGER_CREATE_SERVICE  // access required
                                );
    if ( schSCManager )
    {
       schService = CreateService(
                                 schSCManager,               // SCManager database
                                 TEXT(SZSERVICENAME),        // name of service
                                 TEXT(SZSERVICEDISPLAYNAME), // name to display
                                 SERVICE_QUERY_STATUS,         // desired access
                                 SERVICE_WIN32_OWN_PROCESS,  // service type
 				SERVICE_DEMAND_START,        // start type -- alternative: SERVICE_AUTO_START
                                 SERVICE_ERROR_NORMAL,       // error control type
                                 szPath,                     // service's binary
                                 NULL,                       // no load ordering group
                                 NULL,                       // no tag identifier
                                 TEXT(SZDEPENDENCIES),       // dependencies
                                 NULL,                       // LocalSystem account
                                 NULL);                      // no password
 
       if ( schService )
       {
          _tprintf(TEXT("%s installed.\n"), TEXT(SZSERVICEDISPLAYNAME) );
          CloseServiceHandle(schService);
       }
       else
       {
          _tprintf(TEXT("CreateService failed - %s\n"), GetLastErrorText(szErr, 256));
 	 ret = 1;
       }
 
       CloseServiceHandle(schSCManager);
    }
    else
      {
       _tprintf(TEXT("OpenSCManager failed - %s\n"), GetLastErrorText(szErr,256));
        ret = 1;
      }
    return ret;
 }
 
 //
 //  FUNCTION: CmdStartService()
 //
 //  PURPOSE: Start the service
 //
 //  PARAMETERS:
 //    none
 //
 //  RETURN VALUE:
 //    0 if success
 //
 //  COMMENTS:
 
 int CmdStartService()
 {
   int ret = 0;
 
   SC_HANDLE schSCManager;
   SC_HANDLE schService;
 
 
     // Open a handle to the SC Manager database. 
     schSCManager = OpenSCManager( 
        NULL,                    // local machine 
        NULL,                    // ServicesActive database 
        SC_MANAGER_ALL_ACCESS);  // full access rights 
    
     if (NULL == schSCManager) {
        _tprintf(TEXT("OpenSCManager failed - %s\n"), GetLastErrorText(szErr,256));
        ret = 1;
     }
 
     schService = OpenService( 
         schSCManager,          // SCM database 
         SZSERVICENAME,         // service name
         SERVICE_ALL_ACCESS); 
 
     if (schService == NULL) {
       _tprintf(TEXT("OpenService failed - %s\n"), GetLastErrorText(szErr,256));
        ret = 1;
     }
  
     if (!StartService(
             schService,  // handle to service 
             0,           // number of arguments 
             NULL) )      // no arguments 
     {
       _tprintf(TEXT("StartService failed - %s\n"), GetLastErrorText(szErr,256));
        ret = 1;
     }
     else
 	{
 		_tprintf(TEXT("Service Started\n"));
        ret = 0;
 	}
     CloseServiceHandle(schService); 
     CloseServiceHandle(schSCManager);
     return ret;
 }
 
 //
 //  FUNCTION: CmdRemoveService()
 //
 //  PURPOSE: Stops and removes the service
 //
 //  PARAMETERS:
 //    none
 //
 //  RETURN VALUE:
 //    0 if success
 //
 //  COMMENTS:
 //
 int CmdRemoveService()
 {
    SC_HANDLE   schService;
    SC_HANDLE   schSCManager;
 
    int ret = 0;
 
    schSCManager = OpenSCManager(
                                NULL,                   // machine (NULL == local)
                                NULL,                   // database (NULL == default)
                                SC_MANAGER_CONNECT   // access required
                                );
    if ( schSCManager )
    {
       schService = OpenService(schSCManager, TEXT(SZSERVICENAME), DELETE | SERVICE_STOP | SERVICE_QUERY_STATUS);
 
       if (schService)
       {
          // try to stop the service
          if ( ControlService( schService, SERVICE_CONTROL_STOP, &ssStatus ) )
          {
             _tprintf(TEXT("Stopping %s."), TEXT(SZSERVICEDISPLAYNAME));
             Sleep( 1000 );
 
             while ( QueryServiceStatus( schService, &ssStatus ) )
             {
                if ( ssStatus.dwCurrentState == SERVICE_STOP_PENDING )
                {
                   _tprintf(TEXT("."));
                   Sleep( 1000 );
                }
                else
                   break;
             }
 
             if ( ssStatus.dwCurrentState == SERVICE_STOPPED )
                _tprintf(TEXT("\n%s stopped.\n"), TEXT(SZSERVICEDISPLAYNAME) );
             else
 	      {
                _tprintf(TEXT("\n%s failed to stop.\n"), TEXT(SZSERVICEDISPLAYNAME) );
 		ret = 1;
 	      }
 
          }
 
          // now remove the service
          if ( DeleteService(schService) )
             _tprintf(TEXT("%s removed.\n"), TEXT(SZSERVICEDISPLAYNAME) );
          else
 	   {
             _tprintf(TEXT("DeleteService failed - %s\n"), GetLastErrorText(szErr,256));
 	     ret = 1;
 	   }
 
 
          CloseServiceHandle(schService);
       }
       else
 	{
          _tprintf(TEXT("OpenService failed - %s\n"), GetLastErrorText(szErr,256));
 	  ret = 1;
 	}
 
       CloseServiceHandle(schSCManager);
    }
    else
      {
       _tprintf(TEXT("OpenSCManager failed - %s\n"), GetLastErrorText(szErr,256));
        ret = 1;
      }
    return ret;
 }
 
 
 
 
 ///////////////////////////////////////////////////////////////////
 //
 //  The following code is for running the service as a console app
 //
 
 
 //
 //  FUNCTION: CmdDebugService(int argc, char ** argv)
 //
 //  PURPOSE: Runs the service as a console application
 //
 //  PARAMETERS:
 //    argc - number of command line arguments
 //    argv - array of command line arguments
 //
 //  RETURN VALUE:
 //    none
 //
 //  COMMENTS:
 //
 void CmdDebugService(int argc, char ** argv)
 {
    DWORD dwArgc;
    LPTSTR *lpszArgv;
 
 #ifdef UNICODE
    lpszArgv = CommandLineToArgvW(GetCommandLineW(), &(dwArgc) );
    if (NULL == lpszArgv)
    {
        // CommandLineToArvW failed!!
        _tprintf(TEXT("CmdDebugService CommandLineToArgvW returned NULL\n"));
        return;
    }
 #else
    dwArgc   = (DWORD) argc;
    lpszArgv = argv;
 #endif
 
    _tprintf(TEXT("Debugging %s.\n"), TEXT(SZSERVICEDISPLAYNAME));
 
    SetConsoleCtrlHandler( ControlHandler, TRUE );
 
    ServiceStart( dwArgc, lpszArgv );
 
 #ifdef UNICODE
 // Must free memory allocated for arguments
 
    GlobalFree(lpszArgv);
 #endif // UNICODE
 
 }
 
 
 //
 //  FUNCTION: ControlHandler ( DWORD dwCtrlType )
 //
 //  PURPOSE: Handled console control events
 //
 //  PARAMETERS:
 //    dwCtrlType - type of control event
 //
 //  RETURN VALUE:
 //    True - handled
 //    False - unhandled
 //
 //  COMMENTS:
 //
 BOOL WINAPI ControlHandler ( DWORD dwCtrlType )
 {
    switch ( dwCtrlType )
    {
    case CTRL_BREAK_EVENT:  // use Ctrl+C or Ctrl+Break to simulate
    case CTRL_C_EVENT:      // SERVICE_CONTROL_STOP in debug mode
       _tprintf(TEXT("Stopping %s.\n"), TEXT(SZSERVICEDISPLAYNAME));
       ServiceStop();
       return TRUE;
       break;
 
    }
    return FALSE;
 }
 
 //
 //  FUNCTION: GetLastErrorText
 //
 //  PURPOSE: copies error message text to string
 //
 //  PARAMETERS:
 //    lpszBuf - destination buffer
 //    dwSize - size of buffer
 //
 //  RETURN VALUE:
 //    destination buffer
 //
 //  COMMENTS:
 //
 LPTSTR GetLastErrorText( LPTSTR lpszBuf, DWORD dwSize )
 {
    DWORD dwRet;
    LPTSTR lpszTemp = NULL;
 
    dwRet = FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM |FORMAT_MESSAGE_ARGUMENT_ARRAY,
                           NULL,
                           GetLastError(),
                           LANG_NEUTRAL,
                           (LPTSTR)&lpszTemp,
                           0,
                           NULL );
 
    // supplied buffer is not long enough
    if ( !dwRet || ( (long)dwSize < (long)dwRet+14 ) )
       lpszBuf[0] = TEXT('\0');
    else
    {
       lpszTemp[lstrlen(lpszTemp)-2] = TEXT('\0');  //remove cr and newline character
       _stprintf( lpszBuf, TEXT("%s (0x%x)"), lpszTemp, (int)GetLastError() );
    }
 
    if ( lpszTemp )
       LocalFree((HLOCAL) lpszTemp );
 
    return lpszBuf;
 }