diff options
author | Vincent Povirk <vincent@codeweavers.com> | 2014-03-28 02:54:22 +0400 |
---|---|---|
committer | Vincent Povirk <vincent@codeweavers.com> | 2014-03-28 02:55:40 +0400 |
commit | 223e9566a5f3e0618e6911741ef09b3f4889e505 (patch) | |
tree | 2dad7dcafd4e5409a3c7b00e65be110f94a90eed /mcs/class/System.ServiceProcess | |
parent | 369ce3a4fa6beeee9c6bfee7aa32e2e8ad494278 (diff) |
System.ServiceProcess: Implement ServiceBase.Run for Win32 services.
This commit licensed as MIT/X11.
Diffstat (limited to 'mcs/class/System.ServiceProcess')
-rw-r--r-- | mcs/class/System.ServiceProcess/System.ServiceProcess/ServiceBase.cs | 266 |
1 files changed, 253 insertions, 13 deletions
diff --git a/mcs/class/System.ServiceProcess/System.ServiceProcess/ServiceBase.cs b/mcs/class/System.ServiceProcess/System.ServiceProcess/ServiceBase.cs index 55caa203c1a..6e4acb31751 100644 --- a/mcs/class/System.ServiceProcess/System.ServiceProcess/ServiceBase.cs +++ b/mcs/class/System.ServiceProcess/System.ServiceProcess/ServiceBase.cs @@ -5,9 +5,11 @@ // 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 @@ -35,6 +37,7 @@ using System.ComponentModel; using System.Globalization; using System.Diagnostics; using System.Runtime.InteropServices; +using System.Threading; namespace System.ServiceProcess { @@ -49,6 +52,9 @@ namespace System.ServiceProcess // 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; @@ -59,9 +65,10 @@ namespace System.ServiceProcess bool can_stop = true; EventLog event_log; string service_name; -#if NET_2_0 bool can_handle_session_change_event; -#endif + IntPtr service_handle; + ManualResetEvent stop_event; + static bool share_process; public ServiceBase () { @@ -75,6 +82,7 @@ namespace System.ServiceProcess } [DefaultValue (false)] + [MonoTODO] public bool CanHandlePowerEvent { get { return can_handle_power_event; } set { @@ -87,7 +95,6 @@ namespace System.ServiceProcess } } -#if NET_2_0 [DefaultValue (false)] [MonoTODO] [ComVisible (false)] @@ -102,7 +109,6 @@ namespace System.ServiceProcess can_handle_session_change_event = value; } } -#endif [DefaultValue (false)] public bool CanPauseAndContinue { @@ -153,16 +159,14 @@ namespace System.ServiceProcess } } -#if NET_2_0 [ComVisible (false)] public int ExitCode { get; set; } [MonoTODO] [EditorBrowsable (EditorBrowsableState.Advanced)] protected IntPtr ServiceHandle { - get { throw new NotImplementedException (); } + get { return service_handle; } } -#endif [ServiceProcessDescription ("The name by which the service is identified to the system.")] [TypeConverter ("System.Diagnostics.Design.StringValueConverter, " + Consts.AssemblySystem_Design)] @@ -211,7 +215,6 @@ namespace System.ServiceProcess { } -#if NET_2_0 protected virtual void OnSessionChange (SessionChangeDescription changeDescription) { } @@ -223,19 +226,250 @@ namespace System.ServiceProcess 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] + [MonoTODO ("This only makes sense on Windows")] public void ServiceMainCallback (int argCount, IntPtr argPointer) { - throw new NotImplementedException (); + 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); + } } - public void Stop () + private static void Win32RunService (ServiceBase [] services) { - OnStop (); + 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 (); } -#endif + + #endregion Win32 implementation public static void Run (ServiceBase service) { @@ -244,8 +478,14 @@ namespace System.ServiceProcess 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"); } } } |