ntservice.cc #1

  • //
  • guest/
  • perforce_software/
  • p4/
  • 2014-2/
  • sys/
  • ntservice.cc
  • View
  • Commits
  • Open Download .zip Download (10 KB)
/*
 * /+\
 * +\	Copyright 1995, 2000 Perforce Software.	 All rights reserved.
 * \+/
 *
 * This file is part of Perforce - the FAST SCM System.
 */

# define NEED_WINDOWSH

# include <stdhdrs.h>

# include <strbuf.h>
# include <error.h>
# include <filesys.h>
# include <errorlog.h>
# include <threading.h>

# include "ntservice.h"


#define ASSERT(f)

//
// The static global_this pointer is required so that static callbacks can
// access the instances' data. This scheme fails if there is more than one
// instance. This is not a problem for services of the OWN process type.
//

NtService *NtService::global_this = NULL;

// Implementation of NtService class.

//
// Construction:
//
// In order to create an NtService object an entry point must be specified.
// The entry_point is the function where the service begins execution when it
// is started, and it cannot be main because main attaches to the SCM.
// The SCM ( Service Control Manager ) ultimately calls entry_point.
//

NtService::NtService()
{
	global_this = this;

	// We want to allow our service to stop on user request and during
	// a system shutdown.  The system shutdown will not issue a stop
	// request.  We have to allow and process the shutdown request.

	status.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
	status.dwCurrentState = SERVICE_START_PENDING;
	status.dwControlsAccepted =
		SERVICE_ACCEPT_STOP|SERVICE_ACCEPT_SHUTDOWN; // no pause
	status.dwWin32ExitCode = 0;
	status.dwServiceSpecificExitCode = 0;

	// Start with a check point of zero, the initial value only matters
	// if we have an error.  WaitHint=0 and CheckPoint=0 is a special
	// indicator to the SCM that we died on startup.  We use an initial
	// of 1000ms as a marker.  Normally we should never see this
	// value since the Perforce service should be using a higher value.
	// The SCM default wait hint seems to be 2000ms.
	// Using the svcinst tool with -d will show you the wait hints.

	status.dwCheckPoint = 0;
	status.dwWaitHint = 1000;

	entry_point = 0;
	statusHandle = 0;
}

NtService::~NtService()
{
	global_this = NULL;
}

/*
 * NtService::Start()
 *
 * The dispatcher must be called with 8 to 10 seconds upon execution.
 * This function begins the process of bringing up the Service.  When the
 * dispatch table is registered with the Service Control Manager, the
 * StaticRun entry point is called.  We set the start_pending status
 * with a hint indicating we expect to take 10 seconds to start.
 * 
 * Since this class only supports a single service per executable, it
 * simply creates a dispatch table with a single entry and starts the
 * dispatcher.
 *
 * StaticRun() calls global_this->run().  This added complexity
 * allows the startup behavior to be determined by the objects class
 * through the virtual run() method.  The global_this works because
 * there is only one NtService per process; however, multiple
 * services per process requires a different approach.
 */

void NtService::Start( int (*entryPt)( DWORD, char ** ), char *svc, Error *e )
{
	SERVICE_TABLE_ENTRY dispatch_table[] =
	{
	    { TEXT(""), (void (__stdcall *)( DWORD, char **) )StaticRun },
	    { NULL, NULL }
	} ;

	entry_point = entryPt;

	// This API essentially does a CreateThread with StaticRun as the
	// entry point.  This thread will wait until the Windows Service
	// enters a stopped state.  Then we return to main / ntService,
	// which returns to the CRT, which then calls doexit.  We want to
	// make sure nt_main is done before this thread continues.  We
	// only set the Service state to stopped at the end of nt_main.

	if( StartServiceCtrlDispatcher( dispatch_table ) )
	    return;

	e->Sys( "StartServiceCtrlDispatcher", svc );
	e->Set( E_FATAL, "Can not start the Perforce Service" );
}

/*
 * NtService::SetStatus()
 *
 * Call this function to inform the service control manager of changes in the
 * status of the service and to report errors. There are various points at which
 * this function must be called. For example the entry_point function should
 * call set_status( running ) as soon as the service has successfully started.
 * If initialization takes more than about 20 seconds set_status( start_pending)
 * should be called by entry_point at regular intervals.
 *
 * The check_point and wait_hint parameters can be used to inform the SCM of
 * progress if initialization takes a particularly long time. The wait_hint is
 * in milliseconds and the check_point is simply incremented as things progress.
 * This holds true for pause_pending and continue_pending states too.
 *
 * The win32_exitcode allows you to report an error from GetLastError() and
 * the specific_code allows you to specify an application specific error.
 */

void NtService::SetStatus( 
	states state,
	DWORD win32_exitcode, 
	DWORD specific_code,
	DWORD wait_hint )
{
	ASSERT( statusHandle );

	switch( state )
	{
	case no_change:
		break;
	case running:
		status.dwCurrentState = SERVICE_RUNNING;
		break;
	case stopped:
		status.dwCurrentState = SERVICE_STOPPED;
		break;
	case paused:
		status.dwCurrentState = SERVICE_PAUSED;
		break;
	case start_pending:
		status.dwCurrentState = SERVICE_START_PENDING;
		break;
	case stop_pending:
		status.dwCurrentState = SERVICE_STOP_PENDING;
		break;
	case pause_pending:
		status.dwCurrentState = SERVICE_PAUSE_PENDING;
		break;
	case continue_pending:
		status.dwCurrentState = SERVICE_CONTINUE_PENDING;
		break;
	default:
		ASSERT(0);
		break;
	}

	if( state != no_change )
	{
		status.dwWin32ExitCode = win32_exitcode;
		status.dwServiceSpecificExitCode = specific_code;

		// The wait hint must always increase in value.  If you
		// use 10000, then send a 5000, the SCM might get upset.
		// 0 wait hint is only ok if the service is not in transition.

		if( status.dwWaitHint < wait_hint || state == stopped )
			status.dwWaitHint = wait_hint;
	}

	if( ! SetServiceStatus( statusHandle, &status ) )
	{
	    StrBuf msg;
	    DWORD err;
	    Error e;

	    err = GetLastError( );

	    e.Sys( "SetServiceStatus", svcName );
	    e.Set( E_FATAL, "Cannot communicate with Service Control Manager" );
	    e.Fmt( msg, EF_PLAIN );
	    AssertLog.SysLog( NULL, 0, "Perforce Service", msg.Text() );
	}

	if( state == running )
	{
		// Set these back to initial values in prep for the stop.
		status.dwCheckPoint = 0;
		status.dwWaitHint = 1000;
	}
	else
	{
		// We manage the check point internally.
		status.dwCheckPoint++;
	}
}

/*
 * NtService::ControlHandler()
 *
 * This static member function is passed a call back to the SCM which invokes
 * it to control the service. It examines the opcode and invokes the
 * appropriate virtual member function through the global this pointer.
 *
 */

void WINAPI 
NtService::ControlHandler( DWORD opcode )
{
	if( ! global_this )
		return;

	switch( opcode )
	{
	// No support for pause/resume or parameter change.

	case SERVICE_CONTROL_PAUSE: break;
	case SERVICE_CONTROL_CONTINUE: break;

	// We accept and process stop and shutdown requests.  A stop
	// request is not issued at system shutdown time, only a shutdown.

#ifdef SERVICE_CONTROL_PRESHUTDOWN
	case SERVICE_CONTROL_PRESHUTDOWN:
#endif
	case SERVICE_CONTROL_SHUTDOWN:
	case SERVICE_CONTROL_STOP: global_this->Stop(); break;

	// We always report the last known status of our service.  Only
	// the check point value is increasing.

	case SERVICE_CONTROL_INTERROGATE: global_this->SetStatus(); break;

	default: ASSERT(0);
	}
}

/*
 * NtService::StaticRun()
 *
 * This static function is passed as a callback to the SCM because member
 * functions can not be used in callbacks. It then uses the global this
 * pointer to invoke the appropriate virtual member function.
 */

void 
NtService::StaticRun( DWORD argc, char **argv )
{
	if( ! global_this )
		return;
	global_this->Run( argc, argv );
}

/*
 * Starts the service by invoking entry_point.
 */

void 
NtService::Run( DWORD argc, char **argv )
{
	StrBuf msg;

	// Store the service name for the shutdown message.

	strcpy(svcName, argv[0]);

	statusHandle = 
		RegisterServiceCtrlHandler( TEXT(svcName), ControlHandler );

	if( statusHandle == NULL )
	{
	    DWORD err;
	    Error e;

	    err = GetLastError( );

	    e.Sys( "RegisterServiceCtrlHandler", svcName );
	    e.Set( E_FATAL, "Can not register the Perforce Service" );
	    e.Fmt( msg, EF_PLAIN );
	    AssertLog.SysLog( NULL, 0, "Perforce Service", msg.Text() );

	    // If the check point and wait hint are both zero, with
	    // a service state of stopped, the SCM knows we failed.

	    SetStatus( stopped, err, err, 0 );

	    return;
	}

	// Let the Service Mangler know we plan on taking 10 seconds.

	SetStatus( start_pending, 0, 0, 10000 );

	// We want this in the Windows Event log, use SysLog directly.
	// Using Syslog bypasses the logfile setting.

	msg = "Starting the Perforce Service ";
	msg << svcName;
	AssertLog.SysLog( NULL, 0, "Perforce Service", msg.Text() );

	// Start the Perforce server

	entry_point( argc, argv );
}


/*
 * Stops the service. It's not enough to simply set the status to
 * stopped and return -- this will only cause the service to hang for
 * 30 seconds since the service main (the entry_point) thread is still
 * running.  So bring down the service main thread by signalling the
 * event and all will be well.
 */

void 
NtService::Stop()
{
	StrBuf msg;

	// We want this in the Windows Event log, use SysLog directly.
	// Using Syslog bypasses the logfile setting.

	msg = "End of the Perforce Service ";
	msg << svcName;
	AssertLog.SysLog( NULL, 0, "Perforce Service", msg.Text() );

	// We signal the main thread to stop.  It eventually returns
	// and program flow goes through the SCM back to us where we
	// unwind our main via a return.  The SCM is expecting to
	// close everything down.  We must not call ExitProcess().

	Threading::Cancel();
}

# Change User Description Committed
#1 15903 Matt Attaway Everything should be happy now between the Workshop and the depot paths
//guest/perforce_software/p4/2014_2/sys/ntservice.cc
#1 15901 Matt Attaway Clean up code to fit modern Workshop naming standards
//guest/perforce_software/p4/2014.2/sys/ntservice.cc
#1 12189 Matt Attaway Initial (and much belated) drop of 2014.2 p4 source code