Jo Shields 3c1f479b9d Imported Upstream version 4.0.0~alpha1
Former-commit-id: 806294f5ded97629b74c85c09952f2a74fe182d9
2015-04-07 09:35:12 +01:00

489 lines
14 KiB
C#

//
// System.ServiceProcess.ServiceBase.cs
//
// Authors:
// Cesar Octavio Lopez Nataren (cesar@ciencias.unam.mx)
// Duncan Mak (duncan@ximian.com)
// Joerg Rosenkranz (joergr@voelcker.com)
// Vincent Povirk (madewokherd@gmail.com)
//
// (C) 2003, Ximian Inc and Cesar Octavio Lopez Nataren.
// (C) 2005, Voelcker Informatik AG
// (C) 2014, CodeWeavers Inc.
//
//
// 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.
//
using System;
using System.ComponentModel;
using System.Globalization;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Threading;
namespace System.ServiceProcess
{
[InstallerType (typeof (ServiceProcessInstaller))]
public class ServiceBase : Component
{
internal delegate void RunServiceCallback (ServiceBase [] services);
// This member is used for interoperation with mono-service
internal static RunServiceCallback RunService;
internal delegate void NotifyStatusCallback (ServiceBase service, ServiceControllerStatus status);
internal static NotifyStatusCallback NotifyStatus;
public const int MaxNameLength = 80;
bool hasStarted;
bool auto_log = true;
bool can_handle_power_event;
bool can_pause_and_continue;
bool can_shutdown;
bool can_stop = true;
EventLog event_log;
string service_name;
bool can_handle_session_change_event;
IntPtr service_handle;
ManualResetEvent stop_event;
static bool share_process;
public ServiceBase ()
{
}
[DefaultValue (true)]
[ServiceProcessDescription ("Whether the service should automatically write to the event log on common events such as Install and Start.")]
public bool AutoLog {
get { return auto_log; }
set { auto_log = value; }
}
[DefaultValue (false)]
[MonoTODO]
public bool CanHandlePowerEvent {
get { return can_handle_power_event; }
set {
if (hasStarted)
throw new InvalidOperationException (
Locale.GetText ("Cannot modify this property " +
"after the service has started."));
can_handle_power_event = value;
}
}
[DefaultValue (false)]
[MonoTODO]
[ComVisible (false)]
public bool CanHandleSessionChangeEvent {
get { return can_handle_session_change_event; }
set {
if (hasStarted)
throw new InvalidOperationException (
Locale.GetText ("Cannot modify this property " +
"after the service has started."));
can_handle_session_change_event = value;
}
}
[DefaultValue (false)]
public bool CanPauseAndContinue {
get { return can_pause_and_continue; }
set {
if (hasStarted)
throw new InvalidOperationException (
Locale.GetText ("Cannot modify this property " +
"after the service has started."));
can_pause_and_continue = value;
}
}
[DefaultValue (false)]
public bool CanShutdown {
get { return can_shutdown; }
set {
if (hasStarted)
throw new InvalidOperationException (
Locale.GetText ("Cannot modify this property " +
"after the service has started."));
can_shutdown = value;
}
}
[DefaultValue (true)]
public bool CanStop {
get { return can_stop; }
set {
if (hasStarted)
throw new InvalidOperationException (
Locale.GetText ("Cannot modify this property " +
"after the service has started."));
can_stop = value;
}
}
[Browsable (false)]
[DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)]
public virtual EventLog EventLog {
get {
if (event_log == null)
event_log = new EventLog ("Application", ".", service_name);
return event_log;
}
}
[ComVisible (false)]
public int ExitCode { get; set; }
[MonoTODO]
[EditorBrowsable (EditorBrowsableState.Advanced)]
protected IntPtr ServiceHandle {
get { return service_handle; }
}
[ServiceProcessDescription ("The name by which the service is identified to the system.")]
[TypeConverter ("System.Diagnostics.Design.StringValueConverter, " + Consts.AssemblySystem_Design)]
public string ServiceName {
get { return service_name; }
set {
if (hasStarted)
throw new InvalidOperationException (
Locale.GetText ("Cannot modify this property " +
"after the service has started."));
service_name = value;
}
}
protected override void Dispose (bool disposing)
{
}
protected virtual void OnStart (string [] args)
{
}
protected virtual void OnStop ()
{
}
protected virtual void OnContinue ()
{
}
protected virtual void OnCustomCommand (int command)
{
}
protected virtual void OnPause ()
{
}
protected virtual bool OnPowerEvent (PowerBroadcastStatus powerStatus)
{
return true;
}
protected virtual void OnShutdown ()
{
}
protected virtual void OnSessionChange (SessionChangeDescription changeDescription)
{
}
[ComVisible (false)]
[MonoTODO]
public void RequestAdditionalTime (int milliseconds)
{
throw new NotImplementedException ();
}
public void Stop ()
{
if (stop_event != null)
stop_event.Set ();
else
OnStop ();
}
private void SetStatus (ServiceControllerStatus status)
{
if (!hasStarted && status != ServiceControllerStatus.Stopped)
hasStarted = true;
if (NotifyStatus != null)
NotifyStatus (this, status);
}
#region Win32 implementation
private const int NO_ERROR = 0;
private const int ERROR_CALL_NOT_IMPLEMENTED = 120;
private const int SERVICE_NO_CHANGE = -1;
[Flags]
private enum SERVICE_CONTROL_ACCEPTED
{
SERVICE_ACCEPT_NONE = 0x0,
SERVICE_ACCEPT_STOP = 0x1,
SERVICE_ACCEPT_PAUSE_CONTINUE = 0x2,
SERVICE_ACCEPT_SHUTDOWN = 0x4,
SERVICE_ACCEPT_PARAMCHANGE = 0x8,
SERVICE_ACCEPT_NETBINDCHANGE = 0x10,
SERVICE_ACCEPT_HARDWAREPROFILECHANGE = 0x20,
SERVICE_ACCEPT_POWEREVENT = 0x40,
SERVICE_ACCEPT_SESSIONCHANGE = 0x80
}
private enum SERVICE_CONTROL_TYPE
{
SERVICE_CONTROL_STOP = 0x1,
SERVICE_CONTROL_PAUSE = 0x2,
SERVICE_CONTROL_CONTINUE = 0x3,
SERVICE_CONTROL_INTERROGATE = 0x4,
SERVICE_CONTROL_SHUTDOWN = 0x5,
SERVICE_CONTROL_PARAMCHANGE = 0x6,
SERVICE_CONTROL_NETBINDADD = 0x7,
SERVICE_CONTROL_NETBINDREMOVE = 0x8,
SERVICE_CONTROL_NETBINDENABLE = 0x9,
SERVICE_CONTROL_NETBINDDISABLE = 0xA,
SERVICE_CONTROL_DEVICEEVENT = 0xB,
SERVICE_CONTROL_HARDWAREPROFILECHANGE = 0xC,
SERVICE_CONTROL_POWEREVENT = 0xD,
SERVICE_CONTROL_SESSIONCHANGE = 0xE
}
private enum SERVICE_TYPE
{
SERVICE_KERNEL_DRIVER = 0x1,
SERVICE_FILE_SYSTEM_DRIVER = 0x2,
SERVICE_ADAPTER = 0x4,
SERVICE_RECOGNIZER_DRIVER = 0x8,
SERVICE_DRIVER = (SERVICE_KERNEL_DRIVER | SERVICE_FILE_SYSTEM_DRIVER | SERVICE_RECOGNIZER_DRIVER),
SERVICE_WIN32_OWN_PROCESS = 0x10,
SERVICE_WIN32_SHARE_PROCESS = 0x20,
SERVICE_INTERACTIVE_PROCESS = 0x100,
SERVICETYPE_NO_CHANGE = SERVICE_NO_CHANGE,
SERVICE_WIN32 = (SERVICE_WIN32_OWN_PROCESS | SERVICE_WIN32_SHARE_PROCESS),
SERVICE_TYPE_ALL = (SERVICE_WIN32 | SERVICE_ADAPTER | SERVICE_DRIVER | SERVICE_INTERACTIVE_PROCESS)
}
[UnmanagedFunctionPointerAttribute (CallingConvention.StdCall)]
private delegate int LPHANDLER_FUNCTION_EX(int dwControl, int dwEventType, IntPtr lpEventData, IntPtr lpContext);
[UnmanagedFunctionPointerAttribute (CallingConvention.StdCall)]
private delegate void LPSERVICE_MAIN_FUNCTION(int dwArgc, IntPtr lpszArgv);
[StructLayout (LayoutKind.Sequential, Pack = 1)]
private struct SERVICE_STATUS
{
public int dwServiceType;
public int dwCurrentState;
public int dwControlsAccepted;
public int dwWin32ExitCode;
public int dwServiceSpecificErrorCode;
public int dwCheckPoint;
public int dwWaitHint;
}
[StructLayout (LayoutKind.Sequential, Pack = 1)]
private struct SERVICE_TABLE_ENTRY
{
[MarshalAs (UnmanagedType.LPWStr)]
public string lpServiceName;
public LPSERVICE_MAIN_FUNCTION lpServiceProc;
}
[DllImport ("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
private static extern IntPtr RegisterServiceCtrlHandlerEx (
string lpServiceName,
LPHANDLER_FUNCTION_EX lpHandlerProc,
IntPtr lpContext);
[DllImport ("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
private static extern bool SetServiceStatus (
IntPtr hServiceStatus,
[MarshalAs (UnmanagedType.LPStruct)] SERVICE_STATUS status);
[DllImport ("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
private static extern bool StartServiceCtrlDispatcher (
[MarshalAs (UnmanagedType.LPArray)] SERVICE_TABLE_ENTRY[] lpServiceTable);
private static void Win32NotifyStatus (ServiceBase service, ServiceControllerStatus status)
{
SERVICE_STATUS service_status = new SERVICE_STATUS ();
service_status.dwServiceType = share_process ? (int)SERVICE_TYPE.SERVICE_WIN32_SHARE_PROCESS : (int)SERVICE_TYPE.SERVICE_WIN32_OWN_PROCESS;
service_status.dwCurrentState = (int)status;
if (status != ServiceControllerStatus.StartPending)
{
if (service.can_stop)
service_status.dwControlsAccepted |= (int)SERVICE_CONTROL_ACCEPTED.SERVICE_ACCEPT_STOP;
if (service.can_pause_and_continue)
service_status.dwControlsAccepted |= (int)SERVICE_CONTROL_ACCEPTED.SERVICE_ACCEPT_PAUSE_CONTINUE;
if (service.can_handle_power_event)
service_status.dwControlsAccepted |= (int)SERVICE_CONTROL_ACCEPTED.SERVICE_ACCEPT_POWEREVENT;
if (service.can_handle_session_change_event)
service_status.dwControlsAccepted |= (int)SERVICE_CONTROL_ACCEPTED.SERVICE_ACCEPT_SESSIONCHANGE;
if (service.can_shutdown)
service_status.dwControlsAccepted |= (int)SERVICE_CONTROL_ACCEPTED.SERVICE_ACCEPT_SHUTDOWN;
}
service_status.dwWin32ExitCode = service.ExitCode;
service_status.dwWaitHint = 5000;
SetServiceStatus (service.service_handle, service_status);
}
private int Win32HandlerFn (int dwControl, int dwEventType, IntPtr lpEventData, IntPtr lpContext)
{
switch ((SERVICE_CONTROL_TYPE)dwControl)
{
case SERVICE_CONTROL_TYPE.SERVICE_CONTROL_STOP:
if (can_stop)
{
Stop ();
return NO_ERROR;
}
break;
case SERVICE_CONTROL_TYPE.SERVICE_CONTROL_PAUSE:
if (can_pause_and_continue)
{
SetStatus (ServiceControllerStatus.PausePending);
OnPause ();
SetStatus (ServiceControllerStatus.Paused);
return NO_ERROR;
}
break;
case SERVICE_CONTROL_TYPE.SERVICE_CONTROL_CONTINUE:
if (can_pause_and_continue)
{
SetStatus (ServiceControllerStatus.ContinuePending);
OnContinue ();
SetStatus (ServiceControllerStatus.Running);
return NO_ERROR;
}
break;
case SERVICE_CONTROL_TYPE.SERVICE_CONTROL_INTERROGATE:
return NO_ERROR;
case SERVICE_CONTROL_TYPE.SERVICE_CONTROL_SHUTDOWN:
if (can_shutdown)
{
OnShutdown ();
return NO_ERROR;
}
break;
default:
break;
}
return ERROR_CALL_NOT_IMPLEMENTED;
}
[ComVisible (false)]
[EditorBrowsable (EditorBrowsableState.Never)]
[MonoTODO ("This only makes sense on Windows")]
public void ServiceMainCallback (int argCount, IntPtr argPointer)
{
LPHANDLER_FUNCTION_EX handler = new LPHANDLER_FUNCTION_EX (Win32HandlerFn);
// handler needs to last until the service stops
service_handle = RegisterServiceCtrlHandlerEx (ServiceName ?? "", handler, IntPtr.Zero);
if (service_handle != IntPtr.Zero)
{
SetStatus (ServiceControllerStatus.StartPending);
stop_event = new ManualResetEvent (false);
string[] args = new string[argCount];
for (int i=0; i<argCount; i++)
{
IntPtr arg = Marshal.ReadIntPtr (argPointer, IntPtr.Size * i);
args[i] = Marshal.PtrToStringUni (arg);
}
OnStart (args);
SetStatus (ServiceControllerStatus.Running);
stop_event.WaitOne ();
SetStatus (ServiceControllerStatus.StopPending);
OnStop ();
SetStatus (ServiceControllerStatus.Stopped);
}
}
private static void Win32RunService (ServiceBase [] services)
{
SERVICE_TABLE_ENTRY[] table = new SERVICE_TABLE_ENTRY[services.Length + 1];
NotifyStatus = new NotifyStatusCallback (Win32NotifyStatus);
for (int i = 0; i<services.Length; i++)
{
table[i].lpServiceName = services[i].ServiceName ?? "";
table[i].lpServiceProc = new LPSERVICE_MAIN_FUNCTION (services[i].ServiceMainCallback);
}
// table[services.Length] is a NULL terminator
share_process = (services.Length > 1);
if (!StartServiceCtrlDispatcher (table))
throw new Win32Exception ();
}
#endregion Win32 implementation
public static void Run (ServiceBase service)
{
Run (new ServiceBase [] { service });
}
public static void Run (ServiceBase [] services)
{
int p = (int) Environment.OSVersion.Platform;
if (RunService != null)
RunService (services);
else if (!(p == 4 || p == 128 || p == 6))
Win32RunService (services);
else
Console.Error.WriteLine("Use mono-service to start service processes");
}
}
}