/*
 * Ceph - scalable distributed file system
 *
 * Copyright (C) 2019 SUSE LINUX GmbH
 *
 * This is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License version 2.1, as published by the Free Software
 * Foundation.  See file COPYING.
 *
 */

#define dout_context cct
#define dout_subsys ceph_subsys_

#include "common/debug.h"
#include "common/errno.h"
#include "common/win32/service.h"

// Initialize the singleton service instance.
ServiceBase *ServiceBase::s_service = NULL;

ServiceBase::ServiceBase(CephContext *cct_): cct(cct_)
{
  status.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
  status.dwControlsAccepted = SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_SHUTDOWN;
  status.dwCurrentState = SERVICE_START_PENDING;
  status.dwWin32ExitCode = NO_ERROR;
  status.dwCheckPoint = 0;
  /* The estimated time required for the stop operation in ms. */
  status.dwWaitHint = 0;
}

/* Register service action callbacks */
int ServiceBase::initialize(ServiceBase *service)
{
  s_service = service;

  SERVICE_TABLE_ENTRY service_table[] = {
    {"", (LPSERVICE_MAIN_FUNCTION)run},
    {NULL, NULL}
  };

  /* StartServiceCtrlDispatcher blocks until the service is stopped. */
  if (!StartServiceCtrlDispatcher(service_table)) {
    int err = GetLastError();
    lderr(service->cct) << "StartServiceCtrlDispatcher error: "
                        << err << dendl;
    return -EINVAL;
  }
  return 0;
}

void WINAPI ServiceBase::run()
{
  assert(s_service != NULL);

  /* Register the control handler. This function is called by the service
   * manager to stop the service. The service name that we're passing here
   * doesn't have to be valid as we're using SERVICE_WIN32_OWN_PROCESS. */
  s_service->hstatus = RegisterServiceCtrlHandler(
    "", (LPHANDLER_FUNCTION)control_handler);
  if (!s_service->hstatus) {
    lderr(s_service->cct) << "Could not initialize service control handler. "
                          << "Error: " << GetLastError() << dendl;
    return;
  }

  s_service->set_status(SERVICE_START_PENDING);

  // TODO: should we expect exceptions?
  ldout(s_service->cct, 5) << "Starting service." << dendl;
  int err = s_service->run_hook();
  if (err) {
    lderr(s_service->cct) << "Failed to start service. Error code: "
                          << err << dendl;
    s_service->shutdown(true);
  } else {
    ldout(s_service->cct, 5) << "Successfully started service." << dendl;
    s_service->set_status(SERVICE_RUNNING);
  }
}

void ServiceBase::shutdown(bool ignore_errors)
{
  DWORD original_state = status.dwCurrentState;
  set_status(SERVICE_STOP_PENDING);

  int err = shutdown_hook();
  if (err) {
    derr << "Shutdown service hook failed. Error code: " << err << dendl;
    if (ignore_errors) {
      derr << "Ignoring shutdown hook failure, marking the service as stopped."
           << dendl;
      set_status(SERVICE_STOPPED);
    } else {
      derr << "Reverting to original service state." << dendl;
      set_status(original_state);
    }
  } else {
    dout(5) << "Shutdown hook completed." << dendl;
    set_status(SERVICE_STOPPED);
  }
}

void ServiceBase::stop()
{
  DWORD original_state = status.dwCurrentState;
  set_status(SERVICE_STOP_PENDING);

  int err = stop_hook();
  if (err) {
    derr << "Service stop hook failed. Error code: " << err << dendl;
    set_status(original_state);
  } else {
    dout(5) << "Successfully stopped service." << dendl;
    set_status(SERVICE_STOPPED);
  }
}

/* This function is registered with the Windows services manager through
 * a call to RegisterServiceCtrlHandler() and will be called by the Windows
 * service manager asynchronously to stop the service. */
void ServiceBase::control_handler(DWORD request)
{
  switch (request) {
  case SERVICE_CONTROL_STOP:
    s_service->stop();
    break;
  case SERVICE_CONTROL_SHUTDOWN:
    s_service->shutdown();
    break;
  default:
    break;
  }
}

void ServiceBase::set_status(DWORD current_state, DWORD exit_code) {
  static DWORD dwCheckPoint = 1;
  if (current_state == SERVICE_RUNNING || current_state == SERVICE_STOPPED) {
    status.dwCheckPoint = dwCheckPoint++;
  }

  status.dwCurrentState = current_state;
  status.dwWin32ExitCode = exit_code;

  if (hstatus) {
    dout(5) << "Updating service service status (" << current_state
             << ") and exit code(" << exit_code << ")." << dendl;
    ::SetServiceStatus(hstatus, &status);
  } else {
    derr << "Service control handler not initialized. Cannot "
         << "update service status (" << current_state
         << ") and exit code(" << exit_code << ")." << dendl;
  }
}
