//%2003////////////////////////////////////////////////////////////////////////
//
// Copyright (c) 2000, 2001, 2002  BMC Software, Hewlett-Packard Development
// Company, L. P., IBM Corp., The Open Group, Tivoli Systems.
// Copyright (c) 2003 BMC Software; Hewlett-Packard Development Company, L. P.;
// IBM Corp.; EMC Corporation, The Open Group.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
// 
// THE ABOVE COPYRIGHT NOTICE AND THIS PERMISSION NOTICE SHALL BE INCLUDED IN
// ALL COPIES OR SUBSTANTIAL PORTIONS OF THE SOFTWARE. THE SOFTWARE IS PROVIDED
// "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
// LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
// ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
//==============================================================================
//
// Author: Tony Fiorentino (fiorentino_tony@emc.com)
//
//%/////////////////////////////////////////////////////////////////////////////
#include <time.h>
#include <stdio.h>
#include <windows.h>
#include <process.h>

#include "service.h"

//-------------------------------------------------------------------------
// G L O B A L S
//-------------------------------------------------------------------------
int                    Service::g_argc                  = 0;
char                 **Service::g_argv                  = NULL;
char                  *Service::g_service_name          = NULL;
char                  *Service::g_event_source          = NULL;
DWORD                  Service::g_flags                 = 0;
DWORD                  Service::g_current_state         = 0;
SERVICE_STATUS_HANDLE  Service::g_service_status_handle = 0;
SERVICE_MAIN_T         Service::g_service_main          = NULL;

//-------------------------------------------------------------------------
// P U B L I C
//-------------------------------------------------------------------------
Service::Service(void)
{
}

Service::Service(char *service_name)
{
  g_service_name = service_name;
}

Service::Service(char *service_name, char *event_source)
{
  g_event_source = event_source;
  g_service_name = service_name;
}

Service::~Service(void)
{
}

/*-------------------------------------------------------------------------*
 * Method: Install                                                         *
 *                                                                         *
 * Args:                                                                   *
 *   display_name                                                          *
 *     The service's display name (hopefully more descriptive, will show   *
 *     up in the Win32 Services MMC snapin as "Service Name").             *
 *                                                                         *
 *   exe_name:                                                             *
 *     The service's executable name (full path, please)                   *
 *                                                                         *
 *   flags:                                                                *
 *     Reserved.  Currently unused.                                        *
 *                                                                         *
 * Description:                                                            *
 *     This function creates the service.                                  *
 *                                                                         *
 * NOTE: If the process is successfully launched as a Win32 service, this  *
 *       function never returns, but calls exit() instead.                 *
 *-------------------------------------------------------------------------*/
Service::ReturnCode
Service::Install(
  char  *display_name,
  char  *description,
  char  *exe_name)
{
  ReturnCode status = SERVICE_RETURN_SUCCESS;
  SC_HANDLE sch;

  if (g_service_name == NULL || display_name == NULL || exe_name == NULL)
    return SERVICE_ERROR_NOT_FOUND; // SERVICE_ERROR_NOT_FOUND
  else if ((sch = OpenSCManager(NULL, NULL, SC_MANAGER_CREATE_SERVICE)) == NULL)
    status = get_error(GetLastError(), "open");
  else
    {
      SC_HANDLE service = CreateService( 
        sch,                       // SCManager database 
        g_service_name,            // name of service 
        display_name,              // service name to display 
        SERVICE_ALL_ACCESS,        // desired access 
        SERVICE_WIN32_OWN_PROCESS, // service type 
        SERVICE_DEMAND_START,      // start type 
        SERVICE_ERROR_NORMAL,      // error control type 
        exe_name,                  // service's binary 
        NULL,                      // no load ordering group 
        NULL,                      // no tag identifier 
        NULL,                      // no dependencies 
        NULL,                      // LocalSystem account 
        NULL);                     // no password 
 
      if (service == NULL) 
        {
          status = get_error(GetLastError(), "create");
          return status;
        }
      else 
        {
          change_service_description(service, description);
          CloseServiceHandle(service);
        }
  
      CloseServiceHandle(sch);
    }

  return status;
}

/*-------------------------------------------------------------------------*
 * Method: Remove                                                          *
 *                                                                         *
 * Description:                                                            *
 *     Removes the service.                                                *
 *-------------------------------------------------------------------------*/
Service::ReturnCode
Service::Remove(void)
{
  ReturnCode status = SERVICE_RETURN_SUCCESS;
  SC_HANDLE sch;

  if (g_service_name == NULL)
    return SERVICE_ERROR_NOT_FOUND; /* SERVICE_ERROR_NOT_FOUND */
  else if ((sch = OpenSCManager(NULL, NULL, SC_MANAGER_CREATE_SERVICE)) == NULL)
    status = get_error(GetLastError(), "open");
  else
    {
      SC_HANDLE service = OpenService(sch, g_service_name, DELETE);
 
      if (service == NULL) 
        {
          status = get_error(GetLastError(), "open");
        }
      else 
        {
          if (!DeleteService(service))
            {
              status = get_error(GetLastError(), "remove");
            }

          CloseServiceHandle(service);
        }
  
      CloseServiceHandle(sch);
    }

  return status;
}

/*-------------------------------------------------------------------------*
 * Method: Start                                                           *
 *                                                                         *
 * Args:                                                                   *
 *   wait_time:                                                            *
 *     The user supplied wait (~1 second per QueryServiceStatus() attempt) *
 *                                                                         *
 * Description:                                                            *
 *     Attempt to start the service.                                       *
 *-------------------------------------------------------------------------*/
Service::ReturnCode
Service::Start(int wait_time)
{
  ReturnCode      status = SERVICE_RETURN_SUCCESS;
  SERVICE_STATUS  service_status;
  SC_HANDLE       sch;

  if (g_service_name == NULL)
    return SERVICE_ERROR_NOT_FOUND; // SERVICE_ERROR_NOT_FOUND
  else if ((sch = OpenSCManager(NULL, NULL, GENERIC_READ)) == NULL)
    status = get_error(GetLastError(), "open");
  else
    {
      SC_HANDLE service = OpenService(sch, g_service_name, SERVICE_START | SERVICE_QUERY_STATUS);
 
      if (service == NULL) 
        status = get_error(GetLastError(), "open");
      else if (!StartService(service, 0, NULL))
        status = get_error(GetLastError(), "start");
      else 
        {
          int i, max = (wait_time > 0) ? wait_time : 5;

          // Loop up to max times waiting for the service 
          // state to change to RUNNING

          for (i = 0; i < max; i++)
            {
              if (!QueryServiceStatus(service, &service_status))
                {
                  status = get_error(GetLastError(), "query");
                  return status; // QUERY_FAIL
                }

              if (service_status.dwCurrentState == SERVICE_RUNNING)
                break;

              Sleep(1 * CLOCKS_PER_SEC);
            }
          
          status = (i < max) ? SERVICE_RETURN_SUCCESS : SERVICE_ERROR_REQUEST_TIMEOUT;

          CloseServiceHandle(service);
        }
 
      CloseServiceHandle(sch); 
    }

  return status;
} 

/*-------------------------------------------------------------------------*
 * Method: Stop                                                            *
 *                                                                         *
 * Args:                                                                   *
 *   wait_time:                                                            *
 *     The user supplied wait (~1 second per QueryServiceStatus() attempt) *
 *                                                                         *
 * Description:                                                            *
 *     Attempt to stop the service.                                        *
 *-------------------------------------------------------------------------*/
Service::ReturnCode 
Service::Stop(int wait_time)
{
  ReturnCode      status = SERVICE_RETURN_SUCCESS;
  SERVICE_STATUS  service_status;
  SC_HANDLE       sch;

  if (g_service_name == NULL)
    return SERVICE_ERROR_NOT_FOUND; // SERVICE_ERROR_NOT_FOUND
  else if ((sch = OpenSCManager(NULL, NULL, GENERIC_READ)) == NULL)
    status = get_error(GetLastError(), "open");
 //   show_error("OpenSCMManager", "service", GetLastError());
  else
    {
      SC_HANDLE service = OpenService(sch, g_service_name, SERVICE_STOP | SERVICE_QUERY_STATUS);
 
      if (service == NULL) 
        status = get_error(GetLastError(), "open");
      else if (!ControlService(service, SERVICE_CONTROL_STOP, &service_status))
        status = get_error(GetLastError(), "stop");
      else 
        {
          int i, max = (wait_time > 0) ? wait_time : 5;

          // Loop up to max times waiting for the service 
          // state to change to STOPPED

          for (i = 0; i < max; i++)
            {
              if (!QueryServiceStatus(service, &service_status))
                {
                  status = get_error(GetLastError(), "query");
                  return status; // QUERY_FAIL
                }

              if (service_status.dwCurrentState == SERVICE_STOPPED)
                break;

              Sleep(1 * CLOCKS_PER_SEC);
            }
          
          status = (i < max) ? SERVICE_RETURN_SUCCESS : SERVICE_ERROR_REQUEST_TIMEOUT;

          CloseServiceHandle(service);
        }
 
      CloseServiceHandle(sch); 
    }

  return status;
}

/*-------------------------------------------------------------------------*
 * Method: Run                                                             *
 *                                                                         *
 * Args:                                                                   *
 *   service_main:                                                         *
 *     The user supplied service_main function (not to be confused with    *
 *     real_service_main above)                                            *
 *                                                                         *
 *   flags:                                                                *
 *     Reserved.  Currently unused.                                        *
 *                                                                         *
 * Description:                                                            *
 *     This function interacts with the SCM to run the current process     *
 *     as a Win32 service.                                                 *
 *                                                                         *
 * NOTE: If the process is successfully launched as a Win32 service, this  *
 *       function never returns, but calls exit() instead.                 *
 *-------------------------------------------------------------------------*/

Service::ReturnCode 
Service::Run(SERVICE_MAIN_T service_main, DWORD flags)
{
  ReturnCode status = SERVICE_RETURN_SUCCESS;

  SERVICE_TABLE_ENTRY dispatchTable[] = 
  { 
    { g_service_name, real_service_main },
    { NULL,         NULL              } 
  };

  // Validate the arguments as best we can 

  if (g_service_name == NULL || service_main == NULL)
    return SERVICE_ERROR_NOT_FOUND; // SERVICE_ERROR_NOT_FOUND

  // Save parameters in global variables 

  g_flags        = flags;
  g_service_main = service_main;

  // Kick off the service 

  if (!StartServiceCtrlDispatcher(dispatchTable))
    {
      status = get_error(GetLastError(), "start");
      return status; // FAIL
    }

  // Don't call exit()
  return status;
}

/*-------------------------------------------------------------------------*
 * Method: GetState                                                        *
 *                                                                         *
 * Description:                                                            *
 *     Returns the state of the service into "state".                      *
 *-------------------------------------------------------------------------*/
Service::ReturnCode 
Service::GetState(State *state)
{
  ReturnCode      status = SERVICE_RETURN_SUCCESS;
  SERVICE_STATUS  service_status;
  SC_HANDLE       sch;

  if (g_service_name == NULL)
    return SERVICE_ERROR_NOT_FOUND; // SERVICE_ERROR_NOT_FOUND
  else if ((sch = OpenSCManager(NULL, NULL, GENERIC_READ)) == NULL)
    status = get_error(GetLastError(), "open");
  else
    {
      SC_HANDLE service = OpenService(sch, g_service_name, SERVICE_QUERY_STATUS);
 
      if (service == NULL) 
        status = get_error(GetLastError(), "open");
      else if (!QueryServiceStatus(service, &service_status))
        status = get_error(GetLastError(), "query");
      else
        {
          *state = get_state(service_status.dwCurrentState);
          CloseServiceHandle(service);
        }
  
      CloseServiceHandle(sch);
    }

  return status;
}

/*-------------------------------------------------------------------------*
 * Method: LogEvent                                                        *
 *                                                                         *
 * Args:                                                                   *
 *   event_type:                                                           *
 *     The Win32 event type. Valid event types are:                        *
 *      EVENTLOG_SUCCESS          : Success event                          *
 *      EVENTLOG_ERROR_TYPE       : Error event                            *
 *      EVENTLOG_WARNING_TYPE     : Warning event                          *
 *      EVENTLOG_INFORMATION_TYPE : Information event                      *
 *      EVENTLOG_AUDIT_SUCCESS    : Success audit event                    *
 *      EVENTLOG_AUDIT_FAILURE    : Failure audit event                    *
 *                                                                         *
 *   event_id:                                                             *
 *     A fancy name for error code or error number.                        *
 *                                                                         *
 *   string:                                                               *
 *     String to be logged or merged in with the error string in the       *
 *     message DLL.                                                        *
 *                                                                         *
 * Description:                                                            *
 *     This function provides a simple layer over the Win32 error logging  *
 *     API's.                                                              *
 *                                                                         *
 * Returns:                                                                *
 *     true  if event was successfully logged                              *
 *     false if the event could not be logged                              *
 *-------------------------------------------------------------------------*/
bool
Service::LogEvent(WORD event_type, DWORD event_id, const char *string)
{
  BOOL   status;
  HANDLE h_event_source = RegisterEventSource(NULL, g_event_source);

  if (h_event_source == NULL)
    FALSE;

  status = ReportEvent (h_event_source, 
                        event_type, 
                        0, 
                        event_id, 
                        NULL, 
                        1, 
                        0, 
                        &string, 
                        NULL);

  DeregisterEventSource(h_event_source);

  return (status == TRUE) ? true : false;
}

//-------------------------------------------------------------------------
// P R I V A T E
//-------------------------------------------------------------------------
/*-------------------------------------------------------------------------*
 * Routine: real_service_main                                              *
 *                                                                         *
 * Args:                                                                   *
 *   argc:                                                                 *
 *     The number of arguments in the argv array                           *
 *   argv:                                                                 *
 *     An array of strings representing the command line arguments         *
 *                                                                         *
 * Description:                                                            *
 *     This function is the real service main (as opposed to the user      *
 *     supplied service main, which is called service_main).               *
 *                                                                         *
 * Returns:                                                                *
 *     nothing                                                             *
 *-------------------------------------------------------------------------*/
void __stdcall
Service::real_service_main(DWORD argc, LPTSTR *argv)
{
  // Let the SCM know we're alive and kicking

  report_status(SERVICE_START_PENDING, NO_ERROR, 0, 5000);

  // If the command line arguments include the string "-debug" then
  // invoke the debugger

  if (check_args_for_string("-debug"))
    DebugBreak();

  // Save copy of argc and argc in global variables

  g_argc = argc;
  g_argv = argv;

  // Start service actions

  g_service_status_handle = RegisterServiceCtrlHandler (g_service_name, 
                                                        service_control_handler);

  if (g_service_status_handle == 0)
    {
      show_error("register", "Service Control Handler", GetLastError());
      report_status(SERVICE_STOPPED, NO_ERROR, 0, 5000);
      return;
    }

  // Change our state to RUNNING, then invoke the user supplied
  // service_main function. After the user's service_main exits,
  // change the service state to STOPPED.

  report_status(SERVICE_RUNNING, NO_ERROR, 0, 5000);
  g_service_main(STARTUP_FLAG, argc, argv);
  report_status(SERVICE_STOPPED, NO_ERROR, 0, 5000);

  return;
}

/*-------------------------------------------------------------------------*
 * Routine: check_args_for_string                                          *
 *                                                                         *
 * Args:                                                                   *
 *   string:                                                               *
 *     The string to match.                                                *
 *                                                                         *
 * Description:                                                            *
 *     This function iterates through the command line arguments searching *
 *     for the string specified by the string parameter.                   *
 *                                                                         *
 * Returns:                                                                *
 *     true  if the string was found                                       *
 *     false if the string was not found                                   *
 *-------------------------------------------------------------------------*/
bool
Service::check_args_for_string(char *string)
{
  int i;

  for (i = 1; i < g_argc; i++)
    {
      if (strcmp(g_argv[i], string) == 0)
        return true;
    }

  return false;
}

/*-------------------------------------------------------------------------*
 * Method: service_control_handler                                         *
 *                                                                         *
 * Args:                                                                   *
 *   control:                                                              *
 *     The control sent from the SCM telling us what action to take:       *
 *     start, stop, pause, continue, etc.                                  *
 *                                                                         *
 * Description:                                                            *
 *     This function handles control messages sent from the SCM. Currently *
 *     the only message that is handled differently is the STOP message,   *
 *     which invokes the user's service main function passing to it the    *
 *     SHUTDOWN_FLAG. The user is then responsible to perform all shutdown *
 *     related tasks.                                                      *
 *                                                                         *
 * Returns:                                                                *
 *     Nothing                                                             *
 *-------------------------------------------------------------------------*/

void WINAPI
Service::service_control_handler(DWORD control)
{
  /* Currently, only the stop contol requires special handling */

  if (control == SERVICE_CONTROL_STOP)
    {
      report_status(SERVICE_STOP_PENDING, NO_ERROR, 0, 5000);
      g_service_main(SHUTDOWN_FLAG, g_argc, g_argv);
      return;
    }

  /* For every other control, just send back our current state */

  report_status(g_current_state, NO_ERROR, 0, 5000);
}

/*-------------------------------------------------------------------------*
 * Method: report_status                                                   *
 *                                                                         *
 * Args:                                                                   *
 *   current_state:                                                        *
 *     The service's new state. May be any valid Win32 service state.      *
 *                                                                         *
 *   exit_code:                                                            *
 *     This must be a Win32 exit code. If the server is exiting due to     *
 *     an error returned from the Win32 API, then this is the place to     *
 *     report the error status. Most of the time, this will be NO_ERROR.   *
 *                                                                         *
 *   check_point:                                                          *
 *     An integer value that should start at zero and increment as each    *
 *     discrete step within a phase is completed. For example, if startup  *
 *     consists of 3 steps, then the startup will issue its first check-   *
 *     point of zero, then increment up through and including 2 as each    *
 *     step is finished.                                                   *
 *                                                                         *
 *   wait_hint:                                                            *
 *     Tells the SCM how long to expect to wait for the next status        *
 *     update.                                                             *
 *                                                                         *
 * Description:                                                            *
 *     This function provides an even higher level of abstraction over     *
 *     the Win32 event logging API's.                                      *
 *                                                                         *
 * Returns:                                                                *
 *     true  if the status was successfully reported                       *
 *     false if the status could not be reported                           *
 *-------------------------------------------------------------------------*/
bool
Service::report_status(
  DWORD current_state, 
  DWORD exit_code, 
  DWORD check_point, 
  DWORD wait_hint)
{
  SERVICE_STATUS current_status = 
  {
    SERVICE_WIN32,
    current_state,
    SERVICE_CONTROL_INTERROGATE,
    exit_code,
    0,
    check_point,
    wait_hint
  };

  /* Wait until we're started before we accept a stop control */

  if (current_state == SERVICE_RUNNING)
    current_status.dwControlsAccepted += SERVICE_ACCEPT_STOP;

  /* Save new state */

  g_current_state = current_state;
  
  return (SetServiceStatus(g_service_status_handle, &current_status) == TRUE) ? true : false;
}

/*-------------------------------------------------------------------------*
 * Method: change_service_description                                      *
 *                                                                         *
 * Args:                                                                   *
 *   service:                                                              *
 *     Handle to the service                                               *
 *                                                                         *
 *   description:                                                          *
 *     The service's description                                           *
 *                                                                         *
 * Description:                                                            *
 *     This function sets the description for the service. The description *
 *     text shows up in the Services mmc snapin. The description is added  *
 *     as a benefit to users, but does not affect the service's operation. *
 *     Therefore, if this function should fail for any reason, there is    *
 *     no need to stop. That's why this function has no return value.      *
 *                                                                         *
 * Returns:                                                                *
 *     Nothing                                                             *
 *                                                                         *
 * Notes:                                                                  *
 *     This function uses the ChangeServiceConfig2() API function which    *
 *     first appeared in Windows2000. Therefore, this code checks the      *
 *     OS version, before calling ChangeServiceConfig2().                  *
 *-------------------------------------------------------------------------*/
void 
Service::change_service_description(SC_HANDLE service, char *description)
{
  OSVERSIONINFO osvi;

  /* 
   *  Test for Windows 2000 or greater
   */ 
  
  osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);

  if (GetVersionEx(&osvi) != 0                      && 
      osvi.dwPlatformId    == VER_PLATFORM_WIN32_NT && 
      osvi.dwMajorVersion  >= 5)
    {
      typedef BOOL (WINAPI *CHANGE_SERVICE_CONFIG2_T)(SC_HANDLE, DWORD, LPVOID);

      HINSTANCE hdll = LoadLibrary("advapi32.dll");

      if (hdll != NULL)
        {
          SERVICE_DESCRIPTION      sd;
          CHANGE_SERVICE_CONFIG2_T csc;

          csc = (CHANGE_SERVICE_CONFIG2_T) GetProcAddress(hdll, "ChangeServiceConfig2A");

          if (csc)
            {
              sd.lpDescription = description;
              csc(service, SERVICE_CONFIG_DESCRIPTION, &sd);
            }

          FreeLibrary(hdll);
        }
    }
}

/*-------------------------------------------------------------------------*
 * Method: show_error                                                      *
 *                                                                         *
 * Args:                                                                   *
 *   action:                                                               *
 *     A single verb decribing the action going on when the error          *
 *     occurred. For example, "opening", "creating", etc.                  *
 *                                                                         *
 *   object:                                                               *
 *     Description of the object on which was action occurred. Examples    *
 *     are "file", "service", etc.                                         *
 *                                                                         *
 *   hr:                                                                   *
 *     The error status. Can be an hresult, or Win32 error status.         *
 *                                                                         *
 * Description:                                                            *
 *     This function provides an even higher level of abstraction over     *
 *     the Win32 event logging API's.                                      *
 *                                                                         *
 * Returns:                                                                *
 *     true  if event was successfully logged                              *
 *     false if the event could not be logged                              *
 *-------------------------------------------------------------------------*/
bool
Service::show_error(const char *action, const char *object, DWORD hr)
{
  char console_title[_MAX_PATH] = {0};
  char  txt[_MAX_PATH]          = "";
  char  msg[_MAX_PATH]          = "";
  DWORD nchars;

  nchars = FormatMessage(
    FORMAT_MESSAGE_FROM_SYSTEM,
    NULL,
    hr,
    MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
    msg,
    sizeof(msg),
    NULL);

  if (nchars == 0) 
    sprintf(msg, "Unknown error code - %%X%x", hr);
  else if (nchars > 1)
    {
      if (msg[nchars - 1] == '\n') msg[nchars - 1] = '\0';
      if (msg[nchars - 2] == '\r') msg[nchars - 2] = '\0';
    }

  sprintf(txt, "Failed to %s %s %s. Reason: %s", action, object, g_service_name, msg);

  // Running from a console window
  // send courtesy message txt to stderr
  if (GetConsoleTitle(console_title, _MAX_PATH) > 0)
    {
      PEGASUS_STD(cerr) << txt << PEGASUS_STD(endl);
    }

  return LogEvent(EVENTLOG_ERROR_TYPE, 1, txt);
}

Service::State
Service::get_state(DWORD scm_state)
{
  return (State)scm_state;
}

Service::ReturnCode 
Service::get_error(DWORD error_status, const char *action)
{
  switch (error_status)
    {
    /*
      // INFO: Could add cases to suppress error message.
      case ERROR_SERVICE_DOES_NOT_EXIST:
      case ERROR_SERVICE_CANNOT_ACCEPT_CTRL:
        break;
    */
      case SERVICE_RETURN_SUCCESS:
        break;

      default:
        show_error(action, "service", error_status); 
        break;
    }
  return (ReturnCode)error_status;
}

